diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index b3df8b1823..a48380094c 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -20,6 +20,7 @@ import { BillingWebhookModule } from 'src/engine/core-modules/billing-webhook/bi import { AppBillingModule } from 'src/engine/core-modules/billing/app-billing/app-billing.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { BillingGraphqlApiExceptionFilter } from 'src/engine/core-modules/billing/filters/billing-graphql-api-exception.filter'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { CacheStorageModule } from 'src/engine/core-modules/cache-storage/cache-storage.module'; import { TimelineCalendarEventModule } from 'src/engine/core-modules/calendar/timeline-calendar-event.module'; import { CaptchaModule } from 'src/engine/core-modules/captcha/captcha.module'; @@ -174,6 +175,10 @@ import { FileModule } from './file/file.module'; provide: APP_FILTER, useClass: BillingGraphqlApiExceptionFilter, }, + { + provide: APP_FILTER, + useClass: PermissionsGraphqlApiExceptionFilter, + }, ], exports: [ EventLogsViewerModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts index 2f4818241d..a80a88fa44 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts @@ -13,6 +13,7 @@ import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/conn import { CommandMenuItemModule } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.module'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; import { FlatEntityMapsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-graphql-api-exception.filter'; +import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { FrontComponentModule } from 'src/engine/metadata-modules/front-component/front-component.module'; import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module'; import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; @@ -69,6 +70,10 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work provide: APP_FILTER, useClass: FlatEntityMapsGraphqlApiExceptionFilter, }, + { + provide: APP_FILTER, + useClass: PermissionsGraphqlApiExceptionFilter, + }, ], exports: [ FieldMetadataModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts index 8a3365158c..d8dfbe91c3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter.ts @@ -1,11 +1,20 @@ -import { Catch, type ExceptionFilter } from '@nestjs/common'; +import { + type ArgumentsHost, + Catch, + type ExceptionFilter, +} from '@nestjs/common'; +import { type GqlContextType } from '@nestjs/graphql'; import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { permissionGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util'; @Catch(PermissionsException) export class PermissionsGraphqlApiExceptionFilter implements ExceptionFilter { - catch(exception: PermissionsException) { + catch(exception: PermissionsException, host: ArgumentsHost) { + if (host.getType() !== 'graphql') { + throw exception; + } + return permissionGraphqlApiExceptionHandler(exception); } } diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts index e64f6e766b..f4ab63405c 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts @@ -300,6 +300,34 @@ describe('Granular settings permissions', () => { expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); }); + it('should deny access to applications operations when user does not have APPLICATIONS setting permission', async () => { + const findOneApplicationQuery = { + query: ` + query FindOneApplication { + findOneApplication(id: "20202020-1c25-4d02-bf25-6aeccf7ea419") { + applicationVariables { + key + value + } + } + } + `, + }; + + const response = await client + .post('/metadata') + .set('Authorization', `Bearer ${APPLE_JONY_MEMBER_ACCESS_TOKEN}`) + .send(findOneApplicationQuery); + + expect(response.status).toBe(200); + expect(response.body.data).toBeNull(); + expect(response.body.errors).toBeDefined(); + expect(response.body.errors[0].message).toBe( + PermissionsExceptionMessage.PERMISSION_DENIED, + ); + expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); + }); + it('should deny access to API keys operations when user does not have API_KEYS_AND_WEBHOOKS setting permission', async () => { // Test creating an API key (requires API_KEYS_AND_WEBHOOKS permission) const createApiKeyQuery = {