From bf34ac18fcc508c10125f22d72382ec3eda47831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Mon, 17 Mar 2025 15:32:12 +0000 Subject: [PATCH] chore: add user access overview schema (#9552) https://linear.app/unleash/issue/2-3403/add-response-schema-for-access-overview Adds a response schema for the user access overview. --- src/lib/openapi/spec/index.ts | 1 + .../spec/user-access-overview-schema.ts | 121 ++++++++++++++++++ src/lib/routes/admin-api/user-admin.ts | 28 ++-- 3 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 src/lib/openapi/spec/user-access-overview-schema.ts diff --git a/src/lib/openapi/spec/index.ts b/src/lib/openapi/spec/index.ts index 912979ead8..5084a747ba 100644 --- a/src/lib/openapi/spec/index.ts +++ b/src/lib/openapi/spec/index.ts @@ -209,6 +209,7 @@ export * from './update-tag-type-schema'; export * from './update-tags-schema'; export * from './update-user-schema'; export * from './upsert-segment-schema'; +export * from './user-access-overview-schema'; export * from './user-schema'; export * from './users-groups-base-schema'; export * from './users-schema'; diff --git a/src/lib/openapi/spec/user-access-overview-schema.ts b/src/lib/openapi/spec/user-access-overview-schema.ts new file mode 100644 index 0000000000..605d57905c --- /dev/null +++ b/src/lib/openapi/spec/user-access-overview-schema.ts @@ -0,0 +1,121 @@ +import type { FromSchema } from 'json-schema-to-ts'; +import { userSchema } from './user-schema'; +import { roleSchema } from './role-schema'; + +const permission = { + type: 'object', + required: ['id', 'name', 'displayName', 'type'], + additionalProperties: false, + properties: { + id: { + type: 'integer', + description: 'The ID of the permission', + example: 1, + }, + name: { + type: 'string', + description: 'The name of the permission', + example: 'CREATE_FEATURE_STRATEGY', + }, + displayName: { + type: 'string', + description: 'The display name of the permission', + example: 'Create activation strategies', + }, + type: { + type: 'string', + description: 'The type of the permission', + example: 'environment', + }, + environment: { + type: 'string', + nullable: true, + description: 'The environment that the permission applies to', + example: 'dev', + }, + }, +} as const; + +const permissionWithHasPermission = { + ...permission, + required: [...permission.required, 'hasPermission'], + properties: { + ...permission.properties, + hasPermission: { + type: 'boolean', + description: 'Whether the user has this permission', + example: true, + }, + }, +} as const; + +export const userAccessOverviewSchema = { + $id: '#/components/schemas/userAccessOverviewSchema', + type: 'object', + required: ['overview', 'user', 'rootRole', 'projectRoles'], + additionalProperties: false, + description: + 'Describes the access overview (list of permissions and metadata) for a user.', + properties: { + overview: { + type: 'object', + required: ['root', 'project', 'environment'], + additionalProperties: false, + description: + 'The access overview (list of permissions) for the user', + properties: { + root: { + type: 'array', + description: 'The list of root permissions', + items: permissionWithHasPermission, + }, + project: { + type: 'array', + description: 'The list of project permissions', + items: permissionWithHasPermission, + }, + environment: { + type: 'array', + description: 'The list of environment permissions', + items: permissionWithHasPermission, + }, + }, + }, + user: { + description: 'The user that this access overview is for', + $ref: userSchema.$id, + }, + rootRole: { + description: 'The name of the root role that this user has', + $ref: roleSchema.$id, + }, + projectRoles: { + type: 'array', + description: + 'The list of project roles that this user has in the selected project', + items: { + type: 'object', + required: [...roleSchema.required, 'permissions'], + additionalProperties: false, + properties: { + ...roleSchema.properties, + permissions: { + type: 'array', + description: 'The permissions that this role has', + items: permission, + }, + }, + }, + }, + }, + components: { + schemas: { + userSchema, + roleSchema, + }, + }, +} as const; + +export type UserAccessOverviewSchema = FromSchema< + typeof userAccessOverviewSchema +>; diff --git a/src/lib/routes/admin-api/user-admin.ts b/src/lib/routes/admin-api/user-admin.ts index 4d285e95e2..2ef53d848b 100644 --- a/src/lib/routes/admin-api/user-admin.ts +++ b/src/lib/routes/admin-api/user-admin.ts @@ -55,6 +55,10 @@ import { type CreateUserResponseSchema, } from '../../openapi/spec/create-user-response-schema'; import type { IRoleWithPermissions } from '../../types/stores/access-store'; +import { + type UserAccessOverviewSchema, + userAccessOverviewSchema, +} from '../../openapi'; export default class UserAdminController extends Controller { private flagResolver: IFlagResolver; @@ -260,7 +264,7 @@ export default class UserAdminController extends Controller { handler: this.getPermissions, middleware: [ openApiService.validPath({ - tags: ['Auth'], + tags: ['Unstable'], operationId: 'getUserPermissions', summary: 'Returns the list of permissions for the user', description: @@ -293,7 +297,7 @@ export default class UserAdminController extends Controller { }, ], responses: { - 200: emptyResponse, // TODO define schema + 200: createResponseSchema(userAccessOverviewSchema.$id), ...getStandardResponses(401, 403, 415), }, }), @@ -722,7 +726,7 @@ export default class UserAdminController extends Controller { unknown, { project?: string; environment?: string } >, - res: Response, + res: Response, ): Promise { const { project, environment } = req.query; const user = await this.userService.getUser(req.params.id); @@ -747,13 +751,17 @@ export default class UserAdminController extends Controller { environment, ); - // TODO add response validation based on the schema - res.status(200).json({ - overview, - user, - rootRole, - projectRoles, - }); + this.openApiService.respondWithValidation( + 200, + res, + userAccessOverviewSchema.$id, + { + overview, + user: serializeDates(user), + rootRole, + projectRoles, + }, + ); } async throwIfScimUser({