Add rate limiting in the server using built in Nest.js capability (#3566)

* 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 <jeremy.magrin@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Joe S 2024-02-07 11:11:32 -06:00 committed by GitHub
parent 3831ddc002
commit 850eab8f8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 413 additions and 59 deletions

View File

@ -43,6 +43,12 @@ import TabItem from '@theme/TabItem';
['PORT', '3000', 'Port'],
]}></OptionTable>
### Security
<OptionTable options={[
['API_RATE_LIMITING_TTL', '100', 'API rate limiting time window'],
['API_RATE_LIMITING_LIMIT', '200', 'API rate limiting max requests'],
]}></OptionTable>
### Tokens
<OptionTable options={[

View File

@ -52,3 +52,5 @@ SIGN_IN_PREFILLED=true
# EMAIL_SMTP_USER=
# EMAIL_SMTP_PASSWORD=
# PASSWORD_RESET_TOKEN_EXPIRES_IN=5m
# API_RATE_LIMITING_TTL=
# API_RATE_LIMITING_LIMIT=

View File

@ -10,7 +10,6 @@ ACCESS_TOKEN_SECRET=secret_jwt
LOGIN_TOKEN_SECRET=secret_login_tokens
REFRESH_TOKEN_SECRET=secret_refresh_token
# ———————— Optional ————————
# DEBUG_MODE=false
# SIGN_IN_PREFILLED=false
@ -21,4 +20,4 @@ REFRESH_TOKEN_SECRET=secret_refresh_token
# AUTH_GOOGLE_ENABLED=false
# MESSAGING_PROVIDER_GMAIL_ENABLED=false
# STORAGE_TYPE=local
# STORAGE_LOCAL_PATH=.local-storage
# STORAGE_LOCAL_PATH=.local-storage

View File

@ -36,6 +36,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.363.0",
"@aws-sdk/credential-providers": "^3.363.0",
"@envelop/on-resolve": "^4.1.0",
"@graphql-yoga/nestjs": "patch:@graphql-yoga/nestjs@2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch",
"@nestjs/apollo": "^11.0.5",
"@nestjs/axios": "^3.0.1",
@ -70,6 +71,8 @@
"googleapis": "105",
"graphql": "^16.8.1",
"graphql-fields": "^2.0.3",
"graphql-middleware": "^6.1.35",
"graphql-rate-limit": "^3.3.0",
"graphql-subscriptions": "2.0.0",
"graphql-tag": "^2.12.6",
"graphql-type-json": "^0.3.2",

View File

@ -4,12 +4,13 @@ import { ConfigModule } from '@nestjs/config';
import { YogaDriver, YogaDriverConfig } from '@graphql-yoga/nestjs';
import { GraphQLConfigService } from 'src/graphql-config.service';
import { GraphQLConfigService } from 'src/graphql-config/graphql-config.service';
import { CoreModule } from './core/core.module';
import { IntegrationsModule } from './integrations/integrations.module';
import { HealthModule } from './health/health.module';
import { WorkspaceModule } from './workspace/workspace.module';
import { GraphQLConfigModule } from './graphql-config/graphql-config.module';
@Module({
imports: [
@ -18,7 +19,7 @@ import { WorkspaceModule } from './workspace/workspace.module';
}),
GraphQLModule.forRootAsync<YogaDriverConfig>({
driver: YogaDriver,
imports: [CoreModule],
imports: [CoreModule, GraphQLConfigModule],
useClass: GraphQLConfigService,
}),
HealthModule,

View File

@ -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<GraphQLContext> {
// 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;
}
}

View File

@ -0,0 +1,3 @@
import { CreateContextFactory } from './create-context.factory';
export const graphQLFactories = [CreateContextFactory];

View File

@ -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 {}

View File

@ -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<YogaDriverConfig<'express'>>
{
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,
}),
],
};

View File

@ -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;
}

View File

@ -287,4 +287,12 @@ export class EnvironmentService {
isSignUpDisabled(): boolean {
return this.configService.get<boolean>('IS_SIGN_UP_DISABLED') ?? false;
}
getApiRateLimitingTtl(): number {
return this.configService.get<number>('API_RATE_LIMITING_TTL') ?? 100;
}
getApiRateLimitingLimit(): number {
return this.configService.get<number>('API_RATE_LIMITING_LIMIT') ?? 200;
}
}

View File

@ -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<string, any> = object,
>(
export const useExceptionHandler = <PluginContext extends GraphQLContext>(
options: ExceptionHandlerPluginOptions,
): Plugin<PluginContext> => {
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) {

View File

@ -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<ContextType = ThrottlerContext> = (
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<typeof getGraphQLRateLimiter>;
}
export const useThrottler = (
options: ThrottlerPluginOptions,
): Plugin<ThrottlerContext> => {
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()]);
}

View File

@ -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<YogaDriverConfig> => {
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',

View File

@ -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<YogaDriverConfig>({
driver: YogaDriver,
useFactory: metadataModuleFactory,
imports: [AuthModule],
inject: [EnvironmentService, ExceptionHandlerService, TokenService],
imports: [GraphQLConfigModule],
inject: [
EnvironmentService,
ExceptionHandlerService,
CreateContextFactory,
],
}),
DataSourceModule,
FieldMetadataModule,

227
yarn.lock
View File

@ -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"