From d077d7ef4cb885b7b5d8b3c85c10540ab8e03b6e Mon Sep 17 00:00:00 2001 From: Colin Barber Date: Thu, 25 Jun 2026 13:31:44 -0300 Subject: [PATCH] feat: Add source to role assignments to distinguish group grants Adds a `source` object to `RoleAssignment` responses, mirroring the REST API change. The source carries a `type` ("direct" | "group") and a nullable `groupRoleAssignmentId`, letting callers tell directly-assigned roles apart from those derived from a group role assignment. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/authorization/authorization.spec.ts | 40 +++++++++++++++++++ .../fixtures/list-role-assignments.json | 4 ++ .../fixtures/role-assignment.json | 4 ++ .../interfaces/role-assignment.interface.ts | 15 +++++++ .../serializers/role-assignment.serializer.ts | 4 ++ 5 files changed, 67 insertions(+) diff --git a/src/authorization/authorization.spec.ts b/src/authorization/authorization.spec.ts index d31efa027..0c264a80d 100644 --- a/src/authorization/authorization.spec.ts +++ b/src/authorization/authorization.spec.ts @@ -1629,6 +1629,30 @@ describe('Authorization', () => { }); describe('listRoleAssignments', () => { + it('deserializes a group-derived source', async () => { + fetchOnce({ + ...listRoleAssignmentsFixture, + data: [ + { + ...listRoleAssignmentsFixture.data[0], + source: { + type: 'group', + group_role_assignment_id: testGroupRoleAssignmentId, + }, + }, + ], + }); + + const result = await workos.authorization.listRoleAssignments({ + organizationMembershipId: testOrgMembershipId, + }); + + expect(result.data[0].source).toEqual({ + type: 'group', + groupRoleAssignmentId: testGroupRoleAssignmentId, + }); + }); + it('lists role assignments for an organization membership', async () => { fetchOnce(listRoleAssignmentsFixture); @@ -1651,6 +1675,10 @@ describe('Authorization', () => { externalId: 'doc-123', resourceTypeSlug: 'document', }, + source: { + type: 'direct', + groupRoleAssignmentId: null, + }, createdAt: '2024-01-15T09:30:00.000Z', updatedAt: '2024-01-15T09:30:00.000Z', }); @@ -1797,6 +1825,10 @@ describe('Authorization', () => { externalId: 'doc-123', resourceTypeSlug: 'document', }, + source: { + type: 'direct', + groupRoleAssignmentId: null, + }, createdAt: '2024-01-15T09:30:00.000Z', updatedAt: '2024-01-15T09:30:00.000Z', }); @@ -1891,6 +1923,10 @@ describe('Authorization', () => { externalId: 'doc-123', resourceTypeSlug: 'document', }, + source: { + type: 'direct', + groupRoleAssignmentId: null, + }, createdAt: '2024-01-15T09:30:00.000Z', updatedAt: '2024-01-15T09:30:00.000Z', }); @@ -1993,6 +2029,10 @@ describe('Authorization', () => { externalId: 'doc-123', resourceTypeSlug: 'document', }, + source: { + type: 'direct', + groupRoleAssignmentId: null, + }, createdAt: '2024-01-15T09:30:00.000Z', updatedAt: '2024-01-15T09:30:00.000Z', }); diff --git a/src/authorization/fixtures/list-role-assignments.json b/src/authorization/fixtures/list-role-assignments.json index 2739edc6e..486b1fce2 100644 --- a/src/authorization/fixtures/list-role-assignments.json +++ b/src/authorization/fixtures/list-role-assignments.json @@ -13,6 +13,10 @@ "external_id": "doc-123", "resource_type_slug": "document" }, + "source": { + "type": "direct", + "group_role_assignment_id": null + }, "created_at": "2024-01-15T09:30:00.000Z", "updated_at": "2024-01-15T09:30:00.000Z" } diff --git a/src/authorization/fixtures/role-assignment.json b/src/authorization/fixtures/role-assignment.json index ea957988e..c6086593c 100644 --- a/src/authorization/fixtures/role-assignment.json +++ b/src/authorization/fixtures/role-assignment.json @@ -10,6 +10,10 @@ "external_id": "doc-123", "resource_type_slug": "document" }, + "source": { + "type": "direct", + "group_role_assignment_id": null + }, "created_at": "2024-01-15T09:30:00.000Z", "updated_at": "2024-01-15T09:30:00.000Z" } diff --git a/src/authorization/interfaces/role-assignment.interface.ts b/src/authorization/interfaces/role-assignment.interface.ts index dd4b8ffb8..c5e26b80e 100644 --- a/src/authorization/interfaces/role-assignment.interface.ts +++ b/src/authorization/interfaces/role-assignment.interface.ts @@ -14,6 +14,18 @@ export interface RoleAssignmentResourceResponse { resource_type_slug: string; } +export interface RoleAssignmentSource { + /** Whether the role was assigned directly or derived from a group. */ + type: 'direct' | 'group'; + /** The ID of the group role assignment the role was derived from, or null if direct. */ + groupRoleAssignmentId: string | null; +} + +export interface RoleAssignmentSourceResponse { + type: 'direct' | 'group'; + group_role_assignment_id: string | null; +} + export interface RoleAssignment { /** Distinguishes the role assignment object. */ object: 'role_assignment'; @@ -25,6 +37,8 @@ export interface RoleAssignment { role: RoleAssignmentRole; /** The resource to which the role is assigned. */ resource: RoleAssignmentResource; + /** The origin of the role assignment. */ + source: RoleAssignmentSource; /** An ISO 8601 timestamp. */ createdAt: string; /** An ISO 8601 timestamp. */ @@ -37,6 +51,7 @@ export interface RoleAssignmentResponse { organization_membership_id: string; role: RoleAssignmentRole; resource: RoleAssignmentResourceResponse; + source: RoleAssignmentSourceResponse; created_at: string; updated_at: string; } diff --git a/src/authorization/serializers/role-assignment.serializer.ts b/src/authorization/serializers/role-assignment.serializer.ts index 65103b19b..aea002cfe 100644 --- a/src/authorization/serializers/role-assignment.serializer.ts +++ b/src/authorization/serializers/role-assignment.serializer.ts @@ -15,6 +15,10 @@ export const deserializeRoleAssignment = ( externalId: response.resource.external_id, resourceTypeSlug: response.resource.resource_type_slug, }, + source: { + type: response.source.type, + groupRoleAssignmentId: response.source.group_role_assignment_id, + }, createdAt: response.created_at, updatedAt: response.updated_at, });