From c5880e23b1e1259d4b179d7d444a8eaedfee75f6 Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Thu, 23 May 2024 12:13:00 +0200 Subject: [PATCH 1/8] Add initial structure of the authorization api layer. --- .../api/authorization-reference.controller.ts | 33 ++++++++++++++ .../api/authorization-reference.uc.ts | 31 +++++++++++++ .../api/dto/authorization-body.params.ts | 19 ++++++++ .../api/dto/authorization-url.params.ts | 17 +++++++ .../api/dto/authorization.reponse.ts | 45 +++++++++++++++++++ .../modules/authorization/api/dto/index.ts | 3 ++ .../mapper/authorization.response.mapper.ts | 41 +++++++++++++++++ .../modules/authorization/api/mapper/index.ts | 1 + 8 files changed, 190 insertions(+) create mode 100644 apps/server/src/modules/authorization/api/authorization-reference.controller.ts create mode 100644 apps/server/src/modules/authorization/api/authorization-reference.uc.ts create mode 100644 apps/server/src/modules/authorization/api/dto/authorization-body.params.ts create mode 100644 apps/server/src/modules/authorization/api/dto/authorization-url.params.ts create mode 100644 apps/server/src/modules/authorization/api/dto/authorization.reponse.ts create mode 100644 apps/server/src/modules/authorization/api/dto/index.ts create mode 100644 apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts create mode 100644 apps/server/src/modules/authorization/api/mapper/index.ts diff --git a/apps/server/src/modules/authorization/api/authorization-reference.controller.ts b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts new file mode 100644 index 00000000000..e2ecc24a03c --- /dev/null +++ b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts @@ -0,0 +1,33 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { Body, Controller, InternalServerErrorException, Param, Post, UnauthorizedException } from '@nestjs/common'; +import { ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiValidationError } from '@shared/common'; +import { AuthorizationReferenceUc } from './authorization-reference.uc'; +import { AuthorizationBodyParams, AuthorizationUrlParams, AuthorizedReponse } from './dto'; + +@Authenticate('jwt') +@ApiTags('Authorization') +@Controller('authorization') +export class AuthorizationController { + constructor(private readonly authorizationReferenceUc: AuthorizationReferenceUc) {} + + @ApiResponse({ status: 200, type: AuthorizedReponse }) + @ApiResponse({ status: 400, type: ApiValidationError }) + @ApiResponse({ status: 401, type: UnauthorizedException }) + @ApiResponse({ status: 500, type: InternalServerErrorException }) + @Post('authorize/referenceType/:referenceType/referenceId/:referenceId') + public async authorizeByReference( + @Param() urlParams: AuthorizationUrlParams, + @Body() body: AuthorizationBodyParams, + @CurrentUser() user: ICurrentUser + ): Promise { + const successAuthorizationReponse = await this.authorizationReferenceUc.authorizeByReference( + user.userId, + urlParams.referenceType, + urlParams.referenceId, + body + ); + + return successAuthorizationReponse; + } +} diff --git a/apps/server/src/modules/authorization/api/authorization-reference.uc.ts b/apps/server/src/modules/authorization/api/authorization-reference.uc.ts new file mode 100644 index 00000000000..bf58395101b --- /dev/null +++ b/apps/server/src/modules/authorization/api/authorization-reference.uc.ts @@ -0,0 +1,31 @@ +import { EntityId } from '@shared/domain/types'; +import { AuthorizableReferenceType, AuthorizationContext, AuthorizationReferenceService } from '../domain'; +import { AuthorizedReponse } from './dto'; +import { AuthorizationReponseMapper } from './mapper'; + +export class AuthorizationReferenceUc { + constructor(private readonly authorizationReferenceService: AuthorizationReferenceService) {} + + public async authorizeByReference( + userId: EntityId, + authorizableReferenceType: AuthorizableReferenceType, + authorizableReferenceId: EntityId, + context: AuthorizationContext + ): Promise { + await this.authorizationReferenceService.checkPermissionByReferences( + userId, + authorizableReferenceType, + authorizableReferenceId, + context + ); + + const successAuthorizationReponse = AuthorizationReponseMapper.mapToSuccessResponse( + userId, + authorizableReferenceType, + authorizableReferenceId, + context + ); + + return successAuthorizationReponse; + } +} diff --git a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts new file mode 100644 index 00000000000..c03977e5244 --- /dev/null +++ b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts @@ -0,0 +1,19 @@ +import { Permission } from '@shared/domain/interface'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEnum } from 'class-validator'; +import { Action, AuthorizationContext } from '../../domain'; + +export class AuthorizationBodyParams implements AuthorizationContext { + @IsEnum(Action) + @ApiProperty({ description: 'Define for which action the operation should be performend.' }) + action!: Action; + + @IsArray() + @IsEnum(Permission, { each: true }) + @ApiProperty({ + enum: Permission, + isArray: true, + description: 'Needed user permissions based on user role, that are needed to execute the operation.', + }) + requiredPermissions!: Permission[]; +} diff --git a/apps/server/src/modules/authorization/api/dto/authorization-url.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-url.params.ts new file mode 100644 index 00000000000..98796d09bf8 --- /dev/null +++ b/apps/server/src/modules/authorization/api/dto/authorization-url.params.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsMongoId } from 'class-validator'; +import { AuthorizableReferenceType } from '../../domain'; + +export class AuthorizationUrlParams { + @IsEnum(AuthorizableReferenceType) + @ApiProperty({ + enum: AuthorizableReferenceType, + description: 'Define for which known entity, or domain object the operation should be peformend.', + example: AuthorizableReferenceType.User, + }) + referenceType!: AuthorizableReferenceType; + + @IsMongoId() + @ApiProperty({ description: 'The id of the entity/domain object of the defined referenceType.' }) + referenceId!: string; +} diff --git a/apps/server/src/modules/authorization/api/dto/authorization.reponse.ts b/apps/server/src/modules/authorization/api/dto/authorization.reponse.ts new file mode 100644 index 00000000000..952a01b5556 --- /dev/null +++ b/apps/server/src/modules/authorization/api/dto/authorization.reponse.ts @@ -0,0 +1,45 @@ +import { Permission } from '@shared/domain/interface'; +import { ApiProperty } from '@nestjs/swagger'; +import { Action, AuthorizableReferenceType, AuthorizationContext } from '../../domain'; + +export class AuthorizedReponse implements AuthorizationContext { + @ApiProperty({ + enum: Permission, + isArray: true, + description: 'Needed user permissions based on user role, that are needed to execute the operation.', + example: [Permission.ACCOUNT_VIEW, Permission.BASE_VIEW], + }) + requiredPermissions: Permission[]; + + @ApiProperty() + userId: string; + + @ApiProperty({ + enum: Action, + description: 'Define for which action the operation is performend.', + example: Action.read, + }) + action: Action; + + @ApiProperty({ + enum: AuthorizableReferenceType, + description: 'Define for which known entity, or domain object the operation is peformend.', + example: AuthorizableReferenceType.User, + }) + referenceType: AuthorizableReferenceType; + + @ApiProperty() + refrenceId: string; + + @ApiProperty() + isAuthorized: boolean; + + constructor(props: AuthorizedReponse) { + this.requiredPermissions = props.requiredPermissions; + this.userId = props.userId; + this.action = props.action; + this.referenceType = props.referenceType; + this.refrenceId = props.refrenceId; + this.isAuthorized = props.isAuthorized; + } +} diff --git a/apps/server/src/modules/authorization/api/dto/index.ts b/apps/server/src/modules/authorization/api/dto/index.ts new file mode 100644 index 00000000000..260344b8494 --- /dev/null +++ b/apps/server/src/modules/authorization/api/dto/index.ts @@ -0,0 +1,3 @@ +export * from './authorization-url.params'; +export * from './authorization-body.params'; +export * from './authorization.reponse'; diff --git a/apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts b/apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts new file mode 100644 index 00000000000..681abf7e2ea --- /dev/null +++ b/apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts @@ -0,0 +1,41 @@ +import { EntityId } from '@shared/domain/types'; +import { AuthorizableReferenceType, AuthorizationContext } from '../../domain'; +import { AuthorizedReponse } from '../dto/authorization.reponse'; + +export class AuthorizationReponseMapper { + public static mapToSuccessResponse( + userId: EntityId, + authorizableReferenceType: AuthorizableReferenceType, + authorizableReferenceId: EntityId, + context: AuthorizationContext + ): AuthorizedReponse { + const successAuthorizationReponse = AuthorizationReponseMapper.mapToResponse( + userId, + authorizableReferenceType, + authorizableReferenceId, + context, + true + ); + + return successAuthorizationReponse; + } + + private static mapToResponse( + userId: EntityId, + authorizableReferenceType: AuthorizableReferenceType, + authorizableReferenceId: EntityId, + context: AuthorizationContext, + isAuthorized: boolean + ): AuthorizedReponse { + const authorizationReponse = new AuthorizedReponse({ + userId, + action: context.action, + requiredPermissions: context.requiredPermissions, + referenceType: authorizableReferenceType, + refrenceId: authorizableReferenceId, + isAuthorized, + }); + + return authorizationReponse; + } +} diff --git a/apps/server/src/modules/authorization/api/mapper/index.ts b/apps/server/src/modules/authorization/api/mapper/index.ts new file mode 100644 index 00000000000..b88e894edb7 --- /dev/null +++ b/apps/server/src/modules/authorization/api/mapper/index.ts @@ -0,0 +1 @@ +export * from './authorization.response.mapper'; From e53cea30eeef84f0fbb47cda94707d7b7455e498 Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Fri, 24 May 2024 13:49:12 +0200 Subject: [PATCH 2/8] Create authorization-reference api module and add api tests. --- .../api/authorization-reference.controller.ts | 4 +- .../api/authorization-reference.uc.ts | 2 + .../api/test/authorization.api.spec.ts | 257 ++++++++++++++++++ .../authorization-reference.api.module.ts | 11 + .../src/modules/server/server.module.ts | 4 +- 5 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 apps/server/src/modules/authorization/api/test/authorization.api.spec.ts create mode 100644 apps/server/src/modules/authorization/authorization-reference.api.module.ts diff --git a/apps/server/src/modules/authorization/api/authorization-reference.controller.ts b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts index e2ecc24a03c..8df0f480150 100644 --- a/apps/server/src/modules/authorization/api/authorization-reference.controller.ts +++ b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts @@ -8,14 +8,14 @@ import { AuthorizationBodyParams, AuthorizationUrlParams, AuthorizedReponse } fr @Authenticate('jwt') @ApiTags('Authorization') @Controller('authorization') -export class AuthorizationController { +export class AuthorizationReferenceController { constructor(private readonly authorizationReferenceUc: AuthorizationReferenceUc) {} @ApiResponse({ status: 200, type: AuthorizedReponse }) @ApiResponse({ status: 400, type: ApiValidationError }) @ApiResponse({ status: 401, type: UnauthorizedException }) @ApiResponse({ status: 500, type: InternalServerErrorException }) - @Post('authorize/referenceType/:referenceType/referenceId/:referenceId') + @Post('authorize-by-reference/referenceType/:referenceType/referenceId/:referenceId') public async authorizeByReference( @Param() urlParams: AuthorizationUrlParams, @Body() body: AuthorizationBodyParams, diff --git a/apps/server/src/modules/authorization/api/authorization-reference.uc.ts b/apps/server/src/modules/authorization/api/authorization-reference.uc.ts index bf58395101b..ee9408924c8 100644 --- a/apps/server/src/modules/authorization/api/authorization-reference.uc.ts +++ b/apps/server/src/modules/authorization/api/authorization-reference.uc.ts @@ -1,8 +1,10 @@ import { EntityId } from '@shared/domain/types'; +import { Injectable } from '@nestjs/common'; import { AuthorizableReferenceType, AuthorizationContext, AuthorizationReferenceService } from '../domain'; import { AuthorizedReponse } from './dto'; import { AuthorizationReponseMapper } from './mapper'; +@Injectable() export class AuthorizationReferenceUc { constructor(private readonly authorizationReferenceService: AuthorizationReferenceService) {} diff --git a/apps/server/src/modules/authorization/api/test/authorization.api.spec.ts b/apps/server/src/modules/authorization/api/test/authorization.api.spec.ts new file mode 100644 index 00000000000..7b9e6d9477a --- /dev/null +++ b/apps/server/src/modules/authorization/api/test/authorization.api.spec.ts @@ -0,0 +1,257 @@ +import { EntityManager } from '@mikro-orm/core'; +import { ServerTestModule } from '@modules/server'; +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { TestApiClient, UserAndAccountTestFactory } from '@shared/testing'; +import { Permission } from '@shared/domain/interface'; +import { Action, AuthorizableReferenceType, AuthorizationContext, AuthorizationContextBuilder } from '../../domain'; +import { AuthorizationReponseMapper } from '../mapper'; + +describe('Authorization Controller (API)', () => { + let app: INestApplication; + let em: EntityManager; + let testApiClient: TestApiClient; + + beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ServerTestModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + em = app.get(EntityManager); + testApiClient = new TestApiClient(app, 'authorization'); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('authorize', () => { + describe('When user is not logged in', () => { + const setup = async () => { + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + + await em.persistAndFlush([teacherAccount, teacherUser]); + em.clear(); + + return { + referenceId: teacherUser.id, + referenceType: AuthorizableReferenceType.User, + }; + }; + + it('should response with unauthorized exception', async () => { + const { referenceId, referenceType } = await setup(); + + const response = await testApiClient.post( + `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}` + ); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + expect(response.body).toEqual({ + type: 'UNAUTHORIZED', + title: 'Unauthorized', + message: 'Unauthorized', + code: 401, + }); + }); + }); + + describe('When invalid data passed', () => { + const setup = async () => { + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + + await em.persistAndFlush([teacherAccount, teacherUser]); + em.clear(); + + const loggedInClient = await testApiClient.login(teacherAccount); + + return { + loggedInClient, + referenceId: teacherUser.id, + referenceType: AuthorizableReferenceType.User, + context: AuthorizationContextBuilder.read([]), + }; + }; + + it('should response with api validation error for invalid reference type', async () => { + const { loggedInClient, referenceId, context } = await setup(); + const invalidReferenceType = 'abc' as AuthorizableReferenceType; + + const response = await loggedInClient.post( + `/authorize-by-reference/referenceType/${invalidReferenceType}/referenceId/${referenceId}`, + context + ); + + expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body).toEqual({ + type: 'API_VALIDATION_ERROR', + title: 'API Validation Error', + message: 'API validation failed, see validationErrors for details', + code: 400, + validationErrors: [{ field: ['referenceType'], errors: [expect.any(String)] }], + }); + }); + + it('should response with api validation error for invalid reference id', async () => { + const { loggedInClient, referenceType, context } = await setup(); + const invalidReferenceId = 'abc'; + + const response = await loggedInClient.post( + `/authorize-by-reference/referenceType/${referenceType}/referenceId/${invalidReferenceId}`, + context + ); + + expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body).toEqual({ + type: 'API_VALIDATION_ERROR', + title: 'API Validation Error', + message: 'API validation failed, see validationErrors for details', + code: 400, + validationErrors: [{ field: ['referenceId'], errors: [expect.any(String)] }], + }); + }); + + it('should response with api validation error for invalid action in body', async () => { + const { loggedInClient, referenceType, referenceId } = await setup(); + const invalidActionContext = { requiredPermissions: [] } as unknown as AuthorizationContext; + + const response = await loggedInClient.post( + `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, + invalidActionContext + ); + + expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body).toEqual({ + type: 'API_VALIDATION_ERROR', + title: 'API Validation Error', + message: 'API validation failed, see validationErrors for details', + code: 400, + validationErrors: [{ field: ['action'], errors: [expect.any(String)] }], + }); + }); + + it('should response with api validation error for invalid requiredPermissions in body', async () => { + const { loggedInClient, referenceType, referenceId } = await setup(); + const invalidRequiredPermissionContext = { action: Action.read } as unknown as AuthorizationContext; + + const response = await loggedInClient.post( + `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, + invalidRequiredPermissionContext + ); + + expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body).toEqual({ + type: 'API_VALIDATION_ERROR', + title: 'API Validation Error', + message: 'API validation failed, see validationErrors for details', + code: 400, + validationErrors: [ + { field: ['requiredPermissions'], errors: [expect.any(String), 'requiredPermissions must be an array'] }, + ], + }); + }); + + it('should response with api validation error for wrong permission in requiredPermissions in body', async () => { + const { loggedInClient, referenceType, referenceId } = await setup(); + const invalidPermissionContext = AuthorizationContextBuilder.read([ + Permission.USER_UPDATE, + 'INVALID_PERMISSION' as Permission, + ]); + + const response = await loggedInClient.post( + `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, + invalidPermissionContext + ); + + expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); + expect(response.body).toEqual({ + type: 'API_VALIDATION_ERROR', + title: 'API Validation Error', + message: 'API validation failed, see validationErrors for details', + code: 400, + validationErrors: [{ field: ['requiredPermissions'], errors: [expect.any(String)] }], + }); + }); + }); + + describe('When operation is not authorized', () => { + const setup = async () => { + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const { teacherUser: otherUser } = UserAndAccountTestFactory.buildTeacher(); + + await em.persistAndFlush([teacherAccount, teacherUser, otherUser]); + em.clear(); + + const loggedInClient = await testApiClient.login(teacherAccount); + + return { + loggedInClient, + referenceId: otherUser.id, + referenceType: AuthorizableReferenceType.User, + context: AuthorizationContextBuilder.write([Permission.ADMIN_EDIT]), + }; + }; + + it('should response with forbidden exception', async () => { + // unauthorized expection? + const { loggedInClient, referenceType, referenceId, context } = await setup(); + + const response = await loggedInClient.post( + `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, + context + ); + + expect(response.statusCode).toEqual(HttpStatus.FORBIDDEN); + expect(response.body).toEqual({ + code: 403, + message: 'Forbidden', + title: 'Forbidden', + type: 'FORBIDDEN', + }); + }); + }); + + describe('When operation is authorized', () => { + const setup = async () => { + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + + await em.persistAndFlush([teacherAccount, teacherUser]); + em.clear(); + + const loggedInClient = await testApiClient.login(teacherAccount); + + const referenceId = teacherUser.id; + const referenceType = AuthorizableReferenceType.User; + const context = AuthorizationContextBuilder.write([]); + const expectedResult = AuthorizationReponseMapper.mapToSuccessResponse( + referenceId, + referenceType, + referenceId, + context + ); + + return { + loggedInClient, + referenceId, + referenceType, + context, + expectedResult, + }; + }; + + it('should response with success authorisation response', async () => { + const { loggedInClient, referenceType, referenceId, context, expectedResult } = await setup(); + + const response = await loggedInClient.post( + `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, + context + ); + + expect(response.statusCode).toEqual(HttpStatus.CREATED); + expect(response.body).toEqual(expectedResult); + }); + }); + }); +}); diff --git a/apps/server/src/modules/authorization/authorization-reference.api.module.ts b/apps/server/src/modules/authorization/authorization-reference.api.module.ts new file mode 100644 index 00000000000..305c59b1004 --- /dev/null +++ b/apps/server/src/modules/authorization/authorization-reference.api.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AuthorizationReferenceUc } from './api/authorization-reference.uc'; +import { AuthorizationReferenceModule } from './authorization-reference.module'; +import { AuthorizationReferenceController } from './api/authorization-reference.controller'; + +@Module({ + imports: [AuthorizationReferenceModule], + providers: [AuthorizationReferenceUc], + controllers: [AuthorizationReferenceController], +}) +export class AuthorizationReferenceApiModule {} diff --git a/apps/server/src/modules/server/server.module.ts b/apps/server/src/modules/server/server.module.ts index d7b533f5be9..b28498f9b1e 100644 --- a/apps/server/src/modules/server/server.module.ts +++ b/apps/server/src/modules/server/server.module.ts @@ -41,7 +41,8 @@ import { ALL_ENTITIES } from '@shared/domain/entity'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; -import { UserLicenseModule } from '../user-license'; +import { UserLicenseModule } from '@modules/user-license'; +import { AuthorizationReferenceApiModule } from '@modules/authorization/authorization-reference.api.module'; import { ServerConfigController, ServerController, ServerUc } from './api'; import { SERVER_CONFIG_TOKEN, serverConfig } from './server.config'; @@ -49,6 +50,7 @@ const serverModules = [ ConfigModule.forRoot(createConfigModuleOptions(serverConfig)), CoreModule, AuthenticationApiModule, + AuthorizationReferenceApiModule, AccountApiModule, CollaborativeStorageModule, OauthApiModule, From 0e9d10e3ab1c670ee2a3c99c1c357013666624e6 Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Fri, 24 May 2024 13:55:48 +0200 Subject: [PATCH 3/8] Fix import --- apps/server/src/modules/authorization/api/index.ts | 2 ++ .../authorization/authorization-reference.api.module.ts | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 apps/server/src/modules/authorization/api/index.ts diff --git a/apps/server/src/modules/authorization/api/index.ts b/apps/server/src/modules/authorization/api/index.ts new file mode 100644 index 00000000000..b2b21c87254 --- /dev/null +++ b/apps/server/src/modules/authorization/api/index.ts @@ -0,0 +1,2 @@ +export * from './authorization-reference.controller'; +export * from './authorization-reference.uc'; diff --git a/apps/server/src/modules/authorization/authorization-reference.api.module.ts b/apps/server/src/modules/authorization/authorization-reference.api.module.ts index 305c59b1004..8b3ede8c0f5 100644 --- a/apps/server/src/modules/authorization/authorization-reference.api.module.ts +++ b/apps/server/src/modules/authorization/authorization-reference.api.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; -import { AuthorizationReferenceUc } from './api/authorization-reference.uc'; +import { AuthorizationReferenceUc, AuthorizationReferenceController } from './api'; import { AuthorizationReferenceModule } from './authorization-reference.module'; -import { AuthorizationReferenceController } from './api/authorization-reference.controller'; @Module({ imports: [AuthorizationReferenceModule], From a99ab04d0015fca687e0ab751e71984b81b81a4b Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Mon, 27 May 2024 11:45:33 +0200 Subject: [PATCH 4/8] Cleanup implementation. --- .../api/authorization-reference.controller.ts | 26 +++++++++------ .../api/authorization-reference.uc.ts | 11 ++----- .../api/dto/authorization-body.params.ts | 17 ++++++++-- .../api/dto/authorization-url.params.ts | 17 ---------- .../api/dto/authorization.reponse.ts | 33 +------------------ .../modules/authorization/api/dto/index.ts | 1 - .../mapper/authorization.response.mapper.ts | 30 +---------------- 7 files changed, 36 insertions(+), 99 deletions(-) delete mode 100644 apps/server/src/modules/authorization/api/dto/authorization-url.params.ts diff --git a/apps/server/src/modules/authorization/api/authorization-reference.controller.ts b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts index 8df0f480150..c905faaa371 100644 --- a/apps/server/src/modules/authorization/api/authorization-reference.controller.ts +++ b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts @@ -1,9 +1,10 @@ import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; -import { Body, Controller, InternalServerErrorException, Param, Post, UnauthorizedException } from '@nestjs/common'; -import { ApiResponse, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, InternalServerErrorException, Post, UnauthorizedException } from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; import { AuthorizationReferenceUc } from './authorization-reference.uc'; -import { AuthorizationBodyParams, AuthorizationUrlParams, AuthorizedReponse } from './dto'; +import { AuthorizationBodyParams, AuthorizedReponse } from './dto'; +import { AuthorizationContext } from '../domain'; @Authenticate('jwt') @ApiTags('Authorization') @@ -11,23 +12,28 @@ import { AuthorizationBodyParams, AuthorizationUrlParams, AuthorizedReponse } fr export class AuthorizationReferenceController { constructor(private readonly authorizationReferenceUc: AuthorizationReferenceUc) {} + @ApiOperation({ summary: 'Checks if user is authorized to perform the given operation.' }) @ApiResponse({ status: 200, type: AuthorizedReponse }) @ApiResponse({ status: 400, type: ApiValidationError }) @ApiResponse({ status: 401, type: UnauthorizedException }) @ApiResponse({ status: 500, type: InternalServerErrorException }) - @Post('authorize-by-reference/referenceType/:referenceType/referenceId/:referenceId') + @Post('by-reference') public async authorizeByReference( - @Param() urlParams: AuthorizationUrlParams, @Body() body: AuthorizationBodyParams, @CurrentUser() user: ICurrentUser ): Promise { - const successAuthorizationReponse = await this.authorizationReferenceUc.authorizeByReference( + const context: AuthorizationContext = { + action: body.action, + requiredPermissions: body.requiredPermissions, + }; + + const authorizationReponse = await this.authorizationReferenceUc.authorizeByReference( user.userId, - urlParams.referenceType, - urlParams.referenceId, - body + body.referenceType, + body.referenceId, + context ); - return successAuthorizationReponse; + return authorizationReponse; } } diff --git a/apps/server/src/modules/authorization/api/authorization-reference.uc.ts b/apps/server/src/modules/authorization/api/authorization-reference.uc.ts index ee9408924c8..965efe64c43 100644 --- a/apps/server/src/modules/authorization/api/authorization-reference.uc.ts +++ b/apps/server/src/modules/authorization/api/authorization-reference.uc.ts @@ -14,20 +14,15 @@ export class AuthorizationReferenceUc { authorizableReferenceId: EntityId, context: AuthorizationContext ): Promise { - await this.authorizationReferenceService.checkPermissionByReferences( + const hasPermission = await this.authorizationReferenceService.hasPermissionByReferences( userId, authorizableReferenceType, authorizableReferenceId, context ); - const successAuthorizationReponse = AuthorizationReponseMapper.mapToSuccessResponse( - userId, - authorizableReferenceType, - authorizableReferenceId, - context - ); + const authorizationReponse = AuthorizationReponseMapper.mapToResponse(userId, hasPermission); - return successAuthorizationReponse; + return authorizationReponse; } } diff --git a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts index c03977e5244..3e8d9cb91a5 100644 --- a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts +++ b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts @@ -1,9 +1,10 @@ import { Permission } from '@shared/domain/interface'; import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEnum } from 'class-validator'; -import { Action, AuthorizationContext } from '../../domain'; +import { IsArray, IsEnum, IsMongoId } from 'class-validator'; +import { Action, AuthorizableReferenceType, AuthorizationContext } from '../../domain'; export class AuthorizationBodyParams implements AuthorizationContext { + // TODO: Action is not string enum, is 0 = read / 1 = write ...should be changed in enum @IsEnum(Action) @ApiProperty({ description: 'Define for which action the operation should be performend.' }) action!: Action; @@ -16,4 +17,16 @@ export class AuthorizationBodyParams implements AuthorizationContext { description: 'Needed user permissions based on user role, that are needed to execute the operation.', }) requiredPermissions!: Permission[]; + + @IsEnum(AuthorizableReferenceType) + @ApiProperty({ + enum: AuthorizableReferenceType, + description: 'Define for which known entity, or domain object the operation should be peformend.', + example: AuthorizableReferenceType.User, + }) + referenceType!: AuthorizableReferenceType; + + @IsMongoId() + @ApiProperty({ description: 'The id of the entity/domain object of the defined referenceType.' }) + referenceId!: string; } diff --git a/apps/server/src/modules/authorization/api/dto/authorization-url.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-url.params.ts deleted file mode 100644 index 98796d09bf8..00000000000 --- a/apps/server/src/modules/authorization/api/dto/authorization-url.params.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsEnum, IsMongoId } from 'class-validator'; -import { AuthorizableReferenceType } from '../../domain'; - -export class AuthorizationUrlParams { - @IsEnum(AuthorizableReferenceType) - @ApiProperty({ - enum: AuthorizableReferenceType, - description: 'Define for which known entity, or domain object the operation should be peformend.', - example: AuthorizableReferenceType.User, - }) - referenceType!: AuthorizableReferenceType; - - @IsMongoId() - @ApiProperty({ description: 'The id of the entity/domain object of the defined referenceType.' }) - referenceId!: string; -} diff --git a/apps/server/src/modules/authorization/api/dto/authorization.reponse.ts b/apps/server/src/modules/authorization/api/dto/authorization.reponse.ts index 952a01b5556..7980c5528a7 100644 --- a/apps/server/src/modules/authorization/api/dto/authorization.reponse.ts +++ b/apps/server/src/modules/authorization/api/dto/authorization.reponse.ts @@ -1,45 +1,14 @@ -import { Permission } from '@shared/domain/interface'; import { ApiProperty } from '@nestjs/swagger'; -import { Action, AuthorizableReferenceType, AuthorizationContext } from '../../domain'; - -export class AuthorizedReponse implements AuthorizationContext { - @ApiProperty({ - enum: Permission, - isArray: true, - description: 'Needed user permissions based on user role, that are needed to execute the operation.', - example: [Permission.ACCOUNT_VIEW, Permission.BASE_VIEW], - }) - requiredPermissions: Permission[]; +export class AuthorizedReponse { @ApiProperty() userId: string; - @ApiProperty({ - enum: Action, - description: 'Define for which action the operation is performend.', - example: Action.read, - }) - action: Action; - - @ApiProperty({ - enum: AuthorizableReferenceType, - description: 'Define for which known entity, or domain object the operation is peformend.', - example: AuthorizableReferenceType.User, - }) - referenceType: AuthorizableReferenceType; - - @ApiProperty() - refrenceId: string; - @ApiProperty() isAuthorized: boolean; constructor(props: AuthorizedReponse) { - this.requiredPermissions = props.requiredPermissions; this.userId = props.userId; - this.action = props.action; - this.referenceType = props.referenceType; - this.refrenceId = props.refrenceId; this.isAuthorized = props.isAuthorized; } } diff --git a/apps/server/src/modules/authorization/api/dto/index.ts b/apps/server/src/modules/authorization/api/dto/index.ts index 260344b8494..cc22a40c5bd 100644 --- a/apps/server/src/modules/authorization/api/dto/index.ts +++ b/apps/server/src/modules/authorization/api/dto/index.ts @@ -1,3 +1,2 @@ -export * from './authorization-url.params'; export * from './authorization-body.params'; export * from './authorization.reponse'; diff --git a/apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts b/apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts index 681abf7e2ea..7edc98e0b0e 100644 --- a/apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts +++ b/apps/server/src/modules/authorization/api/mapper/authorization.response.mapper.ts @@ -1,38 +1,10 @@ import { EntityId } from '@shared/domain/types'; -import { AuthorizableReferenceType, AuthorizationContext } from '../../domain'; import { AuthorizedReponse } from '../dto/authorization.reponse'; export class AuthorizationReponseMapper { - public static mapToSuccessResponse( - userId: EntityId, - authorizableReferenceType: AuthorizableReferenceType, - authorizableReferenceId: EntityId, - context: AuthorizationContext - ): AuthorizedReponse { - const successAuthorizationReponse = AuthorizationReponseMapper.mapToResponse( - userId, - authorizableReferenceType, - authorizableReferenceId, - context, - true - ); - - return successAuthorizationReponse; - } - - private static mapToResponse( - userId: EntityId, - authorizableReferenceType: AuthorizableReferenceType, - authorizableReferenceId: EntityId, - context: AuthorizationContext, - isAuthorized: boolean - ): AuthorizedReponse { + public static mapToResponse(userId: EntityId, isAuthorized: boolean): AuthorizedReponse { const authorizationReponse = new AuthorizedReponse({ userId, - action: context.action, - requiredPermissions: context.requiredPermissions, - referenceType: authorizableReferenceType, - refrenceId: authorizableReferenceId, isAuthorized, }); From 79da08bb4a64c2a0c9806f464de239d8ad602058 Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Mon, 27 May 2024 13:00:52 +0200 Subject: [PATCH 5/8] Change api test to new structure --- .../api/authorization-reference.controller.ts | 8 +- .../api/dto/authorization-body.params.ts | 12 +- .../api/test/authorization.api.spec.ts | 131 ++++++++---------- 3 files changed, 69 insertions(+), 82 deletions(-) diff --git a/apps/server/src/modules/authorization/api/authorization-reference.controller.ts b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts index c905faaa371..97906f1948b 100644 --- a/apps/server/src/modules/authorization/api/authorization-reference.controller.ts +++ b/apps/server/src/modules/authorization/api/authorization-reference.controller.ts @@ -4,7 +4,6 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; import { AuthorizationReferenceUc } from './authorization-reference.uc'; import { AuthorizationBodyParams, AuthorizedReponse } from './dto'; -import { AuthorizationContext } from '../domain'; @Authenticate('jwt') @ApiTags('Authorization') @@ -22,16 +21,11 @@ export class AuthorizationReferenceController { @Body() body: AuthorizationBodyParams, @CurrentUser() user: ICurrentUser ): Promise { - const context: AuthorizationContext = { - action: body.action, - requiredPermissions: body.requiredPermissions, - }; - const authorizationReponse = await this.authorizationReferenceUc.authorizeByReference( user.userId, body.referenceType, body.referenceId, - context + body.context ); return authorizationReponse; diff --git a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts index 3e8d9cb91a5..69e9d9c99c8 100644 --- a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts +++ b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts @@ -1,9 +1,9 @@ import { Permission } from '@shared/domain/interface'; import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEnum, IsMongoId } from 'class-validator'; +import { IsArray, IsEnum, IsMongoId, ValidateNested } from 'class-validator'; import { Action, AuthorizableReferenceType, AuthorizationContext } from '../../domain'; -export class AuthorizationBodyParams implements AuthorizationContext { +class AuthorizationContextParms implements AuthorizationContext { // TODO: Action is not string enum, is 0 = read / 1 = write ...should be changed in enum @IsEnum(Action) @ApiProperty({ description: 'Define for which action the operation should be performend.' }) @@ -17,6 +17,14 @@ export class AuthorizationBodyParams implements AuthorizationContext { description: 'Needed user permissions based on user role, that are needed to execute the operation.', }) requiredPermissions!: Permission[]; +} + +export class AuthorizationBodyParams { + @ValidateNested() + @ApiProperty({ + type: AuthorizationContextParms, + }) + context!: AuthorizationContextParms; @IsEnum(AuthorizableReferenceType) @ApiProperty({ diff --git a/apps/server/src/modules/authorization/api/test/authorization.api.spec.ts b/apps/server/src/modules/authorization/api/test/authorization.api.spec.ts index 7b9e6d9477a..068c3ce8480 100644 --- a/apps/server/src/modules/authorization/api/test/authorization.api.spec.ts +++ b/apps/server/src/modules/authorization/api/test/authorization.api.spec.ts @@ -6,6 +6,20 @@ import { TestApiClient, UserAndAccountTestFactory } from '@shared/testing'; import { Permission } from '@shared/domain/interface'; import { Action, AuthorizableReferenceType, AuthorizationContext, AuthorizationContextBuilder } from '../../domain'; import { AuthorizationReponseMapper } from '../mapper'; +import { AuthorizationBodyParams } from '../dto'; + +const createExamplePostData = (userId: string): AuthorizationBodyParams => { + const referenceType = AuthorizableReferenceType.User; + const context = AuthorizationContextBuilder.read([]); + + const postData: AuthorizationBodyParams = { + referenceId: userId, + referenceType, + context, + }; + + return postData; +}; describe('Authorization Controller (API)', () => { let app: INestApplication; @@ -27,7 +41,7 @@ describe('Authorization Controller (API)', () => { await app.close(); }); - describe('authorize', () => { + describe('authorizeByReference', () => { describe('When user is not logged in', () => { const setup = async () => { const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); @@ -35,18 +49,17 @@ describe('Authorization Controller (API)', () => { await em.persistAndFlush([teacherAccount, teacherUser]); em.clear(); + const postData = createExamplePostData(teacherUser.id); + return { - referenceId: teacherUser.id, - referenceType: AuthorizableReferenceType.User, + postData, }; }; it('should response with unauthorized exception', async () => { - const { referenceId, referenceType } = await setup(); + const { postData } = await setup(); - const response = await testApiClient.post( - `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}` - ); + const response = await testApiClient.post(`by-reference`, postData); expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); expect(response.body).toEqual({ @@ -66,23 +79,20 @@ describe('Authorization Controller (API)', () => { em.clear(); const loggedInClient = await testApiClient.login(teacherAccount); + const postData = createExamplePostData(teacherUser.id); return { loggedInClient, - referenceId: teacherUser.id, - referenceType: AuthorizableReferenceType.User, - context: AuthorizationContextBuilder.read([]), + postData, }; }; it('should response with api validation error for invalid reference type', async () => { - const { loggedInClient, referenceId, context } = await setup(); + const { loggedInClient, postData } = await setup(); const invalidReferenceType = 'abc' as AuthorizableReferenceType; + postData.referenceType = invalidReferenceType; - const response = await loggedInClient.post( - `/authorize-by-reference/referenceType/${invalidReferenceType}/referenceId/${referenceId}`, - context - ); + const response = await loggedInClient.post(`/by-reference/`, postData); expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); expect(response.body).toEqual({ @@ -95,13 +105,11 @@ describe('Authorization Controller (API)', () => { }); it('should response with api validation error for invalid reference id', async () => { - const { loggedInClient, referenceType, context } = await setup(); + const { loggedInClient, postData } = await setup(); const invalidReferenceId = 'abc'; + postData.referenceId = invalidReferenceId; - const response = await loggedInClient.post( - `/authorize-by-reference/referenceType/${referenceType}/referenceId/${invalidReferenceId}`, - context - ); + const response = await loggedInClient.post(`/by-reference`, postData); expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); expect(response.body).toEqual({ @@ -114,13 +122,11 @@ describe('Authorization Controller (API)', () => { }); it('should response with api validation error for invalid action in body', async () => { - const { loggedInClient, referenceType, referenceId } = await setup(); + const { loggedInClient, postData } = await setup(); const invalidActionContext = { requiredPermissions: [] } as unknown as AuthorizationContext; + postData.context = invalidActionContext; - const response = await loggedInClient.post( - `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, - invalidActionContext - ); + const response = await loggedInClient.post(`by-reference`, postData); expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); expect(response.body).toEqual({ @@ -128,18 +134,16 @@ describe('Authorization Controller (API)', () => { title: 'API Validation Error', message: 'API validation failed, see validationErrors for details', code: 400, - validationErrors: [{ field: ['action'], errors: [expect.any(String)] }], + validationErrors: [{ field: ['context', 'action'], errors: [expect.any(String)] }], }); }); it('should response with api validation error for invalid requiredPermissions in body', async () => { - const { loggedInClient, referenceType, referenceId } = await setup(); + const { loggedInClient, postData } = await setup(); const invalidRequiredPermissionContext = { action: Action.read } as unknown as AuthorizationContext; + postData.context = invalidRequiredPermissionContext; - const response = await loggedInClient.post( - `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, - invalidRequiredPermissionContext - ); + const response = await loggedInClient.post(`by-reference`, postData); expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); expect(response.body).toEqual({ @@ -148,22 +152,23 @@ describe('Authorization Controller (API)', () => { message: 'API validation failed, see validationErrors for details', code: 400, validationErrors: [ - { field: ['requiredPermissions'], errors: [expect.any(String), 'requiredPermissions must be an array'] }, + { + field: ['context', 'requiredPermissions'], + errors: [expect.any(String), 'requiredPermissions must be an array'], + }, ], }); }); it('should response with api validation error for wrong permission in requiredPermissions in body', async () => { - const { loggedInClient, referenceType, referenceId } = await setup(); + const { loggedInClient, postData } = await setup(); const invalidPermissionContext = AuthorizationContextBuilder.read([ Permission.USER_UPDATE, 'INVALID_PERMISSION' as Permission, ]); + postData.context = invalidPermissionContext; - const response = await loggedInClient.post( - `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, - invalidPermissionContext - ); + const response = await loggedInClient.post(`by-reference`, postData); expect(response.statusCode).toEqual(HttpStatus.BAD_REQUEST); expect(response.body).toEqual({ @@ -171,7 +176,7 @@ describe('Authorization Controller (API)', () => { title: 'API Validation Error', message: 'API validation failed, see validationErrors for details', code: 400, - validationErrors: [{ field: ['requiredPermissions'], errors: [expect.any(String)] }], + validationErrors: [{ field: ['context', 'requiredPermissions'], errors: [expect.any(String)] }], }); }); }); @@ -185,31 +190,24 @@ describe('Authorization Controller (API)', () => { em.clear(); const loggedInClient = await testApiClient.login(teacherAccount); + const postData = createExamplePostData(otherUser.id); + postData.context.requiredPermissions = [Permission.ADMIN_EDIT]; + const expectedResult = AuthorizationReponseMapper.mapToResponse(teacherUser.id, false); return { loggedInClient, - referenceId: otherUser.id, - referenceType: AuthorizableReferenceType.User, - context: AuthorizationContextBuilder.write([Permission.ADMIN_EDIT]), + expectedResult, + postData, }; }; - it('should response with forbidden exception', async () => { - // unauthorized expection? - const { loggedInClient, referenceType, referenceId, context } = await setup(); + it('should response with unsuccess authorisation response', async () => { + const { loggedInClient, expectedResult, postData } = await setup(); - const response = await loggedInClient.post( - `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, - context - ); + const response = await loggedInClient.post(`by-reference`, postData); - expect(response.statusCode).toEqual(HttpStatus.FORBIDDEN); - expect(response.body).toEqual({ - code: 403, - message: 'Forbidden', - title: 'Forbidden', - type: 'FORBIDDEN', - }); + expect(response.statusCode).toEqual(HttpStatus.CREATED); + expect(response.body).toEqual(expectedResult); }); }); @@ -221,33 +219,20 @@ describe('Authorization Controller (API)', () => { em.clear(); const loggedInClient = await testApiClient.login(teacherAccount); - - const referenceId = teacherUser.id; - const referenceType = AuthorizableReferenceType.User; - const context = AuthorizationContextBuilder.write([]); - const expectedResult = AuthorizationReponseMapper.mapToSuccessResponse( - referenceId, - referenceType, - referenceId, - context - ); + const postData = createExamplePostData(teacherUser.id); + const expectedResult = AuthorizationReponseMapper.mapToResponse(teacherUser.id, true); return { loggedInClient, - referenceId, - referenceType, - context, expectedResult, + postData, }; }; it('should response with success authorisation response', async () => { - const { loggedInClient, referenceType, referenceId, context, expectedResult } = await setup(); + const { loggedInClient, expectedResult, postData } = await setup(); - const response = await loggedInClient.post( - `/authorize-by-reference/referenceType/${referenceType}/referenceId/${referenceId}`, - context - ); + const response = await loggedInClient.post(`by-reference`, postData); expect(response.statusCode).toEqual(HttpStatus.CREATED); expect(response.body).toEqual(expectedResult); From fe0586d32e71f6de2ba08e3302873850f7dfd588 Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Mon, 27 May 2024 14:01:47 +0200 Subject: [PATCH 6/8] Change action to string enum and make it useable over api. --- .../authorization/api/dto/authorization-body.params.ts | 10 +++++++--- .../modules/authorization/domain/type/action.enum.ts | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts index 69e9d9c99c8..f448c919365 100644 --- a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts +++ b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts @@ -4,9 +4,12 @@ import { IsArray, IsEnum, IsMongoId, ValidateNested } from 'class-validator'; import { Action, AuthorizableReferenceType, AuthorizationContext } from '../../domain'; class AuthorizationContextParms implements AuthorizationContext { - // TODO: Action is not string enum, is 0 = read / 1 = write ...should be changed in enum @IsEnum(Action) - @ApiProperty({ description: 'Define for which action the operation should be performend.' }) + @ApiProperty({ + description: 'Define for which action the operation should be performend.', + enum: Action, + example: Action.read, + }) action!: Action; @IsArray() @@ -15,12 +18,13 @@ class AuthorizationContextParms implements AuthorizationContext { enum: Permission, isArray: true, description: 'Needed user permissions based on user role, that are needed to execute the operation.', + example: Permission.USER_UPDATE, }) requiredPermissions!: Permission[]; } export class AuthorizationBodyParams { - @ValidateNested() + @ValidateNested({ each: true }) @ApiProperty({ type: AuthorizationContextParms, }) diff --git a/apps/server/src/modules/authorization/domain/type/action.enum.ts b/apps/server/src/modules/authorization/domain/type/action.enum.ts index 2ca9a565c53..e12b4c88e12 100644 --- a/apps/server/src/modules/authorization/domain/type/action.enum.ts +++ b/apps/server/src/modules/authorization/domain/type/action.enum.ts @@ -1,4 +1,4 @@ export enum Action { - read, - write, + read = 'read', + write = 'write', } From 37e493d3367dba639326c6266ecdfa509250e695 Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Mon, 27 May 2024 16:41:37 +0200 Subject: [PATCH 7/8] fix typo --- .../authorization/api/dto/authorization-body.params.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts index f448c919365..25cf1eafa84 100644 --- a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts +++ b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts @@ -3,7 +3,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, IsEnum, IsMongoId, ValidateNested } from 'class-validator'; import { Action, AuthorizableReferenceType, AuthorizationContext } from '../../domain'; -class AuthorizationContextParms implements AuthorizationContext { +class AuthorizationContextParams implements AuthorizationContext { @IsEnum(Action) @ApiProperty({ description: 'Define for which action the operation should be performend.', @@ -26,9 +26,9 @@ class AuthorizationContextParms implements AuthorizationContext { export class AuthorizationBodyParams { @ValidateNested({ each: true }) @ApiProperty({ - type: AuthorizationContextParms, + type: AuthorizationContextParams, }) - context!: AuthorizationContextParms; + context!: AuthorizationContextParams; @IsEnum(AuthorizableReferenceType) @ApiProperty({ From 81497d97d319a34852d5b67c52c3a65ece393b5b Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Mon, 27 May 2024 17:10:12 +0200 Subject: [PATCH 8/8] fix typos --- .../authorization/api/dto/authorization-body.params.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts index 25cf1eafa84..437da409ce0 100644 --- a/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts +++ b/apps/server/src/modules/authorization/api/dto/authorization-body.params.ts @@ -17,7 +17,7 @@ class AuthorizationContextParams implements AuthorizationContext { @ApiProperty({ enum: Permission, isArray: true, - description: 'Needed user permissions based on user role, that are needed to execute the operation.', + description: 'User permissions that are needed to execute the operation.', example: Permission.USER_UPDATE, }) requiredPermissions!: Permission[]; @@ -33,7 +33,7 @@ export class AuthorizationBodyParams { @IsEnum(AuthorizableReferenceType) @ApiProperty({ enum: AuthorizableReferenceType, - description: 'Define for which known entity, or domain object the operation should be peformend.', + description: 'The entity or domain object the operation should be performed on.', example: AuthorizableReferenceType.User, }) referenceType!: AuthorizableReferenceType;