From a24e4113847df9bc98bf58d4b5e257e63d38f877 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:48:04 +0100 Subject: [PATCH] [permissions] Add SettingsPermissionGuard on data model and roles features (#10063) Adding SettingsPermissionsGuard to execute permission check. The guard is added directly in resolver, either at resolver level (ex: roles) or resolver-endpoint level (ex: metadata). this can be challenged ! --- .../guards/settings-permissions.guard.ts | 63 +++++++++++++++++++ .../field-metadata/field-metadata.module.ts | 2 + .../field-metadata/field-metadata.resolver.ts | 9 ++- .../object-metadata/object-metadata.module.ts | 9 +++ .../object-metadata.resolver.ts | 9 ++- .../permissions/permissions.service.ts | 19 ++---- ...rmissions-graphql-api-exception.filter.ts} | 17 ++--- .../relation-metadata.module.ts | 13 +++- .../relation-metadata.resolver.ts | 8 ++- .../metadata-modules/role/role.module.ts | 2 + .../metadata-modules/role/role.resolver.ts | 57 ++++++----------- 11 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 packages/twenty-server/src/engine/guards/settings-permissions.guard.ts rename packages/twenty-server/src/engine/metadata-modules/permissions/utils/{permissions-graphql-api-exception-handler.ts => permissions-graphql-api-exception.filter.ts} (53%) diff --git a/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts new file mode 100644 index 000000000..fdc3dd8b5 --- /dev/null +++ b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts @@ -0,0 +1,63 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + mixin, + Type, +} from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; + +import { SettingsFeatures } from 'twenty-shared'; + +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { + PermissionsException, + PermissionsExceptionCode, +} from 'src/engine/metadata-modules/permissions/permissions.exception'; +import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; + +export const SettingsPermissionsGuard = ( + requiredPermission: SettingsFeatures, +): Type => { + @Injectable() + class SettingsPermissionsMixin implements CanActivate { + constructor( + private readonly featureFlagService: FeatureFlagService, + private readonly permissionsService: PermissionsService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const ctx = GqlExecutionContext.create(context); + const workspaceId = ctx.getContext().req.workspace.id; + + const permissionsEnabled = await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsPermissionsEnabled, + workspaceId, + ); + + if (!permissionsEnabled) { + return true; + } + + const userWorkspaceId = ctx.getContext().req.userWorkspaceId; + + const hasPermission = + await this.permissionsService.userHasWorkspaceSettingPermission({ + userWorkspaceId, + _setting: requiredPermission, + }); + + if (hasPermission === true) { + return true; + } + + throw new PermissionsException( + 'User is not authorized to perform this action', + PermissionsExceptionCode.PERMISSION_DENIED, + ); + } + } + + return mixin(SettingsPermissionsMixin); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts index c7d06a321..154797d9e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts @@ -22,6 +22,7 @@ import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-m import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; @@ -52,6 +53,7 @@ import { UpdateFieldInput } from './dtos/update-field.input'; TypeORMModule, ActorModule, ViewModule, + PermissionsModule, ], services: [ IsFieldMetadataDefaultValue, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts index 3f27757e0..43441176c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts @@ -1,6 +1,7 @@ import { BadRequestException, UnauthorizedException, + UseFilters, UseGuards, } from '@nestjs/common'; import { @@ -12,7 +13,7 @@ import { Resolver, } from '@nestjs/graphql'; -import { FieldMetadataType } from 'twenty-shared'; +import { FieldMetadataType, SettingsFeatures } from 'twenty-shared'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @@ -20,6 +21,7 @@ import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.typ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; @@ -34,10 +36,12 @@ import { } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; @UseGuards(WorkspaceAuthGuard) @Resolver(() => FieldMetadataDTO) +@UseFilters(PermissionsGraphqlApiExceptionFilter) export class FieldMetadataResolver { constructor( private readonly fieldMetadataService: FieldMetadataService, @@ -68,6 +72,7 @@ export class FieldMetadataResolver { ); } + @UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)) @Mutation(() => FieldMetadataDTO) async createOneField( @Args('input') input: CreateOneFieldMetadataInput, @@ -83,6 +88,7 @@ export class FieldMetadataResolver { } } + @UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)) @Mutation(() => FieldMetadataDTO) async updateOneField( @Args('input') input: UpdateOneFieldMetadataInput, @@ -98,6 +104,7 @@ export class FieldMetadataResolver { } } + @UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)) @Mutation(() => FieldMetadataDTO) async deleteOneField( @Args('input') input: DeleteOneFieldInput, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts index 1637b8f67..cebe7bd9e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts @@ -7,9 +7,12 @@ import { PagingStrategies, } from '@ptc-org/nestjs-query-graphql'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; +import { SettingsFeatures } from 'twenty-shared'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -20,6 +23,8 @@ import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metad import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service'; import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service'; import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module'; import { SearchModule } from 'src/engine/metadata-modules/search/search.module'; @@ -51,6 +56,8 @@ import { UpdateObjectPayload } from './dtos/update-object.input'; RemoteTableRelationsModule, SearchModule, IndexMetadataModule, + FeatureFlagModule, + PermissionsModule, ], services: [ ObjectMetadataService, @@ -71,11 +78,13 @@ import { UpdateObjectPayload } from './dtos/update-object.input'; }, create: { many: { disabled: true }, + guards: [SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)], }, update: { disabled: true }, delete: { disabled: true }, guards: [WorkspaceAuthGuard], interceptors: [ObjectMetadataGraphqlApiExceptionInterceptor], + filters: [PermissionsGraphqlApiExceptionFilter], }, ], }), diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts index ddcd4c51e..3d9a252b6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts @@ -1,4 +1,4 @@ -import { UseGuards } from '@nestjs/common'; +import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Context, @@ -8,9 +8,12 @@ import { Resolver, } from '@nestjs/graphql'; +import { SettingsFeatures } from 'twenty-shared'; + import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; @@ -21,9 +24,11 @@ import { import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; @UseGuards(WorkspaceAuthGuard) @Resolver(() => ObjectMetadataDTO) +@UseFilters(PermissionsGraphqlApiExceptionFilter) export class ObjectMetadataResolver { constructor( private readonly objectMetadataService: ObjectMetadataService, @@ -66,6 +71,7 @@ export class ObjectMetadataResolver { ); } + @UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)) @Mutation(() => ObjectMetadataDTO) async deleteOneObject( @Args('input') input: DeleteOneObjectInput, @@ -81,6 +87,7 @@ export class ObjectMetadataResolver { } } + @UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)) @Mutation(() => ObjectMetadataDTO) async updateOneObject( @Args('input') input: UpdateOneObjectInput, diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index 788fd5ab5..4d6f5c912 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -3,10 +3,6 @@ import { Injectable } from '@nestjs/common'; import { SettingsFeatures } from 'twenty-shared'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { - PermissionsException, - PermissionsExceptionCode, -} from 'src/engine/metadata-modules/permissions/permissions.exception'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; @Injectable() @@ -40,25 +36,22 @@ export class PermissionsService { ); } - public async validateUserHasWorkspaceSettingPermissionOrThrow({ + public async userHasWorkspaceSettingPermission({ userWorkspaceId, - setting, + _setting, }: { userWorkspaceId: string; - setting: SettingsFeatures; - }): Promise { + _setting: SettingsFeatures; + }): Promise { const [roleOfUserWorkspace] = await this.userRoleService .getRolesByUserWorkspaces([userWorkspaceId]) .then((roles) => roles?.get(userWorkspaceId) ?? []); if (roleOfUserWorkspace?.canUpdateAllSettings === true) { - return; + return true; } - throw new PermissionsException( - `User does not have permission to access this setting: ${setting}`, - PermissionsExceptionCode.PERMISSION_DENIED, - ); + return false; } public async isPermissionsEnabled(): Promise { diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception-handler.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts similarity index 53% rename from packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception-handler.ts rename to packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts index 4da377e16..dc2cfcca5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception-handler.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts @@ -1,3 +1,5 @@ +import { Catch, ExceptionFilter } from '@nestjs/common'; + import { ForbiddenError, InternalServerError, @@ -7,16 +9,15 @@ import { PermissionsExceptionCode, } from 'src/engine/metadata-modules/permissions/permissions.exception'; -export const permissionsGraphqlApiExceptionHandler = (error: Error) => { - if (error instanceof PermissionsException) { - switch (error.code) { +@Catch(PermissionsException) +export class PermissionsGraphqlApiExceptionFilter implements ExceptionFilter { + catch(exception: PermissionsException) { + switch (exception.code) { case PermissionsExceptionCode.PERMISSION_DENIED: case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN: - throw new ForbiddenError(error.message); + throw new ForbiddenError(exception.message); default: - throw new InternalServerError(error.message); + throw new InternalServerError(exception.message); } } - - throw error; -}; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts index b1c954e8a..4afcca605 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts @@ -5,12 +5,17 @@ import { PagingStrategies, } from '@ptc-org/nestjs-query-graphql'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; +import { SettingsFeatures } from 'twenty-shared'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { RelationMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/relation-metadata/interceptors/relation-metadata-graphql-api-exception.interceptor'; import { RelationMetadataResolver } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.resolver'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; @@ -39,6 +44,8 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto'; WorkspaceMigrationModule, WorkspaceCacheStorageModule, WorkspaceMetadataVersionModule, + FeatureFlagModule, + PermissionsModule, ], services: [RelationMetadataService], resolvers: [ @@ -48,11 +55,15 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto'; ServiceClass: RelationMetadataService, CreateDTOClass: CreateRelationInput, pagingStrategy: PagingStrategies.CURSOR, - create: { many: { disabled: true } }, + create: { + many: { disabled: true }, + guards: [SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)], + }, update: { disabled: true }, delete: { disabled: true }, guards: [WorkspaceAuthGuard], interceptors: [RelationMetadataGraphqlApiExceptionInterceptor], + filters: [PermissionsGraphqlApiExceptionFilter], }, ], }), diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts index b6f98d1ee..8d57dd55b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts @@ -1,9 +1,13 @@ -import { UseGuards } from '@nestjs/common'; +import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { SettingsFeatures } from 'twenty-shared'; + import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input'; import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto'; import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service'; @@ -11,11 +15,13 @@ import { relationMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata- @UseGuards(WorkspaceAuthGuard) @Resolver() +@UseFilters(PermissionsGraphqlApiExceptionFilter) export class RelationMetadataResolver { constructor( private readonly relationMetadataService: RelationMetadataService, ) {} + @UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)) @Mutation(() => RelationMetadataDTO) async deleteOneRelation( @Args('input') input: DeleteOneRelationInput, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts index 5aa3791e4..652ea7fb2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; @@ -17,6 +18,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role. UserRoleModule, PermissionsModule, UserWorkspaceModule, + FeatureFlagModule, ], providers: [RoleService, RoleResolver], exports: [RoleService], diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index b2cffa448..f50f39de3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -1,3 +1,4 @@ +import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Mutation, @@ -12,70 +13,48 @@ import { isDefined, SettingsFeatures } from 'twenty-shared'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; -import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; -import { permissionsGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception-handler'; +import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { RoleService } from 'src/engine/metadata-modules/role/role.service'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Resolver(() => RoleDTO) +@UseGuards(SettingsPermissionsGuard(SettingsFeatures.ROLES)) +@UseFilters(PermissionsGraphqlApiExceptionFilter) export class RoleResolver { constructor( private readonly userRoleService: UserRoleService, - private readonly permissionsService: PermissionsService, private readonly roleService: RoleService, private readonly userWorkspaceService: UserWorkspaceService, ) {} @Query(() => [RoleDTO]) - async getRoles( - @AuthUserWorkspaceId() userWorkspaceId: string, - @AuthWorkspace() workspace: Workspace, - ): Promise { - try { - await this.permissionsService.validateUserHasWorkspaceSettingPermissionOrThrow( - { - userWorkspaceId, - setting: SettingsFeatures.ROLES, - }, - ); + async getRoles(@AuthWorkspace() workspace: Workspace): Promise { + const roles = await this.roleService.getWorkspaceRoles(workspace.id); - const roles = await this.roleService.getWorkspaceRoles(workspace.id); - - return roles.map((role) => ({ - id: role.id, - label: role.label, - canUpdateAllSettings: role.canUpdateAllSettings, - description: role.description, - workspaceId: role.workspaceId, - createdAt: role.createdAt, - updatedAt: role.updatedAt, - isEditable: role.isEditable, - userWorkspaceRoles: role.userWorkspaceRoles, - })); - } catch (error) { - return permissionsGraphqlApiExceptionHandler(error); - } + return roles.map((role) => ({ + id: role.id, + label: role.label, + canUpdateAllSettings: role.canUpdateAllSettings, + description: role.description, + workspaceId: role.workspaceId, + createdAt: role.createdAt, + updatedAt: role.updatedAt, + isEditable: role.isEditable, + userWorkspaceRoles: role.userWorkspaceRoles, + })); } @Mutation(() => WorkspaceMember) async updateWorkspaceMemberRole( - @AuthUserWorkspaceId() currentUserWorkspaceId: string, @AuthWorkspace() workspace: Workspace, @Args('workspaceMemberId') workspaceMemberId: string, @Args('roleId', { type: () => String, nullable: true }) roleId: string | null, ): Promise { - await this.permissionsService.validateUserHasWorkspaceSettingPermissionOrThrow( - { - userWorkspaceId: currentUserWorkspaceId, - setting: SettingsFeatures.ROLES, - }, - ); - const workspaceMember = await this.userWorkspaceService.getWorkspaceMemberOrThrow({ workspaceMemberId,