From 850eab8f8f041c14325355dba070f119f6bfe1b7 Mon Sep 17 00:00:00 2001 From: Joe S Date: Wed, 7 Feb 2024 11:11:32 -0600 Subject: [PATCH] Add rate limiting in the server using built in Nest.js capability (#3566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add rate limiting in the server using built in Nest.js capability * Generatekey based on ip address when an http request is sent * Update env var types to number for ttl and limit * Remove unused env variables * Use getRequest utility function * fix: remove dist from path * fix: adding .env variables * fix: remove unused functions * feat: throttler plugin * Fix according to review --------- Co-authored-by: Jérémy Magrin Co-authored-by: Charles Bochet --- .../docs/start/self-hosting/self-hosting.mdx | 6 + packages/twenty-server/.env.example | 2 + packages/twenty-server/.env.test | 3 +- packages/twenty-server/package.json | 3 + packages/twenty-server/src/app.module.ts | 5 +- .../factories/create-context.factory.ts | 26 ++ .../src/graphql-config/factories/index.ts | 3 + .../graphql-config/graphql-config.module.ts | 11 + .../graphql-config.service.ts | 22 +- .../interfaces/graphql-context.interface.ts | 9 + .../environment/environment.service.ts | 8 + .../hooks/use-exception-handler.hook.ts | 31 +-- .../throttler/hooks/use-throttler.ts | 86 +++++++ .../src/metadata/metadata.module-factory.ts | 17 +- .../src/metadata/metadata.module.ts | 13 +- yarn.lock | 227 ++++++++++++++++-- 16 files changed, 413 insertions(+), 59 deletions(-) create mode 100644 packages/twenty-server/src/graphql-config/factories/create-context.factory.ts create mode 100644 packages/twenty-server/src/graphql-config/factories/index.ts create mode 100644 packages/twenty-server/src/graphql-config/graphql-config.module.ts rename packages/twenty-server/src/{ => graphql-config}/graphql-config.service.ts (82%) create mode 100644 packages/twenty-server/src/graphql-config/interfaces/graphql-context.interface.ts create mode 100644 packages/twenty-server/src/integrations/throttler/hooks/use-throttler.ts diff --git a/packages/twenty-docs/docs/start/self-hosting/self-hosting.mdx b/packages/twenty-docs/docs/start/self-hosting/self-hosting.mdx index c901345a8e..c96947b02f 100644 --- a/packages/twenty-docs/docs/start/self-hosting/self-hosting.mdx +++ b/packages/twenty-docs/docs/start/self-hosting/self-hosting.mdx @@ -43,6 +43,12 @@ import TabItem from '@theme/TabItem'; ['PORT', '3000', 'Port'], ]}> +### Security + + ### Tokens ({ driver: YogaDriver, - imports: [CoreModule], + imports: [CoreModule, GraphQLConfigModule], useClass: GraphQLConfigService, }), HealthModule, diff --git a/packages/twenty-server/src/graphql-config/factories/create-context.factory.ts b/packages/twenty-server/src/graphql-config/factories/create-context.factory.ts new file mode 100644 index 0000000000..1186898cc7 --- /dev/null +++ b/packages/twenty-server/src/graphql-config/factories/create-context.factory.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; + +import { YogaDriverServerContext } from '@graphql-yoga/nestjs'; + +import { GraphQLContext } from 'src/graphql-config/interfaces/graphql-context.interface'; + +import { TokenService } from 'src/core/auth/services/token.service'; + +@Injectable() +export class CreateContextFactory { + constructor(private readonly tokenService: TokenService) {} + + async create( + context: YogaDriverServerContext<'express'>, + ): Promise { + // Check if token is present in the request + if (this.tokenService.isTokenPresent(context.req)) { + const data = await this.tokenService.validateToken(context.req); + + // Inject user and workspace into the context + return { ...context, ...data }; + } + + return context; + } +} diff --git a/packages/twenty-server/src/graphql-config/factories/index.ts b/packages/twenty-server/src/graphql-config/factories/index.ts new file mode 100644 index 0000000000..c81ec577e8 --- /dev/null +++ b/packages/twenty-server/src/graphql-config/factories/index.ts @@ -0,0 +1,3 @@ +import { CreateContextFactory } from './create-context.factory'; + +export const graphQLFactories = [CreateContextFactory]; diff --git a/packages/twenty-server/src/graphql-config/graphql-config.module.ts b/packages/twenty-server/src/graphql-config/graphql-config.module.ts new file mode 100644 index 0000000000..64290089e9 --- /dev/null +++ b/packages/twenty-server/src/graphql-config/graphql-config.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { CoreModule } from 'src/core/core.module'; +import { graphQLFactories } from 'src/graphql-config/factories'; + +@Module({ + imports: [CoreModule], + providers: [...graphQLFactories], + exports: [...graphQLFactories], +}) +export class GraphQLConfigModule {} diff --git a/packages/twenty-server/src/graphql-config.service.ts b/packages/twenty-server/src/graphql-config/graphql-config.service.ts similarity index 82% rename from packages/twenty-server/src/graphql-config.service.ts rename to packages/twenty-server/src/graphql-config/graphql-config.service.ts index 46d62c1a82..9fddf661d3 100644 --- a/packages/twenty-server/src/graphql-config.service.ts +++ b/packages/twenty-server/src/graphql-config/graphql-config.service.ts @@ -19,15 +19,23 @@ import { ExceptionHandlerService } from 'src/integrations/exception-handler/exce import { handleExceptionAndConvertToGraphQLError } from 'src/filters/utils/global-exception-handler.util'; import { renderApolloPlayground } from 'src/workspace/utils/render-apollo-playground.util'; import { EnvironmentService } from 'src/integrations/environment/environment.service'; +import { useExceptionHandler } from 'src/integrations/exception-handler/hooks/use-exception-handler.hook'; +import { User } from 'src/core/user/user.entity'; +import { useThrottler } from 'src/integrations/throttler/hooks/use-throttler'; -import { useExceptionHandler } from './integrations/exception-handler/hooks/use-exception-handler.hook'; -import { User } from './core/user/user.entity'; +import { CreateContextFactory } from './factories/create-context.factory'; + +export interface GraphQLContext extends YogaDriverServerContext<'express'> { + user?: User; + workspace?: Workspace; +} @Injectable() export class GraphQLConfigService implements GqlOptionsFactory> { constructor( + private readonly createContextFactory: CreateContextFactory, private readonly tokenService: TokenService, private readonly exceptionHandlerService: ExceptionHandlerService, private readonly environmentService: EnvironmentService, @@ -37,7 +45,7 @@ export class GraphQLConfigService createGqlOptions(): YogaDriverConfig { const isDebugMode = this.environmentService.isDebugMode(); const config: YogaDriverConfig = { - context: ({ req }) => ({ req }), + context: (context) => this.createContextFactory.create(context), autoSchemaFile: true, include: [CoreModule], conditionalSchema: async (context) => { @@ -93,9 +101,15 @@ export class GraphQLConfigService }, resolvers: { JSON: GraphQLJSON }, plugins: [ + useThrottler({ + ttl: this.environmentService.getApiRateLimitingTtl(), + limit: this.environmentService.getApiRateLimitingLimit(), + identifyFn: (context) => { + return context.user?.id ?? context.req.ip ?? 'anonymous'; + }, + }), useExceptionHandler({ exceptionHandlerService: this.exceptionHandlerService, - tokenService: this.tokenService, }), ], }; diff --git a/packages/twenty-server/src/graphql-config/interfaces/graphql-context.interface.ts b/packages/twenty-server/src/graphql-config/interfaces/graphql-context.interface.ts new file mode 100644 index 0000000000..ab5a5d3c83 --- /dev/null +++ b/packages/twenty-server/src/graphql-config/interfaces/graphql-context.interface.ts @@ -0,0 +1,9 @@ +import { YogaDriverServerContext } from '@graphql-yoga/nestjs'; + +import { User } from 'src/core/user/user.entity'; +import { Workspace } from 'src/core/workspace/workspace.entity'; + +export interface GraphQLContext extends YogaDriverServerContext<'express'> { + user?: User; + workspace?: Workspace; +} diff --git a/packages/twenty-server/src/integrations/environment/environment.service.ts b/packages/twenty-server/src/integrations/environment/environment.service.ts index 3840df1da1..c55f409106 100644 --- a/packages/twenty-server/src/integrations/environment/environment.service.ts +++ b/packages/twenty-server/src/integrations/environment/environment.service.ts @@ -287,4 +287,12 @@ export class EnvironmentService { isSignUpDisabled(): boolean { return this.configService.get('IS_SIGN_UP_DISABLED') ?? false; } + + getApiRateLimitingTtl(): number { + return this.configService.get('API_RATE_LIMITING_TTL') ?? 100; + } + + getApiRateLimitingLimit(): number { + return this.configService.get('API_RATE_LIMITING_LIMIT') ?? 200; + } } diff --git a/packages/twenty-server/src/integrations/exception-handler/hooks/use-exception-handler.hook.ts b/packages/twenty-server/src/integrations/exception-handler/hooks/use-exception-handler.hook.ts index df39db28ee..f258d1666b 100644 --- a/packages/twenty-server/src/integrations/exception-handler/hooks/use-exception-handler.hook.ts +++ b/packages/twenty-server/src/integrations/exception-handler/hooks/use-exception-handler.hook.ts @@ -1,5 +1,4 @@ import { GraphQLError, Kind, OperationDefinitionNode, print } from 'graphql'; -import * as express from 'express'; import { getDocumentString, handleStreamOrSingleExecutionResult, @@ -7,10 +6,9 @@ import { Plugin, } from '@envelop/core'; -import { ExceptionHandlerUser } from 'src/integrations/exception-handler/interfaces/exception-handler-user.interface'; +import { GraphQLContext } from 'src/graphql-config/interfaces/graphql-context.interface'; import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service'; -import { TokenService } from 'src/core/auth/services/token.service'; import { convertExceptionToGraphQLError, filterException, @@ -18,13 +16,9 @@ import { export type ExceptionHandlerPluginOptions = { /** - * The driver to use to handle exceptions. + * The exception handler service to use. */ exceptionHandlerService: ExceptionHandlerService; - /** - * The token service to use to get the token from the request. - */ - tokenService: TokenService; /** * The key of the event id in the error's extension. `null` to disable. * @default exceptionEventId @@ -32,13 +26,9 @@ export type ExceptionHandlerPluginOptions = { eventIdKey?: string | null; }; -export const useExceptionHandler = < - PluginContext extends Record = object, ->( +export const useExceptionHandler = ( options: ExceptionHandlerPluginOptions, ): Plugin => { - const exceptionHandlerService = options.exceptionHandlerService; - const tokenService = options.tokenService; const eventIdKey = options.eventIdKey === null ? null : 'exceptionEventId'; function addEventId( @@ -54,28 +44,17 @@ export const useExceptionHandler = < return { async onExecute({ args }) { + const exceptionHandlerService = options.exceptionHandlerService; const rootOperation = args.document.definitions.find( (o) => o.kind === Kind.OPERATION_DEFINITION, ) as OperationDefinitionNode; const operationType = rootOperation.operation; + const user = args.contextValue.user; const document = getDocumentString(args.document, print); - const request = args.contextValue.req as express.Request; const opName = args.operationName || rootOperation.name?.value || 'Anonymous Operation'; - let user: ExceptionHandlerUser | undefined; - - if (tokenService.isTokenPresent(request)) { - try { - const data = await tokenService.validateToken(request); - - user = { - id: data.user?.id, - email: data.user?.email, - }; - } catch {} - } return { onExecuteDone(payload) { diff --git a/packages/twenty-server/src/integrations/throttler/hooks/use-throttler.ts b/packages/twenty-server/src/integrations/throttler/hooks/use-throttler.ts new file mode 100644 index 0000000000..6c549d532a --- /dev/null +++ b/packages/twenty-server/src/integrations/throttler/hooks/use-throttler.ts @@ -0,0 +1,86 @@ +import { GraphQLResolveInfo } from 'graphql'; +import { getGraphQLRateLimiter } from 'graphql-rate-limit'; +import { Plugin } from '@envelop/core'; +import { useOnResolve } from '@envelop/on-resolve'; + +import { GraphQLContext } from 'src/graphql-config/graphql-config.service'; + +export class UnauthenticatedError extends Error {} + +export type IdentifyFn = ( + context: ContextType, +) => string; + +export type ThrottlerPluginOptions = { + identifyFn: IdentifyFn; + ttl?: number; + limit?: number; + transformError?: (message: string) => Error; + onThrottlerError?: (event: { + error: string; + identifier: string; + context: unknown; + info: GraphQLResolveInfo; + }) => void; +}; + +interface ThrottlerContext extends GraphQLContext { + rateLimiterFn: ReturnType; +} + +export const useThrottler = ( + options: ThrottlerPluginOptions, +): Plugin => { + const rateLimiterFn = getGraphQLRateLimiter({ + identifyContext: options.identifyFn, + }); + + return { + onPluginInit({ addPlugin }) { + addPlugin( + useOnResolve(async ({ args, root, context, info }) => { + if (options.limit && options.ttl) { + const id = options.identifyFn(context); + + const errorMessage = await context.rateLimiterFn( + { parent: root, args, context, info }, + { + max: options?.limit, + window: `${options?.ttl}s`, + message: interpolate('Too much request.', { + id, + }), + }, + ); + + if (errorMessage) { + if (options.onThrottlerError) { + options.onThrottlerError({ + error: errorMessage, + identifier: id, + context, + info, + }); + } + + if (options.transformError) { + throw options.transformError(errorMessage); + } + + throw new Error(errorMessage); + } + } + }), + ); + }, + async onContextBuilding({ extendContext }) { + extendContext({ + rateLimiterFn, + }); + }, + }; +}; + +function interpolate(message: string, args: { [key: string]: string }) { + return message.replace(/\{{([^)]*)\}}/g, (_, key) => args[key.trim()]); +} diff --git a/packages/twenty-server/src/metadata/metadata.module-factory.ts b/packages/twenty-server/src/metadata/metadata.module-factory.ts index 8e76867d11..7dd0dafede 100644 --- a/packages/twenty-server/src/metadata/metadata.module-factory.ts +++ b/packages/twenty-server/src/metadata/metadata.module-factory.ts @@ -1,20 +1,23 @@ import { YogaDriverConfig } from '@graphql-yoga/nestjs'; import GraphQLJSON from 'graphql-type-json'; -import { TokenService } from 'src/core/auth/services/token.service'; +import { CreateContextFactory } from 'src/graphql-config/factories/create-context.factory'; import { EnvironmentService } from 'src/integrations/environment/environment.service'; import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service'; import { useExceptionHandler } from 'src/integrations/exception-handler/hooks/use-exception-handler.hook'; +import { useThrottler } from 'src/integrations/throttler/hooks/use-throttler'; import { MetadataModule } from 'src/metadata/metadata.module'; import { renderApolloPlayground } from 'src/workspace/utils/render-apollo-playground.util'; export const metadataModuleFactory = async ( environmentService: EnvironmentService, exceptionHandlerService: ExceptionHandlerService, - tokenService: TokenService, + createContextFactory: CreateContextFactory, ): Promise => { const config: YogaDriverConfig = { - context: ({ req }) => ({ req }), + context(context) { + return createContextFactory.create(context); + }, autoSchemaFile: true, include: [MetadataModule], renderGraphiQL() { @@ -22,9 +25,15 @@ export const metadataModuleFactory = async ( }, resolvers: { JSON: GraphQLJSON }, plugins: [ + useThrottler({ + ttl: environmentService.getApiRateLimitingTtl(), + limit: environmentService.getApiRateLimitingLimit(), + identifyFn: (context) => { + return context.user?.id ?? context.req.ip ?? 'anonymous'; + }, + }), useExceptionHandler({ exceptionHandlerService, - tokenService, }), ], path: '/metadata', diff --git a/packages/twenty-server/src/metadata/metadata.module.ts b/packages/twenty-server/src/metadata/metadata.module.ts index ff7fac4c37..19bade105b 100644 --- a/packages/twenty-server/src/metadata/metadata.module.ts +++ b/packages/twenty-server/src/metadata/metadata.module.ts @@ -8,20 +8,25 @@ import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/works import { metadataModuleFactory } from 'src/metadata/metadata.module-factory'; import { EnvironmentService } from 'src/integrations/environment/environment.service'; import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service'; -import { TokenService } from 'src/core/auth/services/token.service'; -import { AuthModule } from 'src/core/auth/auth.module'; +import { GraphQLConfigModule } from 'src/graphql-config/graphql-config.module'; +import { CreateContextFactory } from 'src/graphql-config/factories/create-context.factory'; import { DataSourceModule } from './data-source/data-source.module'; import { FieldMetadataModule } from './field-metadata/field-metadata.module'; import { ObjectMetadataModule } from './object-metadata/object-metadata.module'; import { RelationMetadataModule } from './relation-metadata/relation-metadata.module'; + @Module({ imports: [ GraphQLModule.forRootAsync({ driver: YogaDriver, useFactory: metadataModuleFactory, - imports: [AuthModule], - inject: [EnvironmentService, ExceptionHandlerService, TokenService], + imports: [GraphQLConfigModule], + inject: [ + EnvironmentService, + ExceptionHandlerService, + CreateContextFactory, + ], }), DataSourceModule, FieldMetadataModule, diff --git a/yarn.lock b/yarn.lock index 12d260d735..c530d727ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -639,6 +639,15 @@ __metadata: languageName: node linkType: hard +"@ardatan/aggregate-error@npm:0.0.6": + version: 0.0.6 + resolution: "@ardatan/aggregate-error@npm:0.0.6" + dependencies: + tslib: "npm:~2.0.1" + checksum: e374247b506baf753b21fdb32bd8eda12c3b3bf2bd7cc8954e2761ae3eb10e5033ab9cde6a0f279fbdb09e263358b29d40c05e79eb50a1eab08fbf8916a0253c + languageName: node + linkType: hard + "@ardatan/relay-compiler@npm:12.0.0": version: 12.0.0 resolution: "@ardatan/relay-compiler@npm:12.0.0" @@ -3243,6 +3252,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.15.4": + version: 7.23.9 + resolution: "@babel/runtime@npm:7.23.9" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: e71205fdd7082b2656512cc98e647d9ea7e222e4fe5c36e9e5adc026446fcc3ba7b3cdff8b0b694a0b78bb85db83e7b1e3d4c56ef90726682b74f13249cf952d + languageName: node + linkType: hard + "@babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2": version: 7.23.6 resolution: "@babel/runtime@npm:7.23.6" @@ -4611,6 +4629,16 @@ __metadata: languageName: node linkType: hard +"@envelop/on-resolve@npm:^4.1.0": + version: 4.1.0 + resolution: "@envelop/on-resolve@npm:4.1.0" + peerDependencies: + "@envelop/core": ^5.0.0 + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 4eb454df4b446303711edd0cd31e707fec06337c2e8327c7987d279fef8785e47e47ffe72c7444b4650a83efbf3234c6b85f550a8044ca181dcd10c05dcb3650 + languageName: node + linkType: hard + "@envelop/types@npm:4.0.1": version: 4.0.1 resolution: "@envelop/types@npm:4.0.1" @@ -5613,6 +5641,20 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/batch-execute@npm:8.5.1": + version: 8.5.1 + resolution: "@graphql-tools/batch-execute@npm:8.5.1" + dependencies: + "@graphql-tools/utils": "npm:8.9.0" + dataloader: "npm:2.1.0" + tslib: "npm:^2.4.0" + value-or-promise: "npm:1.0.11" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: f41d05247c6d6218e874dfbb7ffc45bcdd957e00954dd6c8a6848fde76de727250b38a71904f973688c70cd1199a4538a4272f66deee6066acdb7442798d59c1 + languageName: node + linkType: hard + "@graphql-tools/batch-execute@npm:^8.5.22": version: 8.5.22 resolution: "@graphql-tools/batch-execute@npm:8.5.22" @@ -5642,6 +5684,22 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/delegate@npm:^8.8.1": + version: 8.8.1 + resolution: "@graphql-tools/delegate@npm:8.8.1" + dependencies: + "@graphql-tools/batch-execute": "npm:8.5.1" + "@graphql-tools/schema": "npm:8.5.1" + "@graphql-tools/utils": "npm:8.9.0" + dataloader: "npm:2.1.0" + tslib: "npm:~2.4.0" + value-or-promise: "npm:1.0.11" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 2cd43a18f4f0ca4bccc52ad405b894933996b1269b676632651ca54e3e9f602615f453a235556dea1c9806ac8223079a1eae8f9d5dc624de7f2a8f822c3ceab4 + languageName: node + linkType: hard + "@graphql-tools/delegate@npm:^9.0.31": version: 9.0.35 resolution: "@graphql-tools/delegate@npm:9.0.35" @@ -6008,6 +6066,20 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/schema@npm:8.5.1, @graphql-tools/schema@npm:^8.0.0, @graphql-tools/schema@npm:^8.5.1": + version: 8.5.1 + resolution: "@graphql-tools/schema@npm:8.5.1" + dependencies: + "@graphql-tools/merge": "npm:8.3.1" + "@graphql-tools/utils": "npm:8.9.0" + tslib: "npm:^2.4.0" + value-or-promise: "npm:1.0.11" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 06000908fc5d3143f7f70eaee82874b87df4dfdd24316e88231e71e6f62f50df2e5a4b6a063b36e98f05caac09afa17861bbc5bf1c886b3f2155b96ea15c973b + languageName: node + linkType: hard + "@graphql-tools/schema@npm:^10.0.0": version: 10.0.2 resolution: "@graphql-tools/schema@npm:10.0.2" @@ -6022,20 +6094,6 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/schema@npm:^8.0.0": - version: 8.5.1 - resolution: "@graphql-tools/schema@npm:8.5.1" - dependencies: - "@graphql-tools/merge": "npm:8.3.1" - "@graphql-tools/utils": "npm:8.9.0" - tslib: "npm:^2.4.0" - value-or-promise: "npm:1.0.11" - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 06000908fc5d3143f7f70eaee82874b87df4dfdd24316e88231e71e6f62f50df2e5a4b6a063b36e98f05caac09afa17861bbc5bf1c886b3f2155b96ea15c973b - languageName: node - linkType: hard - "@graphql-tools/schema@npm:^9.0.0, @graphql-tools/schema@npm:^9.0.18, @graphql-tools/schema@npm:^9.0.19": version: 9.0.19 resolution: "@graphql-tools/schema@npm:9.0.19" @@ -6110,6 +6168,19 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/utils@npm:^7.6.0": + version: 7.10.0 + resolution: "@graphql-tools/utils@npm:7.10.0" + dependencies: + "@ardatan/aggregate-error": "npm:0.0.6" + camel-case: "npm:4.1.2" + tslib: "npm:~2.2.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 + checksum: e8b29bf3ff63c9ca123daa3785422189177ec0273331bb739a422d3055b5b3d0e956d357988e46b4b06e74d727c1ff228fe467d4e956a72ca8b6e292d0ce0f02 + languageName: node + linkType: hard + "@graphql-tools/utils@npm:^8.8.0": version: 8.13.1 resolution: "@graphql-tools/utils@npm:8.13.1" @@ -15259,7 +15330,7 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:*, @types/lodash@npm:^4.14.167": +"@types/lodash@npm:*, @types/lodash@npm:^4.14.167, @types/lodash@npm:^4.14.175": version: 4.14.202 resolution: "@types/lodash@npm:4.14.202" checksum: 6064d43c8f454170841bd67c8266cc9069d9e570a72ca63f06bceb484cb4a3ee60c9c1f305c1b9e3a87826049fd41124b8ef265c4dd08b00f6766609c7fe9973 @@ -15924,6 +15995,13 @@ __metadata: languageName: node linkType: hard +"@types/yup@npm:0.29.13": + version: 0.29.13 + resolution: "@types/yup@npm:0.29.13" + checksum: d42c787bf3a837c2eeb53948a6c7f48046ddadddfdaab28db99b3b7ace3b988094a12e31c4f4cd5715eef94fb1e447b5ea19e321f1745b6279cf38735b455895 + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^6.10.0": version: 6.17.0 resolution: "@typescript-eslint/eslint-plugin@npm:6.17.0" @@ -19836,7 +19914,7 @@ __metadata: languageName: node linkType: hard -"camel-case@npm:^4.1.2": +"camel-case@npm:4.1.2, camel-case@npm:^4.1.2": version: 4.1.2 resolution: "camel-case@npm:4.1.2" dependencies: @@ -22172,6 +22250,13 @@ __metadata: languageName: node linkType: hard +"dataloader@npm:2.1.0": + version: 2.1.0 + resolution: "dataloader@npm:2.1.0" + checksum: 91749b97c6cf218874aecc57116defbe28eb5dd102a2a6e292e084939f725d123dd49c186796069492a77eb105ff2aabae9c8b144cf82f92c1f673eb1abff7da + languageName: node + linkType: hard + "dataloader@npm:^2.2.2": version: 2.2.2 resolution: "dataloader@npm:2.2.2" @@ -26308,6 +26393,32 @@ __metadata: languageName: node linkType: hard +"graphql-middleware@npm:^6.1.35": + version: 6.1.35 + resolution: "graphql-middleware@npm:6.1.35" + dependencies: + "@graphql-tools/delegate": "npm:^8.8.1" + "@graphql-tools/schema": "npm:^8.5.1" + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: faa948e9490493e39a096b9a44941aa0ad66a760ca9413c02b7d177c38251cf6db8560aa182b6efa01e6d519f5e76c4802243e30b87f510fe6e31a89740885d0 + languageName: node + linkType: hard + +"graphql-rate-limit@npm:^3.3.0": + version: 3.3.0 + resolution: "graphql-rate-limit@npm:3.3.0" + dependencies: + "@graphql-tools/utils": "npm:^7.6.0" + graphql-shield: "npm:^7.5.0" + lodash.get: "npm:^4.4.2" + ms: "npm:^2.1.3" + peerDependencies: + graphql: "*" + checksum: 1c4eb8a83204ecec955ac5ad8994ddac723474920b788c77671da813419b383ee0e49123d8634820deada28687fb6f96aa47438b765c3340617048764a6b85f3 + languageName: node + linkType: hard + "graphql-request@npm:^6.0.0": version: 6.1.0 resolution: "graphql-request@npm:6.1.0" @@ -26320,6 +26431,21 @@ __metadata: languageName: node linkType: hard +"graphql-shield@npm:^7.5.0": + version: 7.6.5 + resolution: "graphql-shield@npm:7.6.5" + dependencies: + "@types/yup": "npm:0.29.13" + object-hash: "npm:^3.0.0" + tslib: "npm:^2.4.0" + yup: "npm:^0.32.0" + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + graphql-middleware: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^6.0.0 + checksum: aed5783f2b632a31d93c5b3dc2cd8d62d99a471db397aad8d8db58487b4fbce3ae431129369de711500795ac9b5f5aaa2bdef48c4650ac81a9ff8bd8840a3605 + languageName: node + linkType: hard + "graphql-subscriptions@npm:2.0.0": version: 2.0.0 resolution: "graphql-subscriptions@npm:2.0.0" @@ -30875,6 +31001,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 + languageName: node + linkType: hard + "lodash._reinterpolate@npm:^3.0.0": version: 3.0.0 resolution: "lodash._reinterpolate@npm:3.0.0" @@ -33961,7 +34094,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 @@ -34158,6 +34291,13 @@ __metadata: languageName: node linkType: hard +"nanoclone@npm:^0.2.1": + version: 0.2.1 + resolution: "nanoclone@npm:0.2.1" + checksum: 760b569ea841c9678fdf8d763c6d7bb093f0889150087f82d86c536a318b302939c82ce35cdaec999d0f687789d0d79d0f3f75a272d7a98dfac7a067c0b47053 + languageName: node + linkType: hard + "nanoid@npm:^3.1.32, nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" @@ -35172,6 +35312,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47 + languageName: node + linkType: hard + "object-inspect@npm:^1.13.1, object-inspect@npm:^1.9.0": version: 1.13.1 resolution: "object-inspect@npm:1.13.1" @@ -37472,6 +37619,13 @@ __metadata: languageName: node linkType: hard +"property-expr@npm:^2.0.4": + version: 2.0.6 + resolution: "property-expr@npm:2.0.6" + checksum: 69b7da15038a1146d6447c69c445306f66a33c425271235bb20507f1846dbf9577a8f9dfafe8acbfcb66f924b270157f155248308f026a68758f35fc72265b3c + languageName: node + linkType: hard + "property-information@npm:^6.0.0": version: 6.4.0 resolution: "property-information@npm:6.4.0" @@ -42495,6 +42649,13 @@ __metadata: languageName: node linkType: hard +"toposort@npm:^2.0.2": + version: 2.0.2 + resolution: "toposort@npm:2.0.2" + checksum: ab9ca91fce4b972ccae9e2f539d755bf799a0c7eb60da07fd985fce0f14c159ed1e92305ff55697693b5bc13e300f5417db90e2593b127d421c9f6c440950222 + languageName: node + linkType: hard + "totalist@npm:^3.0.0": version: 3.0.1 resolution: "totalist@npm:3.0.1" @@ -42879,6 +43040,20 @@ __metadata: languageName: node linkType: hard +"tslib@npm:~2.0.1": + version: 2.0.3 + resolution: "tslib@npm:2.0.3" + checksum: 57d9f8e71a768c37a70fcabbb76d686b31773329200db7135faff905818038c742191a0c3791e452ae738d057522c6151d34beddc8e4d0d897f28df84e55a0c0 + languageName: node + linkType: hard + +"tslib@npm:~2.2.0": + version: 2.2.0 + resolution: "tslib@npm:2.2.0" + checksum: 62c705c4d73bcafa3e191df21ed8f024497b61f0e97c3f3e864ae51bcc98d31b830f73ab94b12f7c0dbd2e8f26af759cb521dd61ae88793f0f2abc32b43599a3 + languageName: node + linkType: hard + "tslib@npm:~2.4.0": version: 2.4.1 resolution: "tslib@npm:2.4.1" @@ -43009,6 +43184,7 @@ __metadata: dependencies: "@aws-sdk/client-s3": "npm:^3.363.0" "@aws-sdk/credential-providers": "npm:^3.363.0" + "@envelop/on-resolve": "npm:^4.1.0" "@graphql-yoga/nestjs": "patch:@graphql-yoga/nestjs@2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch" "@nestjs/apollo": "npm:^11.0.5" "@nestjs/axios": "npm:^3.0.1" @@ -43049,6 +43225,8 @@ __metadata: googleapis: "npm:105" graphql: "npm:^16.8.1" graphql-fields: "npm:^2.0.3" + graphql-middleware: "npm:^6.1.35" + graphql-rate-limit: "npm:^3.3.0" graphql-subscriptions: "npm:2.0.0" graphql-tag: "npm:^2.12.6" graphql-type-json: "npm:^0.3.2" @@ -46274,6 +46452,21 @@ __metadata: languageName: node linkType: hard +"yup@npm:^0.32.0": + version: 0.32.11 + resolution: "yup@npm:0.32.11" + dependencies: + "@babel/runtime": "npm:^7.15.4" + "@types/lodash": "npm:^4.14.175" + lodash: "npm:^4.17.21" + lodash-es: "npm:^4.17.21" + nanoclone: "npm:^0.2.1" + property-expr: "npm:^2.0.4" + toposort: "npm:^2.0.2" + checksum: f0802798dc64b49f313886b983a9bea5f283e2094ee2aa1197587b84f50ac5b5d03af99857c313139e63dc02558fac3aaa343503bdbffa96f70006b39d1f59c9 + languageName: node + linkType: hard + "z-schema@npm:~5.0.2": version: 5.0.5 resolution: "z-schema@npm:5.0.5"