diff --git a/src/authorization/authorization.spec.ts b/src/authorization/authorization.spec.ts index f56044b09..e43d2d3a9 100644 --- a/src/authorization/authorization.spec.ts +++ b/src/authorization/authorization.spec.ts @@ -10,6 +10,8 @@ import environmentRoleFixture from './fixtures/environment-role.json'; import listEnvironmentRolesFixture from './fixtures/list-environment-roles.json'; import organizationRoleFixture from './fixtures/organization-role.json'; import listOrganizationRolesFixture from './fixtures/list-organization-roles.json'; +import permissionFixture from './fixtures/permission.json'; +import listPermissionsFixture from './fixtures/list-permissions.json'; const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); const testOrgId = 'org_01HXYZ123ABC456DEF789ABC'; @@ -445,4 +447,172 @@ describe('Authorization', () => { ); }); }); + + describe('createPermission', () => { + it('creates a permission', async () => { + fetchOnce(permissionFixture, { status: 201 }); + + const permission = await workos.authorization.createPermission({ + slug: 'users:read', + name: 'Read Users', + description: 'Allows reading user data', + }); + + expect(fetchURL()).toContain('/authorization/permissions'); + expect(fetchBody()).toEqual({ + slug: 'users:read', + name: 'Read Users', + description: 'Allows reading user data', + }); + expect(permission).toMatchObject({ + object: 'permission', + id: 'perm_01HXYZ123ABC456DEF789GHI', + slug: 'users:read', + name: 'Read Users', + description: 'Allows reading user data', + system: false, + }); + }); + + it('creates a permission without description', async () => { + fetchOnce({ ...permissionFixture, description: null }, { status: 201 }); + + const permission = await workos.authorization.createPermission({ + slug: 'users:read', + name: 'Read Users', + }); + + expect(fetchBody()).toEqual({ + slug: 'users:read', + name: 'Read Users', + }); + expect(permission.description).toBeNull(); + }); + }); + + describe('listPermissions', () => { + it('returns permissions', async () => { + fetchOnce(listPermissionsFixture); + + const { data, object, listMetadata } = + await workos.authorization.listPermissions(); + + expect(fetchURL()).toContain('/authorization/permissions'); + expect(object).toEqual('list'); + expect(data).toHaveLength(2); + expect(data).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + object: 'permission', + id: 'perm_01HXYZ123ABC456DEF789GHI', + slug: 'users:read', + name: 'Read Users', + }), + expect.objectContaining({ + object: 'permission', + id: 'perm_01HXYZ123ABC456DEF789GHJ', + slug: 'users:write', + name: 'Write Users', + }), + ]), + ); + expect(listMetadata).toEqual({ + before: null, + after: 'perm_01HXYZ123ABC456DEF789GHJ', + }); + }); + + it('passes pagination parameters', async () => { + fetchOnce(listPermissionsFixture); + + await workos.authorization.listPermissions({ + limit: 10, + after: 'perm_01HXYZ123ABC456DEF789GHI', + order: 'desc', + }); + + expect(fetchSearchParams()).toEqual({ + limit: '10', + after: 'perm_01HXYZ123ABC456DEF789GHI', + order: 'desc', + }); + }); + }); + + describe('getPermission', () => { + it('gets a permission by slug', async () => { + fetchOnce(permissionFixture); + + const permission = await workos.authorization.getPermission('users:read'); + + expect(fetchURL()).toContain('/authorization/permissions/users:read'); + expect(permission).toMatchObject({ + object: 'permission', + id: 'perm_01HXYZ123ABC456DEF789GHI', + slug: 'users:read', + name: 'Read Users', + description: 'Allows reading user data', + system: false, + }); + }); + }); + + describe('updatePermission', () => { + it('updates a permission', async () => { + const updatedPermissionFixture = { + ...permissionFixture, + name: 'Read All Users', + description: 'Updated description', + }; + fetchOnce(updatedPermissionFixture); + + const permission = await workos.authorization.updatePermission( + 'users:read', + { + name: 'Read All Users', + description: 'Updated description', + }, + ); + + expect(fetchURL()).toContain('/authorization/permissions/users:read'); + expect(fetchBody()).toEqual({ + name: 'Read All Users', + description: 'Updated description', + }); + expect(permission).toMatchObject({ + name: 'Read All Users', + description: 'Updated description', + }); + }); + + it('clears description when set to null', async () => { + const updatedPermissionFixture = { + ...permissionFixture, + description: null, + }; + fetchOnce(updatedPermissionFixture); + + const permission = await workos.authorization.updatePermission( + 'users:read', + { + description: null, + }, + ); + + expect(fetchBody()).toEqual({ + description: null, + }); + expect(permission.description).toBeNull(); + }); + }); + + describe('deletePermission', () => { + it('deletes a permission', async () => { + fetchOnce({}, { status: 204 }); + + await workos.authorization.deletePermission('users:read'); + + expect(fetchURL()).toContain('/authorization/permissions/users:read'); + }); + }); }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 8324095ef..7c7edd61c 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -17,6 +17,13 @@ import { CreateOrganizationRoleOptions, UpdateOrganizationRoleOptions, ListOrganizationRolesOptions, + Permission, + PermissionResponse, + PermissionList, + PermissionListResponse, + CreatePermissionOptions, + UpdatePermissionOptions, + ListPermissionsOptions, } from './interfaces'; import { deserializeEnvironmentRole, @@ -26,6 +33,9 @@ import { deserializeOrganizationRole, serializeCreateOrganizationRoleOptions, serializeUpdateOrganizationRoleOptions, + deserializePermission, + serializeCreatePermissionOptions, + serializeUpdatePermissionOptions, } from './serializers'; export class Authorization { @@ -183,4 +193,53 @@ export class Authorization { `/authorization/organizations/${organizationId}/roles/${slug}/permissions/${permissionSlug}`, ); } + + async createPermission( + options: CreatePermissionOptions, + ): Promise { + const { data } = await this.workos.post( + '/authorization/permissions', + serializeCreatePermissionOptions(options), + ); + return deserializePermission(data); + } + + async listPermissions( + options?: ListPermissionsOptions, + ): Promise { + const { data } = await this.workos.get( + '/authorization/permissions', + { query: options }, + ); + return { + object: 'list', + data: data.data.map(deserializePermission), + listMetadata: { + before: data.list_metadata.before, + after: data.list_metadata.after, + }, + }; + } + + async getPermission(slug: string): Promise { + const { data } = await this.workos.get( + `/authorization/permissions/${slug}`, + ); + return deserializePermission(data); + } + + async updatePermission( + slug: string, + options: UpdatePermissionOptions, + ): Promise { + const { data } = await this.workos.patch( + `/authorization/permissions/${slug}`, + serializeUpdatePermissionOptions(options), + ); + return deserializePermission(data); + } + + async deletePermission(slug: string): Promise { + await this.workos.delete(`/authorization/permissions/${slug}`); + } } diff --git a/src/authorization/fixtures/list-permissions.json b/src/authorization/fixtures/list-permissions.json new file mode 100644 index 000000000..c9f4bc8a2 --- /dev/null +++ b/src/authorization/fixtures/list-permissions.json @@ -0,0 +1,29 @@ +{ + "object": "list", + "data": [ + { + "object": "permission", + "id": "perm_01HXYZ123ABC456DEF789GHI", + "slug": "users:read", + "name": "Read Users", + "description": "Allows reading user data", + "system": false, + "created_at": "2024-01-15T08:00:00.000Z", + "updated_at": "2024-01-15T08:00:00.000Z" + }, + { + "object": "permission", + "id": "perm_01HXYZ123ABC456DEF789GHJ", + "slug": "users:write", + "name": "Write Users", + "description": null, + "system": true, + "created_at": "2024-01-15T09:00:00.000Z", + "updated_at": "2024-01-15T09:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": "perm_01HXYZ123ABC456DEF789GHJ" + } +} diff --git a/src/authorization/fixtures/permission.json b/src/authorization/fixtures/permission.json new file mode 100644 index 000000000..d8759fba1 --- /dev/null +++ b/src/authorization/fixtures/permission.json @@ -0,0 +1,10 @@ +{ + "object": "permission", + "id": "perm_01HXYZ123ABC456DEF789GHI", + "slug": "users:read", + "name": "Read Users", + "description": "Allows reading user data", + "system": false, + "created_at": "2024-01-15T08:00:00.000Z", + "updated_at": "2024-01-15T08:00:00.000Z" +} diff --git a/src/authorization/interfaces/create-permission-options.interface.ts b/src/authorization/interfaces/create-permission-options.interface.ts new file mode 100644 index 000000000..d69437fb5 --- /dev/null +++ b/src/authorization/interfaces/create-permission-options.interface.ts @@ -0,0 +1,11 @@ +export interface CreatePermissionOptions { + slug: string; + name: string; + description?: string; +} + +export interface SerializedCreatePermissionOptions { + slug: string; + name: string; + description?: string; +} diff --git a/src/authorization/interfaces/index.ts b/src/authorization/interfaces/index.ts index 0f52d5312..1bbcd9b0f 100644 --- a/src/authorization/interfaces/index.ts +++ b/src/authorization/interfaces/index.ts @@ -6,3 +6,7 @@ export * from './organization-role.interface'; export * from './create-organization-role-options.interface'; export * from './update-organization-role-options.interface'; export * from './list-organization-roles-options.interface'; +export * from './permission.interface'; +export * from './create-permission-options.interface'; +export * from './update-permission-options.interface'; +export * from './list-permissions-options.interface'; diff --git a/src/authorization/interfaces/list-permissions-options.interface.ts b/src/authorization/interfaces/list-permissions-options.interface.ts new file mode 100644 index 000000000..46af16b10 --- /dev/null +++ b/src/authorization/interfaces/list-permissions-options.interface.ts @@ -0,0 +1,6 @@ +export interface ListPermissionsOptions { + before?: string; + after?: string; + limit?: number; + order?: 'asc' | 'desc'; +} diff --git a/src/authorization/interfaces/permission.interface.ts b/src/authorization/interfaces/permission.interface.ts new file mode 100644 index 000000000..a70c14804 --- /dev/null +++ b/src/authorization/interfaces/permission.interface.ts @@ -0,0 +1,39 @@ +export interface Permission { + object: 'permission'; + id: string; + slug: string; + name: string; + description: string | null; + system: boolean; + createdAt: string; + updatedAt: string; +} + +export interface PermissionResponse { + object: 'permission'; + id: string; + slug: string; + name: string; + description: string | null; + system: boolean; + created_at: string; + updated_at: string; +} + +export interface PermissionList { + object: 'list'; + data: Permission[]; + listMetadata: { + before: string | null; + after: string | null; + }; +} + +export interface PermissionListResponse { + object: 'list'; + data: PermissionResponse[]; + list_metadata: { + before: string | null; + after: string | null; + }; +} diff --git a/src/authorization/interfaces/update-permission-options.interface.ts b/src/authorization/interfaces/update-permission-options.interface.ts new file mode 100644 index 000000000..600645607 --- /dev/null +++ b/src/authorization/interfaces/update-permission-options.interface.ts @@ -0,0 +1,9 @@ +export interface UpdatePermissionOptions { + name?: string; + description?: string | null; +} + +export interface SerializedUpdatePermissionOptions { + name?: string; + description?: string | null; +} diff --git a/src/authorization/serializers/create-permission-options.serializer.ts b/src/authorization/serializers/create-permission-options.serializer.ts new file mode 100644 index 000000000..f3f0081ee --- /dev/null +++ b/src/authorization/serializers/create-permission-options.serializer.ts @@ -0,0 +1,12 @@ +import { + CreatePermissionOptions, + SerializedCreatePermissionOptions, +} from '../interfaces/create-permission-options.interface'; + +export const serializeCreatePermissionOptions = ( + options: CreatePermissionOptions, +): SerializedCreatePermissionOptions => ({ + slug: options.slug, + name: options.name, + description: options.description, +}); diff --git a/src/authorization/serializers/index.ts b/src/authorization/serializers/index.ts index 5e0f4c741..bbaf011f3 100644 --- a/src/authorization/serializers/index.ts +++ b/src/authorization/serializers/index.ts @@ -4,3 +4,6 @@ export * from './update-environment-role-options.serializer'; export * from './organization-role.serializer'; export * from './create-organization-role-options.serializer'; export * from './update-organization-role-options.serializer'; +export * from './permission.serializer'; +export * from './create-permission-options.serializer'; +export * from './update-permission-options.serializer'; diff --git a/src/authorization/serializers/permission.serializer.ts b/src/authorization/serializers/permission.serializer.ts new file mode 100644 index 000000000..a8b698f31 --- /dev/null +++ b/src/authorization/serializers/permission.serializer.ts @@ -0,0 +1,17 @@ +import { + Permission, + PermissionResponse, +} from '../interfaces/permission.interface'; + +export const deserializePermission = ( + permission: PermissionResponse, +): Permission => ({ + object: permission.object, + id: permission.id, + slug: permission.slug, + name: permission.name, + description: permission.description, + system: permission.system, + createdAt: permission.created_at, + updatedAt: permission.updated_at, +}); diff --git a/src/authorization/serializers/update-permission-options.serializer.ts b/src/authorization/serializers/update-permission-options.serializer.ts new file mode 100644 index 000000000..4ac4e67d0 --- /dev/null +++ b/src/authorization/serializers/update-permission-options.serializer.ts @@ -0,0 +1,11 @@ +import { + UpdatePermissionOptions, + SerializedUpdatePermissionOptions, +} from '../interfaces/update-permission-options.interface'; + +export const serializeUpdatePermissionOptions = ( + options: UpdatePermissionOptions, +): SerializedUpdatePermissionOptions => ({ + name: options.name, + description: options.description, +});