Skip to content

Commit 289e8bf

Browse files
fix: ensure unique GraphQL schema caching per API key (#16411)
## Description This PR fixes an issue where the GraphQL schema was being incorrectly cached and shared across different API keys within the same workspace. This resulted in the `createdBy` field (Actor) from the first API key's request being erroneously attributed to subsequent requests made by different API keys. ## Changes - Updated the `@graphql-yoga/nestjs` patch to include the request's `Authorization` header in the schema cache key generation logic. - This ensures that every unique authentication token (and thus every unique API key) generates a distinct cache entry, preventing schema context collisions. Closes #15093
1 parent 0ecb60f commit 289e8bf

File tree

2 files changed

+38
-32
lines changed

2 files changed

+38
-32
lines changed

packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ index 1684394..32602b3 100644
1111
const common_1 = require("@nestjs/common");
1212
const graphql_2 = require("@nestjs/graphql");
1313
class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver {
14-
+
14+
+
1515
+ schemaCache = new Map();
1616
+
1717
async start(options) {
@@ -26,22 +26,23 @@ index 1684394..32602b3 100644
2626
const app = this.httpAdapterHost.httpAdapter.getInstance();
2727
preStartHook?.(app);
2828
// nest's logger doesnt have the info method
29-
@@ -42,6 +46,46 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver {
29+
@@ -42,6 +46,47 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver {
3030
}
3131
const yoga = (0, graphql_yoga_1.createYoga)({
3232
...options,
3333
+ schema: async (request) => {
3434
+ const workspaceId = request.req.workspace?.id ?? 'anonymous'
3535
+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0'
3636
+ const workspaceUserId = request.req.user?.id ?? 'anonymous'
37+
+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key'
3738
+ const url = request.req.baseUrl
3839
+
39-
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${url}-${workspaceCacheVersion}`
40+
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}`
4041
+
4142
+ if(this.schemaCache.has(cacheKey)) {
4243
+ return this.schemaCache.get(cacheKey)
4344
+ }
44-
+
45+
+
4546
+ const schemas = [];
4647
+
4748
+ if (options.schema) {
@@ -67,13 +68,13 @@ index 1684394..32602b3 100644
6768
+ }
6869
+
6970
+ this.schemaCache.set(cacheKey, mergedSchemas)
70-
+
71+
+
7172
+ return mergedSchemas;
7273
+ },
7374
graphqlEndpoint: options.path,
7475
// disable logging by default
7576
// however, if `true` use nest logger
76-
@@ -54,11 +98,51 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver {
77+
@@ -54,11 +98,52 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver {
7778
this.yoga = yoga;
7879
app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res }));
7980
}
@@ -87,14 +88,15 @@ index 1684394..32602b3 100644
8788
+ const workspaceId = request.req.workspace?.id ?? 'anonymous'
8889
+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0'
8990
+ const workspaceUserId = request.req.user?.id ?? 'anonymous'
91+
+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key'
9092
+ const url = request.req.baseUrl
9193
+
92-
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${url}-${workspaceCacheVersion}`
94+
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}`
9395
+
9496
+ if(this.schemaCache.has(cacheKey)) {
9597
+ return this.schemaCache.get(cacheKey)
9698
+ }
97-
+
99+
+
98100
+ const schemas = [];
99101
+
100102
+ if (options.schema) {
@@ -120,7 +122,7 @@ index 1684394..32602b3 100644
120122
+ }
121123
+
122124
+ this.schemaCache.set(cacheKey, mergedSchemas)
123-
+
125+
+
124126
+ return mergedSchemas;
125127
+ },
126128
graphqlEndpoint: options.path,
@@ -141,9 +143,9 @@ index 7068c51..b8cbf9e 100644
141143
+import { createYoga, filter, pipe } from 'graphql-yoga';
142144
+import { __decorate } from "tslib";
143145
export class AbstractYogaDriver extends AbstractGraphQLDriver {
144-
+
146+
+
145147
+ schemaCache = new Map();
146-
+
148+
+
147149
async start(options) {
148150
const platformName = this.httpAdapterHost.httpAdapter.getType();
149151
options = {
@@ -156,17 +158,18 @@ index 7068c51..b8cbf9e 100644
156158
const app = this.httpAdapterHost.httpAdapter.getInstance();
157159
preStartHook?.(app);
158160
// nest's logger doesnt have the info method
159-
@@ -39,6 +43,46 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver {
161+
@@ -39,6 +43,47 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver {
160162
}
161163
const yoga = createYoga({
162164
...options,
163165
+ schema: async (request) => {
164166
+ const workspaceId = request.req.workspace?.id ?? 'anonymous'
165167
+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0'
166168
+ const workspaceUserId = request.req.user?.id ?? 'anonymous'
169+
+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key'
167170
+ const url = request.req.baseUrl
168171
+
169-
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${url}-${workspaceCacheVersion}`
172+
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}`
170173
+
171174
+ if (this.schemaCache.has(cacheKey)) {
172175
+ return this.schemaCache.get(cacheKey)
@@ -203,7 +206,7 @@ index 7068c51..b8cbf9e 100644
203206
graphqlEndpoint: options.path,
204207
// disable logging by default
205208
// however, if `true` use nest logger
206-
@@ -51,11 +95,51 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver {
209+
@@ -51,11 +95,52 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver {
207210
this.yoga = yoga;
208211
app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res }));
209212
}
@@ -217,9 +220,10 @@ index 7068c51..b8cbf9e 100644
217220
+ const workspaceId = request.req.workspace?.id ?? 'anonymous'
218221
+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0'
219222
+ const workspaceUserId = request.req.user?.id ?? 'anonymous'
223+
+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key'
220224
+ const url = request.req.baseUrl
221225
+
222-
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${url}-${workspaceCacheVersion}`
226+
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}`
223227
+
224228
+ if (this.schemaCache.has(cacheKey)) {
225229
+ return this.schemaCache.get(cacheKey)
@@ -353,15 +357,15 @@ index ce142f6..10e17d2 100644
353357
@@ -11,23 +12,31 @@ import {
354358
SubscriptionConfig,
355359
} from '@nestjs/graphql';
356-
360+
357361
+export type YogaSchemaDefinition<TContext> =
358362
+ | PromiseOrValue<GraphQLSchemaWithContext<TContext>>
359363
+ | ((
360364
+ context: TContext & YogaInitialContext,
361365
+ ) => PromiseOrValue<GraphQLSchemaWithContext<TContext>>);
362366
+
363367
export type YogaDriverPlatform = 'express' | 'fastify';
364-
368+
365369
export type YogaDriverServerContext<Platform extends YogaDriverPlatform> =
366370
Platform extends 'fastify'
367371
- ? {
@@ -380,15 +384,15 @@ index ce142f6..10e17d2 100644
380384
+ req: ExpressRequest;
381385
+ res: ExpressResponse;
382386
+ };
383-
387+
384388
export type YogaDriverServerOptions<Platform extends YogaDriverPlatform> = Omit<
385389
YogaServerOptions<YogaDriverServerContext<Platform>, never>,
386390
'context' | 'schema'
387391
->;
388392
+> & {
389393
+ conditionalSchema?: YogaSchemaDefinition<YogaDriverServerContext<Platform>> | undefined;
390394
+};
391-
395+
392396
export type YogaDriverServerInstance<Platform extends YogaDriverPlatform> = YogaServerInstance<
393397
YogaDriverServerContext<Platform>,
394398
@@ -53,6 +62,8 @@ export type YogaDriverSubscriptionConfig = {
@@ -398,28 +402,29 @@ index ce142f6..10e17d2 100644
398402
+ schemaCache = new Map();
399403
+
400404
protected yoga!: YogaDriverServerInstance<Platform>;
401-
405+
402406
public async start(options: YogaDriverConfig<Platform>) {
403407
@@ -78,7 +89,7 @@ export abstract class AbstractYogaDriver<
404408
}
405-
409+
406410
protected registerExpress(
407411
- options: YogaDriverConfig<'express'>,
408412
+ { conditionalSchema, ...options }: YogaDriverConfig<'express'>,
409413
{ preStartHook }: { preStartHook?: (app: Express) => void } = {},
410414
) {
411415
const app: Express = this.httpAdapterHost.httpAdapter.getInstance();
412-
@@ -98,6 +109,40 @@ export abstract class AbstractYogaDriver<
413-
416+
@@ -98,6 +109,41 @@ export abstract class AbstractYogaDriver<
417+
414418
const yoga = createYoga<YogaDriverServerContext<'express'>>({
415419
...options,
416420
+ schema: async request => {
417421
+ const workspaceId = request.req.workspace.id
418422
+ const workspaceCacheVersion = request.req.workspaceMetadataVersion
419423
+ const workspaceUserId = request.req.user?.id ?? 'anonymous'
424+
+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key'
420425
+ const url = request.req.baseUrl
421426
+
422-
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${url}-${workspaceCacheVersion}`
427+
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}`
423428
+
424429
+ if (this.schemaCache.has(cacheKey)) {
425430
+ return this.schemaCache.get(cacheKey)
@@ -459,28 +464,29 @@ index ce142f6..10e17d2 100644
459464
+ ? new LoggerWithInfo('YogaDriver')
460465
+ : options.logging,
461466
});
462-
467+
463468
this.yoga = yoga as YogaDriverServerInstance<Platform>;
464469
@@ -115,7 +160,7 @@ export abstract class AbstractYogaDriver<
465470
}
466-
471+
467472
protected registerFastify(
468473
- options: YogaDriverConfig<'fastify'>,
469474
+ { conditionalSchema, ...options }: YogaDriverConfig<'fastify'>,
470475
{ preStartHook }: { preStartHook?: (app: FastifyInstance) => void } = {},
471476
) {
472477
const app: FastifyInstance = this.httpAdapterHost.httpAdapter.getInstance();
473-
@@ -124,6 +169,40 @@ export abstract class AbstractYogaDriver<
474-
478+
@@ -124,6 +169,41 @@ export abstract class AbstractYogaDriver<
479+
475480
const yoga = createYoga<YogaDriverServerContext<'fastify'>>({
476481
...options,
477482
+ schema: async request => {
478483
+ const workspaceId = request.req.workspace.id
479484
+ const workspaceCacheVersion = request.req.workspaceMetadataVersion
480485
+ const workspaceUserId = request.req.user?.id ?? 'anonymous'
486+
+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key'
481487
+ const url = request.req.baseUrl
482488
+
483-
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${url}-${workspaceCacheVersion}`
489+
+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}`
484490
+
485491
+ if (this.schemaCache.has(cacheKey)) {
486492
+ return this.schemaCache.get(cacheKey)
@@ -520,5 +526,5 @@ index ce142f6..10e17d2 100644
520526
+ 'graphql-ws': true,
521527
+ }
522528
: options.subscriptions;
523-
529+
524530
if (config['graphql-ws']) {

yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7626,14 +7626,14 @@ __metadata:
76267626

76277627
"@graphql-yoga/nestjs@patch:@graphql-yoga/nestjs@2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch::locator=twenty-server%40workspace%3Apackages%2Ftwenty-server":
76287628
version: 2.1.0
7629-
resolution: "@graphql-yoga/nestjs@patch:@graphql-yoga/nestjs@npm%3A2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch::version=2.1.0&hash=8f5028&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server"
7629+
resolution: "@graphql-yoga/nestjs@patch:@graphql-yoga/nestjs@npm%3A2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch::version=2.1.0&hash=971f26&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server"
76307630
peerDependencies:
76317631
"@nestjs/common": ^10.0.0
76327632
"@nestjs/core": ^10.0.0
76337633
"@nestjs/graphql": ^12.0.0
76347634
graphql: ^15.0.0 || ^16.0.0
76357635
graphql-yoga: ^4.0.4
7636-
checksum: 10c0/503620747ab1e747ef61b21a794060f613dc86313f7452e86fcbef9ca722be262d83be0dc57e9add15172e0b6cfb6999fe5785419529e7c81663da5283b97169
7636+
checksum: 10c0/68ebaf195c93a6d31e22f91e5474cfb51675eac44a6c1a2ef7c856539e704dd9e7f8ea7cfd930a0ad37894edbcab6502b642f202d9f31f8f38b36c7d4d5add43
76377637
languageName: node
76387638
linkType: hard
76397639

0 commit comments

Comments
 (0)