fix: return proper FORBIDDEN GraphQL errors from ApiKeyResolver (#21107)

## Context
CI is broken on main, regression introduced in
https://github.com/twentyhq/twenty/pull/21072

Guard-rejected ApiKey mutations returned malformed GraphQL responses.
RequireAccessTokenGuard (and SettingsPermissionGuard) throw plain
AuthException/PermissionException classes, which are not GraphQLErrors.
ApiKeyResolver had no @UseFilters, so these exceptions were never
translated, they surfaced as request-level errors with no data key
(data: undefined) and a non-FORBIDDEN code, instead of data: null +
FORBIDDEN.

This broke the `createApiKey › should reject a non-ACCESS token even
with API key permission` integration test
(expect(res.body.data).toBeNull() received undefined). The sibling
generateApiKeyToken test passed only because it lives on AuthResolver,
which already declares these filters.

## Fix
Add the standard exception filters to ApiKeyResolver, matching the idiom
used by other guard-protected resolvers
```ts
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
```
This commit is contained in:
Weiko
2026-06-01 18:08:02 +02:00
committed by GitHub
parent 6ad6fcce0f
commit d86e827563
2 changed files with 5 additions and 2 deletions
@@ -1,4 +1,4 @@
import { UseGuards } from '@nestjs/common';
import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Mutation, Parent, Query, ResolveField } from '@nestjs/graphql';
import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
@@ -7,6 +7,7 @@ import { PermissionFlagType } from 'twenty-shared/constants';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { CreateApiKeyInput } from 'src/engine/core-modules/api-key/dtos/create-api-key.input';
import { GetApiKeyInput } from 'src/engine/core-modules/api-key/dtos/get-api-key.input';
import { RevokeApiKeyInput } from 'src/engine/core-modules/api-key/dtos/revoke-api-key.input';
@@ -17,12 +18,14 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
import { RequireAccessTokenGuard } from 'src/engine/guards/require-access-token.guard';
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.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 { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
import { ApiKeyRoleService } from './services/api-key-role.service';
import { ApiKeyService } from './services/api-key.service';
@MetadataResolver(() => ApiKeyEntity)
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
@UseGuards(
WorkspaceAuthGuard,
SettingsPermissionGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
@@ -65,7 +65,7 @@ describe('api key and webhooks permissions', () => {
const queryData = {
query: `
mutation createApiKey {
createApiKey(input: { name: "escalation", expiresAt: "2025-01-01T00:00:00Z" }) {
createApiKey(input: { name: "escalation", expiresAt: "2025-01-01T00:00:00Z", roleId: "20202020-0000-4000-8000-000000000000" }) {
id
}
}