From ef1b5bfc415d7af531004de78f3985e30b7c6304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Wieczorek?= Date: Tue, 14 Apr 2026 09:58:45 +0200 Subject: [PATCH] Migrate to Apollo v5 (#119) * server: Replace error classes no longer provided by Apollo * server: Remove no longer available DataSource base class * server: Remove no longer necessary gql tag * server: Upgrade Apollo packages * server: Refactor API server bootstrap * server: Remove Apollo packages that reached end-of-life * server/client: Bump graphql package version * fixup! server: Refactor API server bootstrap * fixup! server: Remove Apollo packages that reached end-of-life * fixup! server: Refactor API server bootstrap * fixup! server: Replace error classes no longer provided by Apollo * fix merge main and final code review changes * lint fixes --------- Co-authored-by: Juan S. Mrad --- client/package-lock.json | 48 +- client/package.json | 2 +- server/api.ts | 245 +++-- server/decs.d.ts | 11 - .../customScalars/CoopInputOrString.ts | 9 +- server/graphql/customScalars/Cursor.ts | 9 +- .../graphql/customScalars/NonEmptyString.ts | 7 +- .../customScalars/OpaqueScalarMixin.ts | 6 +- server/graphql/customScalars/StringOrFloat.ts | 11 +- server/graphql/datasources/ActionApi.ts | 4 +- server/graphql/datasources/IntegrationApi.ts | 4 +- .../graphql/datasources/InvestigationApi.ts | 4 +- server/graphql/datasources/LocationBankApi.ts | 4 +- server/graphql/datasources/OrgApi.ts | 4 +- server/graphql/datasources/RuleApi.ts | 11 +- server/graphql/datasources/UserApi.ts | 4 +- server/graphql/modules/action.ts | 21 +- server/graphql/modules/actionStatistics.ts | 10 +- server/graphql/modules/apiKey.ts | 17 +- server/graphql/modules/backtest.ts | 9 +- server/graphql/modules/hashBanks/resolvers.ts | 20 +- server/graphql/modules/hashBanks/schema.ts | 4 +- server/graphql/modules/insights.ts | 22 +- server/graphql/modules/integration.ts | 11 +- server/graphql/modules/investigation.ts | 27 +- server/graphql/modules/itemType.ts | 42 +- server/graphql/modules/locationBank.ts | 11 +- server/graphql/modules/manualReviewTool.ts | 95 +- server/graphql/modules/ncmec.ts | 16 +- server/graphql/modules/org.ts | 91 +- server/graphql/modules/policy.ts | 10 +- server/graphql/modules/reportingRule.ts | 21 +- server/graphql/modules/retroaction.ts | 9 +- server/graphql/modules/routingRule.ts | 15 +- server/graphql/modules/rule.ts | 40 +- server/graphql/modules/signal.ts | 7 +- server/graphql/modules/spotTest.ts | 4 +- server/graphql/modules/textBank.ts | 11 +- server/graphql/modules/user.ts | 27 +- server/graphql/resolvers.ts | 34 +- server/graphql/utils/authorization.ts | 5 +- server/graphql/utils/errors.ts | 10 + server/graphql/utils/paginationHandler.ts | 13 +- .../iocContainer/services/gqlDataSources.ts | 3 +- server/package-lock.json | 861 ++++++++++-------- server/package.json | 11 +- 46 files changed, 929 insertions(+), 931 deletions(-) create mode 100644 server/graphql/utils/errors.ts diff --git a/client/package-lock.json b/client/package-lock.json index 174cbe6..39262c6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -50,7 +50,7 @@ "clsx": "^2.1.1", "date-fns": "^3.6.0", "framer-motion": "^11.1.7", - "graphql": "^16.2.0", + "graphql": "^16.13.2", "jsonpointer": "^5.0.1", "latlon-geohash": "^2.0.0", "lodash": "^4.18.1", @@ -2875,9 +2875,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2892,9 +2889,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2909,9 +2903,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2926,9 +2917,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2943,9 +2931,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2960,9 +2945,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2977,9 +2959,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2994,9 +2973,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3011,9 +2987,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3028,9 +3001,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3045,9 +3015,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3062,9 +3029,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3079,9 +3043,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7665,9 +7626,10 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphql": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.2.tgz", - "integrity": "sha512-cvVIBILwuoSyD54U4cF/UXDh5yAobhNV/tPygI4lZhgOIJQE/WLWC4waBRb4I6bDVYb3OVx3lfHbaQOEoUD5sg==", + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } diff --git a/client/package.json b/client/package.json index 5b93f04..3e64e0b 100644 --- a/client/package.json +++ b/client/package.json @@ -57,7 +57,7 @@ "clsx": "^2.1.1", "date-fns": "^3.6.0", "framer-motion": "^11.1.7", - "graphql": "^16.2.0", + "graphql": "^16.13.2", "jsonpointer": "^5.0.1", "latlon-geohash": "^2.0.0", "lodash": "^4.18.1", diff --git a/server/api.ts b/server/api.ts index 920601c..fe52d80 100644 --- a/server/api.ts +++ b/server/api.ts @@ -1,10 +1,10 @@ /* eslint-disable max-lines */ -// In this case, we want to rely on apollo-server-express bundling a -// corresponding version of apollo-server-core, rather than picking an -// apollo-server-core version in package.json -// eslint-disable-next-line import/no-extraneous-dependencies import os from 'node:os'; import path from 'path'; +import { ApolloServer } from '@apollo/server'; +import { unwrapResolverError } from '@apollo/server/errors'; +import { ApolloServerPluginLandingPageDisabled } from '@apollo/server/plugin/disabled'; +import { expressMiddleware } from '@as-integrations/express4'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { MapperKind, mapSchema } from '@graphql-tools/utils'; import { SpanStatusCode } from '@opentelemetry/api'; @@ -13,12 +13,7 @@ import { SEMATTRS_EXCEPTION_STACKTRACE, SEMATTRS_EXCEPTION_TYPE, } from '@opentelemetry/semantic-conventions'; -import { - ApolloError, - ApolloServerPluginLandingPageDisabled, - ApolloServerPluginLandingPageGraphQLPlayground, -} from 'apollo-server-core'; -import { ApolloServer } from 'apollo-server-express'; +import { GraphQLError, type GraphQLFormattedError } from 'graphql'; import connectPgSimple from 'connect-pg-simple'; import cors from 'cors'; import express, { type ErrorRequestHandler } from 'express'; @@ -34,7 +29,7 @@ import { makeLoginSsoRequiredError, makeLoginUserDoesNotExistError, } from './graphql/datasources/UserApi.js'; -import resolvers from './graphql/resolvers.js'; +import resolvers, { type Context } from './graphql/resolvers.js'; import typeDefs from './graphql/schema.js'; import { authSchemaWrapper } from './graphql/utils/authorization.js'; import { type Dependencies } from './iocContainer/index.js'; @@ -342,7 +337,7 @@ export default async function makeApiServer(deps: Dependencies) { /** * Apollo Server - uses /api/graphql path */ - const apolloServer = new ApolloServer({ + const apolloServer = new ApolloServer({ schema: mapSchema(makeExecutableSchema({ typeDefs, resolvers }), { [MapperKind.QUERY_ROOT_FIELD]( fieldConfig, @@ -361,148 +356,150 @@ export default async function makeApiServer(deps: Dependencies) { return authSchemaWrapper(fieldConfig, schema); }, }), - dataSources: () => deps.DataSources, - context: ({ req, res }) => { - return { - ...buildContext({ req, res }), - services: makeGqlServices(deps), - }; - }, plugins: [ - { - ...(process.env.NODE_ENV === 'production' - ? ApolloServerPluginLandingPageDisabled() - : ApolloServerPluginLandingPageGraphQLPlayground()), - }, + ...(process.env.NODE_ENV === 'production' + ? [ApolloServerPluginLandingPageDisabled()] + : []), ], validationRules: [depthLimit(safeGetEnvInt('GRAPHQL_MAX_DEPTH', 10))], introspection: process.env.NODE_ENV !== 'production', - formatError(e) { - // `e` can be an ApolloError instance, but will only be one if such an - // instance (or an ApolloError subclass) was explicitly thrown from a - // resolver. In that case, we assume the thrower knows they're dealing - // with apollo, and we can just pass the error through as-is. - if (e instanceof ApolloError) { - return e; + formatError(formattedError, error) { + // unwrapResolverError removes the GraphQLError wrapper added by graphql-js + // when a non-GraphQL error is thrown from a resolver. + const rawError = unwrapResolverError(error); + + // If the raw error is a GraphQLError (explicitly thrown by our code or + // generated by graphql-js for parse/validation errors), the formattedError + // is already correctly shaped -- pass it through. + if (rawError instanceof GraphQLError) { + return formattedError; } - // In almost all other cases, the error will be an instance of the - // `GraphQLError` class, which apollo instantiates automatically, and uses - // to wrap any non-ApolloError error thrown from a resolver. However, - // ocassionally -- e.g., if an error occurs during context creation rather - // than in the resolver -- the error doesn't get wrapped (or it's wrapped - // but with no originalError), so we handle both cases. Once we have the - // underlying error that was actually thrown, we sanitize it to remove - // sensitive details, and then try to format it in the most informative - // way possible. - const sanitizedError = sanitizeError(e.originalError ?? e); + // For all other errors (CoopError, unexpected errors, context errors), + // sanitize to remove sensitive details and reformat for the client. + const sanitizedError = sanitizeError( + rawError instanceof Error ? rawError : (error as Error), + ); const { title: sanitizedErrorTitle, ...extensions } = sanitizedError; - return { - // When apollo-server wraps the resolver-thrown error in a GraphQLError, + const result: GraphQLFormattedError = { + // When graphql-js wraps the resolver-thrown error in a GraphQLError, // it automatically tracks some metadata about where the error was thrown // from. That can be useful to clients, in a way that's a bit different // from our CoopError.pointer field; it tells them whether a null // value was return in the response because a given resolver failed, or // because the field's value is actually null. So, we pass this - // apollo-annotated metdata through as-is. - locations: e.locations, - path: e.path, + // metadata through as-is. + locations: formattedError.locations, + path: formattedError.path, // Apollo server also defines some predefined error codes that it could // be helpful for us to mimic on our custom errors (in case Apollo // clients handle them out of the box). The true, Coop-assigned code // for the error, though, will be in the `type` key, just like when // sending errors in REST responses (though, for GQL, this lives under // `extensions`). - code: extensions.type.includes(ErrorType.Unauthenticated) - ? 'UNAUTHENTICATED' - : extensions.type.includes(ErrorType.Unauthorized) - ? 'FORBIDDEN' - : extensions.type.includes(ErrorType.InvalidUserInput) - ? 'BAD_USER_INPUT' - : 'INTERNAL_SERVER_ERROR', - // Then, this is info from the sanitized verion of the actual thrown error. + extensions: { + ...extensions, + code: extensions.type.includes(ErrorType.Unauthenticated) + ? 'UNAUTHENTICATED' + : extensions.type.includes(ErrorType.Unauthorized) + ? 'FORBIDDEN' + : extensions.type.includes(ErrorType.InvalidUserInput) + ? 'BAD_USER_INPUT' + : 'INTERNAL_SERVER_ERROR', + }, message: sanitizedErrorTitle, - extensions, }; + return result; }, }); - await apolloServer.start().then(() => { - apolloServer.applyMiddleware({ app }); - Object.entries(controllers).forEach(([_k, controller]) => { - controller.routes.forEach((it) => { - const handler = it.handler(deps); - app[it.method]( - path.join(controller.pathPrefix, it.path), - ...(Array.isArray(handler) ? handler : [handler]), - ); - }); - }); + await apolloServer.start(); - // catch 404 and forward to error handler - app.use(function (_req, _res, next) { - next( - makeNotFoundError('Requested route not found.', { - shouldErrorSpan: true, - }), + app.use( + '/graphql', + express.json(), + expressMiddleware(apolloServer, { + context: async ({ req, res }) => ({ + ...buildContext({ req, res }), + services: makeGqlServices(deps), + dataSources: deps.DataSources, + } as unknown as Context), + }), + ); + + Object.entries(controllers).forEach(([_k, controller]) => { + controller.routes.forEach((it) => { + const handler = it.handler(deps); + app[it.method]( + path.join(controller.pathPrefix, it.path), + ...(Array.isArray(handler) ? handler : [handler]), ); }); + }); - // error handler - app.use(async function (err, _req, res, _next) { - await deps.Tracer.addActiveSpan( - { resource: 'app', operation: 'handleError' }, - async (span) => { - span.recordException(err); - span.setStatus({ code: SpanStatusCode.ERROR, message: err.message }); - - // I don't know if these attributes are necessary, with recordException - span.setAttribute(SEMATTRS_EXCEPTION_MESSAGE, err.message); - if (err.stack) { - span.setAttribute(SEMATTRS_EXCEPTION_STACKTRACE, err.stack); - } - span.setAttribute(SEMATTRS_EXCEPTION_TYPE, err.name); - - const errors = (() => { - if (err instanceof AggregateError) { - const extractedErrors = getErrorsFromAggregateError(err); - return isNonEmptyArray(extractedErrors) ? extractedErrors : [err]; - } else { - return [err]; - } - })() satisfies NonEmptyArray; - - // If we had any nested errors (from an AggregateError), - // attach those to the span too. - if (errors.length > 1 || errors[0] !== err) { - span.setAttribute( - 'errors', - jsonStringify( - errors.map((it) => safePick(it, ['name', 'message', 'stack'])), - ), - ); - } + // catch 404 and forward to error handler + app.use(function (_req, _res, next) { + next( + makeNotFoundError('Requested route not found.', { + shouldErrorSpan: true, + }), + ); + }); - // If we've already sent response headers or the response status code, - // we can't actually send a different status code here: it's an error - // in HTTP to send the headers portion of a response twice. So, we - // need to skip this step. - // - // This can happen, e.g., if we have a request handler that - // immediately responds with a 202/204 but then continues to do some - // processing work in the background, and that work errors. - if (!res.headersSent) { - const safeErrors = errors.map((it) => - sanitizeError(it), - ) satisfies SerializableError[] as NonEmptyArray; - - res.status(pickStatus(safeErrors)).json({ errors: safeErrors }); + // error handler + app.use(async function (err, _req, res, _next) { + await deps.Tracer.addActiveSpan( + { resource: 'app', operation: 'handleError' }, + async (span) => { + span.recordException(err); + span.setStatus({ code: SpanStatusCode.ERROR, message: err.message }); + + // I don't know if these attributes are necessary, with recordException + span.setAttribute(SEMATTRS_EXCEPTION_MESSAGE, err.message); + if (err.stack) { + span.setAttribute(SEMATTRS_EXCEPTION_STACKTRACE, err.stack); + } + span.setAttribute(SEMATTRS_EXCEPTION_TYPE, err.name); + + const errors = (() => { + if (err instanceof AggregateError) { + const extractedErrors = getErrorsFromAggregateError(err); + return isNonEmptyArray(extractedErrors) ? extractedErrors : [err]; + } else { + return [err]; } - }, - ); - } as ErrorRequestHandler); - }); + })() satisfies NonEmptyArray; + + // If we had any nested errors (from an AggregateError), + // attach those to the span too. + if (errors.length > 1 || errors[0] !== err) { + span.setAttribute( + 'errors', + jsonStringify( + errors.map((it) => safePick(it, ['name', 'message', 'stack'])), + ), + ); + } + + // If we've already sent response headers or the response status code, + // we can't actually send a different status code here: it's an error + // in HTTP to send the headers portion of a response twice. So, we + // need to skip this step. + // + // This can happen, e.g., if we have a request handler that + // immediately responds with a 202/204 but then continues to do some + // processing work in the background, and that work errors. + if (!res.headersSent) { + const safeErrors = errors.map((it) => + sanitizeError(it), + ) satisfies SerializableError[] as NonEmptyArray; + + res.status(pickStatus(safeErrors)).json({ errors: safeErrors }); + } + }, + ); + } as ErrorRequestHandler); return { app, diff --git a/server/decs.d.ts b/server/decs.d.ts index de631a1..e00b395 100644 --- a/server/decs.d.ts +++ b/server/decs.d.ts @@ -8,17 +8,6 @@ declare interface String { toLowerCase(this: T): Lowercase; } -// Workaround for https://github.com/apollographql/apollo-server/issues/6868 -// At some point, we should just upgrade to Apollo Server 4, but that's a big lift. -// -// We also have to fork + override retry-axios, which we don't depend on -// directly, but it's a dependency of the google maps sdk. However, the version -// of retry-axios used by the SDK isn't compatible with moduleResolution=nodeNext, -// so we were getting a type error. Forking the SDK to update retry-axios isn't -// feasible, because the new version of retry-axios uses a newer axios version, -// which contains some breaking changes (to param serialization; see -// https://github.com/axios/axios/pull/4734), which would be hard to update the -// SDK to account for. declare module '@graphql-tools/schema' { import { GraphQLSchema } from 'graphql'; diff --git a/server/graphql/customScalars/CoopInputOrString.ts b/server/graphql/customScalars/CoopInputOrString.ts index ef2f623..34fc3be 100644 --- a/server/graphql/customScalars/CoopInputOrString.ts +++ b/server/graphql/customScalars/CoopInputOrString.ts @@ -1,8 +1,9 @@ -import { UserInputError } from 'apollo-server-express'; import { GraphQLScalarType, Kind } from 'graphql'; import { CoopInput } from '../../services/moderationConfigService/index.js'; +import { userInputError } from '../utils/errors.js'; + export const CoopInputEnumInverted = Object.fromEntries( Object.entries(CoopInput).map(([key, value]) => [value, key]), ) as { [key: string]: string | undefined }; @@ -20,7 +21,7 @@ export default new GraphQLScalarType({ 'Either an arbitrary string or a CoopInput enum key name (not the TS runtime value).', serialize(value) { if (typeof value !== 'string') { - throw new UserInputError('Expected a string.'); + throw userInputError('Expected a string.'); } return CoopInputEnumInverted[value] ?? value; @@ -28,7 +29,7 @@ export default new GraphQLScalarType({ parseValue: parseCoopInputOrStringValue, parseLiteral(ast) { if (ast.kind !== Kind.STRING) { - throw new UserInputError('CoopInputOrString must be a string.'); + throw userInputError('CoopInputOrString must be a string.'); } return parseCoopInputOrStringValue(ast.value); }, @@ -36,7 +37,7 @@ export default new GraphQLScalarType({ function parseCoopInputOrStringValue(value: unknown) { if (typeof value !== 'string') { - throw new UserInputError('CoopInputOrString must be a CoopInput.'); + throw userInputError('CoopInputOrString must be a CoopInput.'); } // @ts-ignore diff --git a/server/graphql/customScalars/Cursor.ts b/server/graphql/customScalars/Cursor.ts index c708eb9..dbac1af 100644 --- a/server/graphql/customScalars/Cursor.ts +++ b/server/graphql/customScalars/Cursor.ts @@ -1,4 +1,3 @@ -import { UserInputError } from 'apollo-server-express'; import { GraphQLScalarType, Kind } from 'graphql'; import { @@ -11,6 +10,8 @@ import { } from '../../utils/encoding.js'; import { type JSON } from '../../utils/json-schema-types.js'; +import { userInputError } from '../utils/errors.js'; + /** * A cursor is a pointer into a particular place in an ordered collection. * This custom type helps us serialize cursors as Base64-encoded strings, and @@ -33,7 +34,7 @@ export default new GraphQLScalarType>>({ parseValue: parseCursorValue, parseLiteral(ast) { if (ast.kind !== Kind.STRING) { - throw new UserInputError('Cursor values must be strings.'); + throw userInputError('Cursor values must be strings.'); } return parseCursorValue(ast.value); }, @@ -41,7 +42,7 @@ export default new GraphQLScalarType>>({ function parseCursorValue(value: unknown) { if (typeof value !== 'string') { - throw new UserInputError('Cursor values must be strings.'); + throw userInputError('Cursor values must be strings.'); } try { @@ -49,6 +50,6 @@ function parseCursorValue(value: unknown) { const jsonString = b64Decode(value as B64Of>); return jsonParse(jsonString); } catch { - throw new UserInputError('Invalid cursor value'); + throw userInputError('Invalid cursor value'); } } diff --git a/server/graphql/customScalars/NonEmptyString.ts b/server/graphql/customScalars/NonEmptyString.ts index 8dd55ac..27ede3a 100644 --- a/server/graphql/customScalars/NonEmptyString.ts +++ b/server/graphql/customScalars/NonEmptyString.ts @@ -1,4 +1,3 @@ -import { UserInputError } from 'apollo-server-express'; import { GraphQLScalarType, Kind } from 'graphql'; import { @@ -6,6 +5,8 @@ import { type NonEmptyString, } from '../../utils/typescript-types.js'; +import { userInputError } from '../utils/errors.js'; + /** * This scalar is needed for values that must be non-empty strings. This * implementation borrows from the graphql-scalars library's implementation, but @@ -16,14 +17,14 @@ export default new GraphQLScalarType({ description: 'A string that must be non-empty.', serialize(value) { if (typeof value !== 'string') { - throw new UserInputError('Expected a string.'); + throw userInputError('Expected a string.'); } return tryParseNonEmptyString(value); }, parseValue: tryParseNonEmptyString, parseLiteral(ast) { if (ast.kind !== Kind.STRING) { - throw new UserInputError('NonEmptyString must be a string.'); + throw userInputError('NonEmptyString must be a string.'); } return tryParseNonEmptyString(ast.value); }, diff --git a/server/graphql/customScalars/OpaqueScalarMixin.ts b/server/graphql/customScalars/OpaqueScalarMixin.ts index c32459d..33ac57e 100644 --- a/server/graphql/customScalars/OpaqueScalarMixin.ts +++ b/server/graphql/customScalars/OpaqueScalarMixin.ts @@ -1,12 +1,12 @@ -import { UserInputError } from 'apollo-server-express'; import { Kind, type GraphQLScalarType } from 'graphql'; import jwt from 'jsonwebtoken'; +import { userInputError } from '../utils/errors.js'; const parseOpaqueScalarValue = (jwtSigningKey: string) => (inputValue: unknown) => { if (typeof inputValue !== 'string') { - throw new UserInputError('OpaqueScalar values must be strings.'); + throw userInputError('OpaqueScalar values must be strings.'); } return jwt.verify(inputValue, jwtSigningKey) as T; @@ -50,7 +50,7 @@ export default ( parseValue: parseOpaqueScalarValue(jwtSigningKey), parseLiteral(ast) { if (ast.kind !== Kind.STRING) { - throw new UserInputError('OpaqueScalar values must be strings.'); + throw userInputError('OpaqueScalar values must be strings.'); } return parseOpaqueScalarValue(jwtSigningKey)(ast.value); }, diff --git a/server/graphql/customScalars/StringOrFloat.ts b/server/graphql/customScalars/StringOrFloat.ts index 2a1cb35..0aa10c6 100644 --- a/server/graphql/customScalars/StringOrFloat.ts +++ b/server/graphql/customScalars/StringOrFloat.ts @@ -1,6 +1,7 @@ -import { UserInputError } from 'apollo-server-express'; import { GraphQLScalarType, Kind } from 'graphql'; +import { userInputError } from '../utils/errors.js'; + /** * This scalar is needed for values that can be represented either * as strings or numbers. @@ -10,7 +11,7 @@ export default new GraphQLScalarType({ description: 'Either an arbitrary string or a float.', serialize(value) { if (typeof value !== 'string' && typeof value !== 'number') { - throw new UserInputError('Expected a string or float.'); + throw userInputError('Expected a string or float.'); } return value; }, @@ -21,7 +22,7 @@ export default new GraphQLScalarType({ ast.kind !== Kind.FLOAT && ast.kind !== Kind.INT ) { - throw new UserInputError('StringOrFloat must be a string or number.'); + throw userInputError('StringOrFloat must be a string or number.'); } return parseStringOrFloatValue(ast.value); }, @@ -29,9 +30,7 @@ export default new GraphQLScalarType({ function parseStringOrFloatValue(value: unknown) { if (typeof value !== 'string' && typeof value !== 'number') { - throw new UserInputError( - 'StringOrFloat must be a string or number when passed to the server.', - ); + throw userInputError('StringOrFloat must be a string or number when passed to the server.'); } // NB: Number('') returns 0, so we have to check for the empty string diff --git a/server/graphql/datasources/ActionApi.ts b/server/graphql/datasources/ActionApi.ts index 888e735..666d94d 100644 --- a/server/graphql/datasources/ActionApi.ts +++ b/server/graphql/datasources/ActionApi.ts @@ -1,5 +1,4 @@ import { type Exception } from '@opentelemetry/api'; -import { DataSource } from 'apollo-datasource'; import pLimit from 'p-limit'; import { uid } from 'uid'; import { v1 as uuidv1 } from 'uuid'; @@ -30,7 +29,7 @@ import { /** * GraphQL Object for an Action */ -class ActionAPI extends DataSource { +class ActionAPI { constructor( private readonly actionPublisher: Dependencies['ActionPublisher'], private readonly sequelize: Dependencies['Sequelize'], @@ -38,7 +37,6 @@ class ActionAPI extends DataSource { private readonly itemInvestigationService: Dependencies['ItemInvestigationService'], private readonly getItemTypeEventuallyConsistent: Dependencies['getItemTypeEventuallyConsistent'], ) { - super(); } async getGraphQLActionFromId(opts: { id: string; orgId: string }) { diff --git a/server/graphql/datasources/IntegrationApi.ts b/server/graphql/datasources/IntegrationApi.ts index 9e10b3a..c1adae0 100644 --- a/server/graphql/datasources/IntegrationApi.ts +++ b/server/graphql/datasources/IntegrationApi.ts @@ -1,4 +1,3 @@ -import { DataSource } from 'apollo-datasource'; import { inject, type Dependencies } from '../../iocContainer/index.js'; import '../../services/signalAuthService/index.js'; @@ -62,11 +61,10 @@ function mergeManifest( /** * TODO: this whole class should probably be merged into the signal auth service. */ -class IntegrationAPI extends DataSource { +class IntegrationAPI { constructor( private readonly signalAuthService: Dependencies['SignalAuthService'], ) { - super(); } async setConfig( diff --git a/server/graphql/datasources/InvestigationApi.ts b/server/graphql/datasources/InvestigationApi.ts index a867c84..46f62aa 100644 --- a/server/graphql/datasources/InvestigationApi.ts +++ b/server/graphql/datasources/InvestigationApi.ts @@ -1,4 +1,3 @@ -import { DataSource } from 'apollo-datasource'; import { inject, type Dependencies } from '../../iocContainer/index.js'; import { type ItemSubmissionForGQL } from '../types.js'; @@ -8,11 +7,10 @@ export type UserHistoryForGQL = { user: ItemSubmissionForGQL; }; -class InvestigationAPI extends DataSource { +class InvestigationAPI { constructor( private readonly itemHistoryQueries: Dependencies['ItemHistoryQueries'], ) { - super(); } async getItemHistory(opts: { diff --git a/server/graphql/datasources/LocationBankApi.ts b/server/graphql/datasources/LocationBankApi.ts index 1a5f90e..eeed5db 100644 --- a/server/graphql/datasources/LocationBankApi.ts +++ b/server/graphql/datasources/LocationBankApi.ts @@ -1,5 +1,4 @@ import { type Exception } from '@opentelemetry/api'; -import { DataSource } from 'apollo-datasource'; import { uid } from 'uid'; import { v1 as uuidV1 } from 'uuid'; @@ -28,14 +27,13 @@ export type LocationBankWithoutFullPlacesAPIResponse = Omit< 'fullPlacesApiResponse' >; -class LocationBankAPI extends DataSource { +class LocationBankAPI { private lookupPlaceId: PlacesApiService['lookupPlaceId']; constructor( placesApiService: PlacesApiService, private readonly sequelize: Dependencies['Sequelize'], private readonly tracer: Dependencies['Tracer'], ) { - super(); this.lookupPlaceId = placesApiService.lookupPlaceId.bind(placesApiService); } diff --git a/server/graphql/datasources/OrgApi.ts b/server/graphql/datasources/OrgApi.ts index ce3d05d..ccd7ed7 100644 --- a/server/graphql/datasources/OrgApi.ts +++ b/server/graphql/datasources/OrgApi.ts @@ -1,7 +1,6 @@ import crypto from 'node:crypto'; import { URL } from 'node:url'; import { type Exception } from '@opentelemetry/api'; -import { DataSource } from 'apollo-datasource'; import { uid } from 'uid'; import { inject, type Dependencies } from '../../iocContainer/index.js'; @@ -20,7 +19,7 @@ import { type GQLRequestDemoInput, } from '../generated.js'; -class OrgAPI extends DataSource { +class OrgAPI { constructor( private readonly orgCreationLogger: Dependencies['OrgCreationLogger'], private readonly apiKeyService: Dependencies['ApiKeyService'], @@ -34,7 +33,6 @@ class OrgAPI extends DataSource { private readonly orgSettingsService: Dependencies['OrgSettingsService'], private readonly manualReviewToolService: Dependencies['ManualReviewToolService'], ) { - super(); } async createOrg(params: GQLMutationCreateOrgArgs) { diff --git a/server/graphql/datasources/RuleApi.ts b/server/graphql/datasources/RuleApi.ts index 1a34644..46f9b84 100644 --- a/server/graphql/datasources/RuleApi.ts +++ b/server/graphql/datasources/RuleApi.ts @@ -2,8 +2,7 @@ import { type Exception } from '@opentelemetry/api'; import { makeEnumLike } from '@roostorg/types'; -import { DataSource } from 'apollo-datasource'; -import { AuthenticationError } from 'apollo-server-express'; + import { sql, type Kysely } from 'kysely'; import Sequelize from 'sequelize'; import { uid } from 'uid'; @@ -68,6 +67,7 @@ import { import { oneOfInputToTaggedUnion } from '../utils/inputHelpers.js'; import { type CursorInfo, type Edge } from '../utils/paginationHandler.js'; import { locationAreaInputToLocationArea } from './LocationBankApi.js'; +import { unauthenticatedError } from '../utils/errors.js'; const { Op, Transaction } = Sequelize; const SortOrder = makeEnumLike(['ASC', 'DESC']); @@ -267,7 +267,7 @@ function parseAggregationClauseInput( /** * GraphQL Object for a Rule */ -class RuleAPI extends DataSource { +class RuleAPI { private readonly warehouse: Kysely; constructor( @@ -279,16 +279,13 @@ class RuleAPI extends DataSource { private readonly tracer: Dependencies['Tracer'], private readonly signalsService: Dependencies['SignalsService'], ) { - super(); this.warehouse = dialect.getKyselyInstance() as Kysely; } async getGraphQLRuleFromId(id: string, orgId: string) { const rule = await this.models.Rule.findByPk(id, { rejectOnEmpty: true }); if (rule.orgId !== orgId) { - throw new AuthenticationError( - 'User not authenticated to fetch this rule', - ); + throw unauthenticatedError('User not authenticated to fetch this rule'); } return rule; diff --git a/server/graphql/datasources/UserApi.ts b/server/graphql/datasources/UserApi.ts index 7062e88..4b780b6 100644 --- a/server/graphql/datasources/UserApi.ts +++ b/server/graphql/datasources/UserApi.ts @@ -1,7 +1,6 @@ /* eslint-disable max-classes-per-file */ import { type Exception } from '@opentelemetry/api'; -import { DataSource } from 'apollo-datasource'; import { type PassportContext } from 'graphql-passport'; import { uid } from 'uid'; @@ -23,13 +22,12 @@ import { WEEK_MS } from '../../utils/time.js'; /** * GraphQL Object for a User */ -class UserAPI extends DataSource { +class UserAPI { constructor( private readonly sequelize: Dependencies['Sequelize'], private readonly tracer: Dependencies['Tracer'], private readonly userManagementService: Dependencies['UserManagementService'], ) { - super(); } async getGraphQLUserFromId(opts: { id: string; orgId: string }) { diff --git a/server/graphql/modules/action.ts b/server/graphql/modules/action.ts index b818a3c..94e3038 100644 --- a/server/graphql/modules/action.ts +++ b/server/graphql/modules/action.ts @@ -1,5 +1,3 @@ -import { AuthenticationError } from 'apollo-server-express'; - import { isCoopErrorOfType } from '../../utils/errors.js'; import { assertUnreachable } from '../../utils/misc.js'; import { @@ -12,6 +10,7 @@ import { type GQLQueryResolvers, } from '../generated.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` interface ActionBase { @@ -183,7 +182,7 @@ const CustomAction: GQLCustomActionResolvers = { async itemTypes(action, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getItemTypesForAction({ orgId: user.orgId, @@ -196,7 +195,7 @@ const EnqueueAuthorToMrtAction: GQLEnqueueAuthorToMrtActionResolvers = { async itemTypes(action, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getItemTypesForAction({ orgId: user.orgId, @@ -209,7 +208,7 @@ const EnqueueToMrtAction: GQLEnqueueToMrtActionResolvers = { async itemTypes(action, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getItemTypesForAction({ orgId: user.orgId, @@ -222,7 +221,7 @@ const EnqueueToNcmecAction: GQLEnqueueToNcmecActionResolvers = { async itemTypes(action, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getItemTypesForAction({ orgId: user.orgId, @@ -235,7 +234,7 @@ const Query: GQLQueryResolvers = { async action(_, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.dataSources.actionAPI.getGraphQLActionFromId({ @@ -250,7 +249,7 @@ const Mutation: GQLMutationResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const action = await context.dataSources.actionAPI.createAction( params.input, @@ -269,7 +268,7 @@ const Mutation: GQLMutationResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { orgId } = user; const action = await context.dataSources.actionAPI.updateAction( @@ -288,7 +287,7 @@ const Mutation: GQLMutationResolvers = { async deleteAction(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { orgId } = user; return context.dataSources.actionAPI.deleteAction(orgId, params.id); @@ -296,7 +295,7 @@ const Mutation: GQLMutationResolvers = { async bulkExecuteActions(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { orgId, id, email } = user; diff --git a/server/graphql/modules/actionStatistics.ts b/server/graphql/modules/actionStatistics.ts index dbdd1ab..38c512b 100644 --- a/server/graphql/modules/actionStatistics.ts +++ b/server/graphql/modules/actionStatistics.ts @@ -1,9 +1,9 @@ /* eslint-disable max-lines */ -import { AuthenticationError } from 'apollo-server-core'; - import { type GQLQueryResolvers } from '../generated.js'; +import { unauthenticatedError } from '../utils/errors.js'; + const typeDefs = /* GraphQL */ ` type ActionData { count: Int! @@ -90,7 +90,7 @@ const Query: GQLQueryResolvers = { async actionStatistics(_, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const a = { @@ -122,7 +122,7 @@ const Query: GQLQueryResolvers = { async topPolicyViolations(_, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { @@ -149,7 +149,7 @@ const Query: GQLQueryResolvers = { async recentUserStrikeActions(_, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const recentUserStrikeActions = await context.services.UserStrikeService.getRecentUserStrikeActions({ diff --git a/server/graphql/modules/apiKey.ts b/server/graphql/modules/apiKey.ts index 1ba4145..394c827 100644 --- a/server/graphql/modules/apiKey.ts +++ b/server/graphql/modules/apiKey.ts @@ -1,8 +1,7 @@ -import { AuthenticationError } from 'apollo-server-express'; - import { ErrorType, CoopError } from '../../utils/errors.js'; import { logErrorJson } from '../../utils/logging.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { forbiddenError } from '../utils/errors.js'; /** Context shape required by rotateWebhookSigningKey (avoids importing resolvers). */ type RotateWebhookSigningKeyContext = { @@ -78,7 +77,7 @@ const Query: any = { async apiKey(_: any, __: any, context: any) { const user = context.getUser(); if (!user || !user.orgId) { - throw new AuthenticationError('User must be authenticated'); + throw forbiddenError('User does not have permission to check if key exists'); } const apiKeyRecord = await context.services.ApiKeyService.getActiveApiKeyForOrg(user.orgId); @@ -94,12 +93,10 @@ const Mutation: any = { async rotateApiKey(_: any, { input }: any, context: any) { const user = context.getUser(); if (!user || !user.orgId) { - throw new AuthenticationError('User must be authenticated'); + throw forbiddenError('User does not have permission to rotate the API key'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to rotate the API key', - ); + throw forbiddenError('User does not have permission to rotate the API key'); } try { @@ -148,12 +145,10 @@ const Mutation: any = { ) { const user = context.getUser(); if (!user || !user.orgId) { - throw new AuthenticationError('User must be authenticated'); + throw forbiddenError('User does not have permission to rotate the webhook signing key'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to rotate the webhook signing key', - ); + throw forbiddenError('User does not have permission to rotate the webhook signing key'); } try { diff --git a/server/graphql/modules/backtest.ts b/server/graphql/modules/backtest.ts index dca8f90..5456bde 100644 --- a/server/graphql/modules/backtest.ts +++ b/server/graphql/modules/backtest.ts @@ -1,5 +1,3 @@ -import { AuthenticationError, ForbiddenError } from 'apollo-server-express'; - import { type Backtest } from '../../models/rules/BacktestModel.js'; import { hasPermission, @@ -12,6 +10,7 @@ import { makeConnectionResolver, type ConnectionArguments, } from '../utils/paginationHandler.js'; +import { forbiddenError, unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` enum BacktestStatus { @@ -119,11 +118,11 @@ const resolvers = { ); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } else if (!hasPermission(UserPermission.RUN_BACKTEST, user.role)) { - throw new ForbiddenError('User not authorized to create backtests.'); + throw forbiddenError('User not authorized to create backtests.'); } else if (!rule || user.orgId !== rule.orgId) { - throw new ForbiddenError('Invalid rule.'); + throw forbiddenError('Invalid rule.'); } return { diff --git a/server/graphql/modules/hashBanks/resolvers.ts b/server/graphql/modules/hashBanks/resolvers.ts index f12ea56..736edad 100644 --- a/server/graphql/modules/hashBanks/resolvers.ts +++ b/server/graphql/modules/hashBanks/resolvers.ts @@ -1,8 +1,8 @@ -import { AuthenticationError } from 'apollo-server-express'; import { isCoopErrorOfType } from '../../../utils/errors.js'; import type { Context } from '../../resolvers.js'; import type { GQLMutationResolvers, GQLQueryResolvers } from '../../generated.js'; import { gqlErrorResult, gqlSuccessResult } from '../../utils/gqlResult.js'; +import { unauthenticatedError } from '../../utils/errors.js'; interface ExchangeConfigInput { api_name: string; @@ -14,7 +14,7 @@ const Query: GQLQueryResolvers = { async hashBanks(_: unknown, __: unknown, context: Context) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.HMAHashBankService.listBanks(user.orgId); @@ -23,7 +23,7 @@ const Query: GQLQueryResolvers = { async hashBank(_: unknown, { name }: { name: string }, context: Context) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } try { @@ -39,7 +39,7 @@ const Query: GQLQueryResolvers = { async hashBankById(_: unknown, { id }: { id: string }, context: Context) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } try { @@ -55,7 +55,7 @@ const Query: GQLQueryResolvers = { async exchangeApis(_: unknown, __: unknown, context: Context) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.HMAHashBankService.getExchangeApis(); @@ -64,7 +64,7 @@ const Query: GQLQueryResolvers = { async exchangeApiSchema(_: unknown, { apiName }: { apiName: string }, context: Context) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.HMAHashBankService.getExchangeApiSchema(apiName); @@ -84,7 +84,7 @@ const Mutation: GQLMutationResolvers = { ) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } try { @@ -136,7 +136,7 @@ const Mutation: GQLMutationResolvers = { ) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } try { @@ -165,7 +165,7 @@ const Mutation: GQLMutationResolvers = { ) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.HMAHashBankService.deleteBank(user.orgId, id); @@ -179,7 +179,7 @@ const Mutation: GQLMutationResolvers = { ) { const user = context.getUser(); if (!user?.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } // eslint-disable-next-line no-restricted-syntax diff --git a/server/graphql/modules/hashBanks/schema.ts b/server/graphql/modules/hashBanks/schema.ts index 04fd4ed..dd2affb 100644 --- a/server/graphql/modules/hashBanks/schema.ts +++ b/server/graphql/modules/hashBanks/schema.ts @@ -1,6 +1,4 @@ -import { gql } from 'apollo-server-express'; - -export const typeDefs = gql` +export const typeDefs = /* GraphQL */ ` type ExchangeInfo { api: String! enabled: Boolean! diff --git a/server/graphql/modules/insights.ts b/server/graphql/modules/insights.ts index 3fbe270..54e54fd 100644 --- a/server/graphql/modules/insights.ts +++ b/server/graphql/modules/insights.ts @@ -1,4 +1,3 @@ -import { AuthenticationError } from 'apollo-server-core'; // because graphql args are sometimes imported wiht _, we use // lodash as opposed to _ to avoid overloading import lodash from 'lodash'; @@ -14,6 +13,7 @@ import type { GQLRuleInsightsResolvers, } from '../generated.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` enum LookbackVersion { @@ -123,7 +123,7 @@ const RuleExecutionResult: GQLRuleExecutionResultResolvers = { async signalResults(ruleExecutionResult, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const fullResults = ruleExecutionResult.result; @@ -139,7 +139,7 @@ const ReportingRuleExecutionResult: GQLReportingRuleExecutionResultResolvers = { async signalResults(reportingRuleExecutionResult, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const fullResults = reportingRuleExecutionResult.result; @@ -155,7 +155,7 @@ const RuleInsights: GQLRuleInsightsResolvers = { async passRateData(rule, args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.ruleAPI.ruleInsights.getRulePassRateData( @@ -167,7 +167,7 @@ const RuleInsights: GQLRuleInsightsResolvers = { async latestVersionSamples(rule, _args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const samples = @@ -197,7 +197,7 @@ const RuleInsights: GQLRuleInsightsResolvers = { async priorVersionSamples(rule, _args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const samples = @@ -230,7 +230,7 @@ const ReportingRuleInsights: GQLReportingRuleInsightsResolvers = { async passRateData(rule, args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.ruleAPI.ruleInsights.getRulePassRateData( @@ -242,7 +242,7 @@ const ReportingRuleInsights: GQLReportingRuleInsightsResolvers = { async latestVersionSamples(rule, _args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const samples = @@ -272,7 +272,7 @@ const ReportingRuleInsights: GQLReportingRuleInsightsResolvers = { async priorVersionSamples(rule, _args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const samples = @@ -305,7 +305,7 @@ const Query: GQLQueryResolvers = { async getUserStrikeCountDistribution(_, __, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const allUserStrikeCounts = @@ -324,7 +324,7 @@ const Query: GQLQueryResolvers = { async getFullRuleResultForItem(_, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { ruleId, item, date, lookback } = input; try { diff --git a/server/graphql/modules/integration.ts b/server/graphql/modules/integration.ts index 7dc2963..bd91a09 100644 --- a/server/graphql/modules/integration.ts +++ b/server/graphql/modules/integration.ts @@ -1,5 +1,3 @@ -import { AuthenticationError } from 'apollo-server-express'; - import { getIntegrationRegistry } from '../../services/integrationRegistry/index.js'; import { Integration } from '../../services/signalsService/index.js'; import { isCoopErrorOfType } from '../../utils/errors.js'; @@ -15,6 +13,7 @@ import { } from '../generated.js'; import { type ResolverMap } from '../resolvers.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` enum Integration { @@ -219,7 +218,7 @@ const Query: GQLQueryResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } if (!getIntegrationRegistry().has(name)) { @@ -251,7 +250,7 @@ const Query: GQLQueryResolvers = { async availableIntegrations(_, __, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } return context.dataSources.integrationAPI.getAvailableIntegrations() as GQLIntegrationMetadata[]; }, @@ -268,7 +267,7 @@ const Mutation: GQLMutationResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const newConfig = await context.dataSources.integrationAPI.setConfig( params.input, @@ -297,7 +296,7 @@ const Mutation: GQLMutationResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const newConfig = await context.dataSources.integrationAPI.setConfigByIntegrationId( diff --git a/server/graphql/modules/investigation.ts b/server/graphql/modules/investigation.ts index 724ccd9..5c12fc1 100644 --- a/server/graphql/modules/investigation.ts +++ b/server/graphql/modules/investigation.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import { type DateString } from '@roostorg/types'; -import { AuthenticationError } from 'apollo-server-express'; + import _ from 'lodash'; import { type ConditionSetWithResult } from '../../models/rules/RuleModel.js'; @@ -25,6 +25,7 @@ import { } from '../generated.js'; import { formatItemSubmissionForGQL } from '../types.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` type Query { @@ -130,7 +131,7 @@ const UserHistory: GQLUserHistoryResolvers = { async executions(it, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const rows = @@ -151,7 +152,7 @@ const UserHistory: GQLUserHistoryResolvers = { async actions(it, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const actions = @@ -165,7 +166,7 @@ const UserHistory: GQLUserHistoryResolvers = { async submissions(it, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const submissions = @@ -182,7 +183,7 @@ const Query: GQLQueryResolvers = { async itemSubmissions(_, { itemIdentifiers }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const items = await Promise.all( @@ -205,7 +206,7 @@ const Query: GQLQueryResolvers = { async latestItemSubmissions(_, { itemIdentifiers }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const items = await Promise.all( @@ -224,7 +225,7 @@ const Query: GQLQueryResolvers = { async userHistory(_, { itemIdentifier }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } try { @@ -259,7 +260,7 @@ const Query: GQLQueryResolvers = { async itemsWithId(_, { itemId, typeId, returnFirstResultOnly }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } if (typeId) { @@ -335,7 +336,7 @@ const Query: GQLQueryResolvers = { const { id: itemId, typeId } = itemIdentifier; const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const item = @@ -378,7 +379,7 @@ const Query: GQLQueryResolvers = { async threadHistory(_, { threadIdentifier, endDate }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const threadSubmissions = await asyncIterableToArray( context.services.ItemInvestigationService.getThreadSubmissionsByTime({ @@ -415,7 +416,7 @@ const Query: GQLQueryResolvers = { ) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const items = await asyncIterableToArray( context.services.ItemInvestigationService.getItemSubmissionsByCreator({ @@ -446,7 +447,7 @@ const Query: GQLQueryResolvers = { async latestItemsCreatedByWithThread(__, { itemIdentifier }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } const items = await asyncIterableToArray( @@ -555,7 +556,7 @@ const Query: GQLQueryResolvers = { async itemActionHistory(_, { itemIdentifier, submissionTime }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Unauthenticated User'); + throw unauthenticatedError('Unauthenticated User'); } return context.services.ItemInvestigationService.getItemActionHistory({ orgId: user.orgId, diff --git a/server/graphql/modules/itemType.ts b/server/graphql/modules/itemType.ts index daaeddb..3ceb78d 100644 --- a/server/graphql/modules/itemType.ts +++ b/server/graphql/modules/itemType.ts @@ -7,7 +7,6 @@ import { type FieldType, type ScalarType, } from '@roostorg/types'; -import { AuthenticationError } from 'apollo-server-core'; import { type ContentItemType as ContentItemTypeT, @@ -37,6 +36,7 @@ import { import { type Context } from '../resolvers.js'; import { formatItemSubmissionForGQL } from '../types.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; export type ItemTypeResolversParentType = ItemTypeT | ItemTypeSelector; export type ThreadItemTypeResolversParentType = @@ -533,7 +533,7 @@ const UserItem: GQLUserItemResolvers = { async userScore(userItem, __, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id, type } = userItem; @@ -551,7 +551,7 @@ const itemTypeBaseFieldResolvers: Omit< async baseFields(it, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const itemType = await getItemTypeFromItemTypeOrSelector(it, context); return itemType.schema; @@ -559,7 +559,7 @@ const itemTypeBaseFieldResolvers: Omit< async derivedFields(itemTypeOrSelector, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const itemType = await getItemTypeFromItemTypeOrSelector( itemTypeOrSelector, @@ -574,7 +574,7 @@ const itemTypeBaseFieldResolvers: Omit< async name(itemTypeOrSelector, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const itemType = await getItemTypeFromItemTypeOrSelector( itemTypeOrSelector, @@ -585,7 +585,7 @@ const itemTypeBaseFieldResolvers: Omit< async description(itemTypeOrSelector, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const itemType = await getItemTypeFromItemTypeOrSelector( itemTypeOrSelector, @@ -596,7 +596,7 @@ const itemTypeBaseFieldResolvers: Omit< async version(itemTypeOrSelector, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const itemType = await getItemTypeFromItemTypeOrSelector( itemTypeOrSelector, @@ -607,7 +607,7 @@ const itemTypeBaseFieldResolvers: Omit< async schemaVariant(itemTypeOrSelector, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const itemType = await getItemTypeFromItemTypeOrSelector( itemTypeOrSelector, @@ -618,7 +618,7 @@ const itemTypeBaseFieldResolvers: Omit< async hiddenFields(itemTypeOrSelector, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ManualReviewToolService.getHiddenFieldsForItemType({ @@ -656,7 +656,7 @@ const UserItemType: GQLUserItemTypeResolvers = { async isDefaultUserType(itemTypeOrSelector, _, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const itemType = await getItemTypeFromItemTypeOrSelector( itemTypeOrSelector, @@ -675,7 +675,7 @@ const Query: GQLQueryResolvers = { async itemType(_, { id, version }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return ( @@ -691,7 +691,7 @@ const Query: GQLQueryResolvers = { async itemTypes(_, { identifiers }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return filterNullOrUndefined( @@ -713,7 +713,7 @@ const Query: GQLQueryResolvers = { try { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const partialItemSubmissions = @@ -746,7 +746,7 @@ const Mutation: GQLMutationResolvers = { async createContentItemType(__, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { fields, hiddenFields, fieldRoles } = params.input; @@ -784,7 +784,7 @@ const Mutation: GQLMutationResolvers = { async updateContentItemType(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id, name, description, fields, hiddenFields, fieldRoles } = @@ -830,7 +830,7 @@ const Mutation: GQLMutationResolvers = { async createThreadItemType(__, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { fields, hiddenFields, fieldRoles } = params.input; @@ -866,7 +866,7 @@ const Mutation: GQLMutationResolvers = { async updateThreadItemType(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id, name, description, fields, hiddenFields, fieldRoles } = @@ -910,7 +910,7 @@ const Mutation: GQLMutationResolvers = { async createUserItemType(__, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { fields, hiddenFields, fieldRoles } = params.input; @@ -949,7 +949,7 @@ const Mutation: GQLMutationResolvers = { async updateUserItemType(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id, name, description, fields, hiddenFields, fieldRoles } = @@ -991,7 +991,7 @@ const Mutation: GQLMutationResolvers = { async deleteItemType(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id } = params; @@ -1026,7 +1026,7 @@ export const getItemTypeFromItemTypeOrSelector = async ( ) => { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } if ('name' in itemTypeOrSelector) { return itemTypeOrSelector; diff --git a/server/graphql/modules/locationBank.ts b/server/graphql/modules/locationBank.ts index 69fa06f..bb5a9a0 100644 --- a/server/graphql/modules/locationBank.ts +++ b/server/graphql/modules/locationBank.ts @@ -1,5 +1,3 @@ -import { AuthenticationError } from 'apollo-server-express'; - import { type LocationBank as TLocationBank } from '../../models/banks/LocationBankModel.js'; import { isCoopErrorOfType } from '../../utils/errors.js'; import { @@ -8,6 +6,7 @@ import { } from '../generated.js'; import { type ResolverMap } from '../resolvers.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` type Query { @@ -132,7 +131,7 @@ const Query: GQLQueryResolvers = { async locationBank(_, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.locationBankAPI.getGraphQLLocationBankFromId({ @@ -147,7 +146,7 @@ const Mutation: GQLMutationResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const bank = await context.dataSources.locationBankAPI.createLocationBank( @@ -170,7 +169,7 @@ const Mutation: GQLMutationResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const bank = context.dataSources.locationBankAPI.updateLocationBank( @@ -192,7 +191,7 @@ const Mutation: GQLMutationResolvers = { async deleteLocationBank(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.locationBankAPI.deleteLocationBank({ diff --git a/server/graphql/modules/manualReviewTool.ts b/server/graphql/modules/manualReviewTool.ts index e9bb479..2b4619a 100644 --- a/server/graphql/modules/manualReviewTool.ts +++ b/server/graphql/modules/manualReviewTool.ts @@ -1,5 +1,4 @@ /* eslint-disable max-lines */ -import { AuthenticationError } from 'apollo-server-core'; import _ from 'lodash'; import { @@ -41,6 +40,7 @@ import { import { formatItemSubmissionForGQL } from '../types.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; import { oneOfInputToTaggedUnion } from '../utils/inputHelpers.js'; +import { forbiddenError, unauthenticatedError } from '../utils/errors.js'; const { omit, sumBy } = _; @@ -998,7 +998,6 @@ const ManualReviewJobPayload: GQLManualReviewJobPayloadResolvers = { }, }; - const ContentManualReviewJobPayload: GQLContentManualReviewJobPayloadResolvers = { async item(it, _, context) { @@ -1668,7 +1667,7 @@ const ManualReviewQueue: GQLManualReviewQueueResolvers = { async explicitlyAssignedReviewers(queue, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id: userId, orgId } = user; @@ -1684,7 +1683,7 @@ const ManualReviewQueue: GQLManualReviewQueueResolvers = { async hiddenActionIds(queue, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { orgId } = user; const { id: queueId } = queue; @@ -1746,7 +1745,7 @@ const Query: GQLQueryResolvers = { async getDecisionCounts(_: unknown, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.services.ManualReviewToolService.getDecisionCounts({ ...input, @@ -1766,7 +1765,7 @@ const Query: GQLQueryResolvers = { async getJobCreationCounts(_: unknown, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const result = await context.services.ManualReviewToolService.getJobCreationCounts({ @@ -1794,7 +1793,7 @@ const Query: GQLQueryResolvers = { async getResolvedJobCounts(_: unknown, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const result = await context.services.ManualReviewToolService.getResolvedJobCounts({ @@ -1819,7 +1818,7 @@ const Query: GQLQueryResolvers = { async getSkippedJobCounts(_: unknown, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const result = await context.services.ManualReviewToolService.getSkippedJobCounts({ @@ -1845,7 +1844,7 @@ const Query: GQLQueryResolvers = { async getResolvedJobsForUser(_: unknown, { timeZone }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const counts = @@ -1871,7 +1870,7 @@ const Query: GQLQueryResolvers = { async getSkippedJobsForUser(_: unknown, { timeZone }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const counts = @@ -1897,7 +1896,7 @@ const Query: GQLQueryResolvers = { async getTimeToAction(_: unknown, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const result = await context.services.ManualReviewToolService.getDecisionTimeToAction({ @@ -1917,7 +1916,7 @@ const Query: GQLQueryResolvers = { async getTotalPendingJobsCount(_: unknown, __: unknown, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const allQueues = await context.services.ManualReviewToolService.getAllQueuesForOrgAndDangerouslyBypassPermissioning( @@ -1933,7 +1932,7 @@ const Query: GQLQueryResolvers = { async getRecentDecisions(_: unknown, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const permissions = user.getPermissions(); const { filter, page } = input; @@ -1982,7 +1981,7 @@ const Query: GQLQueryResolvers = { async getDecidedJob(_: unknown, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.services.ManualReviewToolService.getDecidedJob({ @@ -1993,7 +1992,7 @@ const Query: GQLQueryResolvers = { async getDecidedJobFromJobId(_: unknown, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.services.ManualReviewToolService.getDecidedJobFromJobId({ @@ -2009,7 +2008,7 @@ const Query: GQLQueryResolvers = { ) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const queue = @@ -2021,7 +2020,7 @@ const Query: GQLQueryResolvers = { async getCommentsForJob(_: unknown, { jobId }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ManualReviewToolService.getJobComments({ orgId: user.orgId, @@ -2031,7 +2030,7 @@ const Query: GQLQueryResolvers = { async getExistingJobsForItem(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.services.ManualReviewToolService.getExistingJobsForItem({ @@ -2043,7 +2042,7 @@ const Query: GQLQueryResolvers = { async getDecisionsTable(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.services.ManualReviewToolService.getDecisionCountsTable({ @@ -2060,7 +2059,7 @@ const Query: GQLQueryResolvers = { async getSkipsForRecentDecisions(_, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const filter = input.filter; @@ -2108,7 +2107,7 @@ const Mutation: GQLMutationResolvers = { async dequeueManualReviewJob(_, { queueId }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id: userId, orgId } = user; @@ -2135,7 +2134,7 @@ const Mutation: GQLMutationResolvers = { async submitManualReviewDecision(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id: userId, orgId, email: userEmail } = user; @@ -2237,7 +2236,7 @@ const Mutation: GQLMutationResolvers = { async createManualReviewQueue(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { @@ -2279,11 +2278,11 @@ const Mutation: GQLMutationResolvers = { }, async updateManualReviewQueue(_, params, context) { const user = context.getUser(); - if ( - user == null || - !user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES) - ) { - throw new AuthenticationError('Authenticated user required'); + if (user == null) { + throw unauthenticatedError('Authenticated user required'); + } + if (!user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES)) { + throw forbiddenError('User does not have permission to edit MRT queues'); } const { @@ -2323,11 +2322,11 @@ const Mutation: GQLMutationResolvers = { }, async deleteManualReviewQueue(_, params, context) { const user = context.getUser(); - if ( - user == null || - !user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES) - ) { - throw new AuthenticationError('Authenticated user required'); + if (user == null) { + throw unauthenticatedError('Authenticated user required'); + } + if (!user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES)) { + throw forbiddenError('User does not have permission to edit MRT queues'); } return context.services.ManualReviewToolService.deleteManualReviewQueue( @@ -2337,11 +2336,11 @@ const Mutation: GQLMutationResolvers = { }, async addAccessibleQueuesToUser(_, params, context) { const user = context.getUser(); - if ( - user == null || - !user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES) - ) { - throw new AuthenticationError('Authenticated user required'); + if (user == null) { + throw unauthenticatedError('Authenticated user required'); + } + if (!user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES)) { + throw forbiddenError('User does not have permission to edit MRT queues'); } await context.services.ManualReviewToolService.addAccessibleQueuesForUser( @@ -2357,11 +2356,11 @@ const Mutation: GQLMutationResolvers = { }, async removeAccessibleQueuesToUser(_, params, context) { const user = context.getUser(); - if ( - user == null || - !user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES) - ) { - throw new AuthenticationError('Authenticated user required'); + if (user == null) { + throw unauthenticatedError('Authenticated user required'); + } + if (!user.getPermissions().includes(UserPermission.EDIT_MRT_QUEUES)) { + throw forbiddenError('User does not have permission to edit MRT queues'); } await context.services.ManualReviewToolService.removeAccessibleQueuesForUser( @@ -2381,7 +2380,7 @@ const Mutation: GQLMutationResolvers = { // that are being deleted const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { await context.services.ManualReviewToolService.deleteAllJobsFromQueue({ @@ -2404,7 +2403,7 @@ const Mutation: GQLMutationResolvers = { async createManualReviewJobComment(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { @@ -2432,7 +2431,7 @@ const Mutation: GQLMutationResolvers = { async deleteManualReviewJobComment(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { id: userId, orgId } = user; @@ -2452,7 +2451,7 @@ const Mutation: GQLMutationResolvers = { async logSkip(_, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { await context.services.ManualReviewToolService.logSkip({ @@ -2469,7 +2468,7 @@ const Mutation: GQLMutationResolvers = { async releaseJobLock(_, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { await context.services.ManualReviewToolService.releaseJobLock({ diff --git a/server/graphql/modules/ncmec.ts b/server/graphql/modules/ncmec.ts index bc17bdd..7307a93 100644 --- a/server/graphql/modules/ncmec.ts +++ b/server/graphql/modules/ncmec.ts @@ -1,12 +1,10 @@ -import { AuthenticationError } from 'apollo-server-core'; -import { UserInputError } from 'apollo-server-express'; - import { formatItemSubmissionForGQL } from '../../graphql/types.js'; import type { GQLMutationResolvers, GQLNcmecOrgSettings, GQLQueryResolvers, } from '../generated.js'; +import { unauthenticatedError, userInputError } from '../utils/errors.js'; /** Input shape for updateNcmecOrgSettings; matches NcmecOrgSettingsInput in schema (used so resolver type-checks even if generated types are stale). */ type NcmecOrgSettingsInputShape = { @@ -215,7 +213,7 @@ const Query: GQLQueryResolvers = { async ncmecReportById(_, { reportId }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const report = await context.services.NcmecService.getNcmecReportById({ orgId: user.orgId, @@ -248,7 +246,7 @@ const Query: GQLQueryResolvers = { async ncmecThreads(_, { userId, reportedMessages }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const threads = await context.services.NcmecService.getNcmecMessages( user.orgId, @@ -267,7 +265,7 @@ const Query: GQLQueryResolvers = { async ncmecOrgSettings(_, __, context): Promise { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const settings = await context.services.NcmecService.getNcmecOrgSettings( user.orgId, @@ -280,7 +278,7 @@ const Mutation: GQLMutationResolvers = { async updateNcmecOrgSettings(_, { input: rawInput }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const input = rawInput as NcmecOrgSettingsInputShape; const defaultInternetDetailType = @@ -292,9 +290,7 @@ const Mutation: GQLMutationResolvers = { trimmed !== '' && !VALID_NCMEC_INTERNET_DETAIL_TYPES.includes(trimmed) ) { - throw new UserInputError( - `defaultInternetDetailType must be one of: ${VALID_NCMEC_INTERNET_DETAIL_TYPES.join(', ')}`, - ); + throw userInputError(`defaultInternetDetailType must be one of: ${VALID_NCMEC_INTERNET_DETAIL_TYPES.join(', ')}`); } return trimmed === '' ? null : trimmed; })(); diff --git a/server/graphql/modules/org.ts b/server/graphql/modules/org.ts index 196e96e..4c89a5a 100644 --- a/server/graphql/modules/org.ts +++ b/server/graphql/modules/org.ts @@ -1,5 +1,4 @@ /* eslint-disable max-lines */ -import { AuthenticationError } from 'apollo-server-express'; import { isCoopErrorOfType } from '../../utils/errors.js'; import { __throw } from '../../utils/misc.js'; @@ -11,7 +10,9 @@ import { type GQLPendingInvite, type GQLQueryResolvers, } from '../generated.js'; +import { GraphQLError } from 'graphql'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { forbiddenError, unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` type Org { @@ -172,7 +173,7 @@ const Query: GQLQueryResolvers = { async org(_, { id }, context) { const user = context.getUser(); if (user == null || user.orgId !== id) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.orgAPI.getGraphQLOrgFromId(id); @@ -186,7 +187,7 @@ const Query: GQLQueryResolvers = { async appealSettings(_, __, context) { const user = context.getUser(); if (user == null || !user.orgId) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const settings = await context.services.OrgSettingsService.getAppealSettings(user.orgId); @@ -202,7 +203,7 @@ const Org: GQLOrgResolvers = { async actions(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return org.getActions(); @@ -210,14 +211,14 @@ const Org: GQLOrgResolvers = { async contentTypes(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return org.getContentTypes(); }, async itemTypes(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getItemTypes({ orgId: org.id, @@ -226,20 +227,18 @@ const Org: GQLOrgResolvers = { async users(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return org.getUsers(); }, async pendingInvites(org, _, context): Promise { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to view pending invites', - ); + throw forbiddenError('User does not have permission to view pending invites'); } const invites = @@ -249,14 +248,14 @@ const Org: GQLOrgResolvers = { async rules(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return org.getRules(); }, async routingRules(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } return context.services.ManualReviewToolService.getRoutingRules({ @@ -269,7 +268,7 @@ const Org: GQLOrgResolvers = { async appealsRoutingRules(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } return context.services.ManualReviewToolService.getAppealsRoutingRules({ @@ -282,7 +281,7 @@ const Org: GQLOrgResolvers = { async reportingRules(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } return context.services.ReportingService.getReportingRules({ @@ -295,7 +294,7 @@ const Org: GQLOrgResolvers = { async mrtQueues(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } return context.services.ManualReviewToolService.getAllQueuesForOrgAndDangerouslyBypassPermissioning( { @@ -306,7 +305,7 @@ const Org: GQLOrgResolvers = { async apiKey(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const apiKey = await context.dataSources.orgAPI.getActivatedApiKeyForOrg( org.id, @@ -317,7 +316,7 @@ const Org: GQLOrgResolvers = { if (!apiKey) { return process.env.NODE_ENV !== 'production' ? '' - : __throw(new AuthenticationError('API Key not found')); + : __throw(new GraphQLError('API Key not found')); } return apiKey.key; @@ -325,7 +324,7 @@ const Org: GQLOrgResolvers = { async integrationConfigs(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.dataSources.integrationAPI.getAllIntegrationConfigs( @@ -336,7 +335,7 @@ const Org: GQLOrgResolvers = { async signals(org, { customOnly }, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return customOnly @@ -346,7 +345,7 @@ const Org: GQLOrgResolvers = { async userStrikeThresholds(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getUserStrikeThresholdsForOrg( @@ -356,7 +355,7 @@ const Org: GQLOrgResolvers = { async policies(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getPolicies({ @@ -367,49 +366,49 @@ const Org: GQLOrgResolvers = { async banks(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return org; }, async hasReportingRulesEnabled(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.OrgSettingsService.hasReportingRulesEnabled(org.id); }, async hasAppealsEnabled(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.OrgSettingsService.hasAppealsEnabled(org.id); }, async userStrikeTTL(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.OrgSettingsService.userStrikeTTLInDays(org.id); }, async publicSigningKey(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.dataSources.orgAPI.getPublicSigningKeyPem(org.id); }, async hasNCMECReportingEnabled(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.NcmecService.hasNCMECReportingEnabled(org.id); }, async ncmecReports(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const reports = await context.services.NcmecService.getNcmecReports({ orgId: user.orgId, @@ -489,13 +488,11 @@ const Org: GQLOrgResolvers = { async ssoUrl(org, _, context) { const user = context.getUser(); if (user == null || user.orgId !== org.id) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to manage SSO settings', - ); + throw forbiddenError('User does not have permission to manage SSO settings'); } const settings = await context.services.OrgSettingsService.getSamlSettings( @@ -511,13 +508,11 @@ const Org: GQLOrgResolvers = { async ssoCert(org, _, context) { const user = context.getUser(); if (user == null || user.orgId !== org.id) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to manage SSO settings', - ); + throw forbiddenError('User does not have permission to manage SSO settings'); } const settings = await context.services.OrgSettingsService.getSamlSettings( @@ -543,7 +538,7 @@ const MatchingBanks: GQLMatchingBanksResolvers = { async textBanks(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ModerationConfigService.getTextBanks({ orgId: org.id, @@ -552,7 +547,7 @@ const MatchingBanks: GQLMatchingBanksResolvers = { async locationBanks(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.dataSources.locationBankAPI.getGraphQLLocationBanksForOrg( org.id, @@ -561,7 +556,7 @@ const MatchingBanks: GQLMatchingBanksResolvers = { async hashBanks(org, _, context) { const user = context.getUser(); if (!user || user.orgId !== org.id) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.HMAHashBankService.listBanks(org.id); }, @@ -588,7 +583,7 @@ const Mutation: GQLMutationResolvers = { async updateAppealSettings(_, { input }, context) { const user = context.getUser(); if (!user || !user.orgId) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const settings = await context.services.OrgSettingsService.updateAppealSettings({ @@ -603,7 +598,7 @@ const Mutation: GQLMutationResolvers = { async setOrgDefaultSafetySettings(_, params, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.UserManagementService.upsertOrgDefaultUserInterfaceSettings( { @@ -617,7 +612,7 @@ const Mutation: GQLMutationResolvers = { async setAllUserStrikeThresholds(_, params, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.ModerationConfigService.setAllUserStrikeThresholds({ @@ -630,7 +625,7 @@ const Mutation: GQLMutationResolvers = { async updateUserStrikeTTL(_, { input }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.OrgSettingsService.updateUserStrikeTTL({ orgId: user.orgId, @@ -641,7 +636,7 @@ const Mutation: GQLMutationResolvers = { async updateSSOCredentials(_, { input }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.OrgSettingsService.updateSamlSettings({ @@ -653,13 +648,11 @@ const Mutation: GQLMutationResolvers = { async updateOrgInfo(_, { input }, context) { const user = context.getUser(); if (!user) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to manage org info', - ); + throw forbiddenError('User does not have permission to manage org info'); } await context.dataSources.orgAPI.updateOrgInfo(user.orgId, input); diff --git a/server/graphql/modules/policy.ts b/server/graphql/modules/policy.ts index 5441ee8..05b939a 100644 --- a/server/graphql/modules/policy.ts +++ b/server/graphql/modules/policy.ts @@ -1,4 +1,3 @@ -import { AuthenticationError } from 'apollo-server-core'; import _ from 'lodash'; import { type Policy } from '../../models/PolicyModel.js'; @@ -11,6 +10,7 @@ import { type GQLUpdatePolicyResponseResolvers, } from '../generated.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const { partition } = _; @@ -101,7 +101,7 @@ const Query: GQLQueryResolvers = { async policy(_: unknown, { id }: GQLQueryPolicyArgs, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.services.ModerationConfigService.getPolicy({ @@ -115,7 +115,7 @@ const Mutation: GQLMutationResolvers = { async addPolicies(_: unknown, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required.'); + throw unauthenticatedError('Authenticated user required.'); } const { policies } = params; @@ -160,7 +160,7 @@ const Mutation: GQLMutationResolvers = { async updatePolicy(_: unknown, { input }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required.'); + throw unauthenticatedError('Authenticated user required.'); } try { const { @@ -206,7 +206,7 @@ const Mutation: GQLMutationResolvers = { async deletePolicy(_: unknown, params: GQLMutationDeletePolicyArgs, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.services.ModerationConfigService.deletePolicy({ diff --git a/server/graphql/modules/reportingRule.ts b/server/graphql/modules/reportingRule.ts index 8c6b5a0..b210eb9 100644 --- a/server/graphql/modules/reportingRule.ts +++ b/server/graphql/modules/reportingRule.ts @@ -1,5 +1,3 @@ -import { AuthenticationError } from 'apollo-server-core'; - import { isCoopErrorOfType } from '../../utils/errors.js'; import { isNonEmptyArray, @@ -14,6 +12,7 @@ import { type GQLReportingRuleResolvers, } from '../generated.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` enum ReportingRuleStatus { @@ -99,7 +98,7 @@ const ReportingRule: GQLReportingRuleResolvers = { async creator(reportingRule, _, { dataSources, getUser }) { const user = getUser(); if (!user || user.orgId !== reportingRule.orgId) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } if (!reportingRule.creatorId) { return null; @@ -114,7 +113,7 @@ const ReportingRule: GQLReportingRuleResolvers = { async itemTypes(reportingRule, _, { services, getUser }) { const user = getUser(); if (!user || user.orgId !== reportingRule.orgId) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } const itemTypes = await services.ModerationConfigService.getItemTypes({ @@ -128,7 +127,7 @@ const ReportingRule: GQLReportingRuleResolvers = { async actions(reportingRule, _, { dataSources, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return dataSources.actionAPI.getGraphQLActionsFromIds( @@ -139,7 +138,7 @@ const ReportingRule: GQLReportingRuleResolvers = { async policies(reportingRule, _, { services, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { orgId } = user; @@ -159,7 +158,7 @@ const ReportingRule: GQLReportingRuleResolvers = { // insights resolver. But verify the rule is owned by the user's org const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } if (rule.orgId !== user.orgId) { @@ -174,7 +173,7 @@ const Query: GQLQueryResolvers = { async reportingRule(_, { id }, { services, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const reportingRules = await services.ReportingService.getReportingRules({ @@ -189,7 +188,7 @@ const Mutation: GQLMutationResolvers = { async createReportingRule(_, { input }, { services, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { itemTypeIds, actionIds } = input; @@ -228,7 +227,7 @@ const Mutation: GQLMutationResolvers = { async updateReportingRule(_, { input }, { services, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { id, name, description, status, conditionSet, policyIds } = input; @@ -283,7 +282,7 @@ const Mutation: GQLMutationResolvers = { async deleteReportingRule(_, { id }, { services, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return services.ReportingService.deleteReportingRule({ diff --git a/server/graphql/modules/retroaction.ts b/server/graphql/modules/retroaction.ts index 052fec8..efea608 100644 --- a/server/graphql/modules/retroaction.ts +++ b/server/graphql/modules/retroaction.ts @@ -1,11 +1,10 @@ -import { AuthenticationError, ForbiddenError } from 'apollo-server-express'; - import { hasPermission, UserPermission, } from '../../models/types/permissioning.js'; import { type GQLMutationRunRetroactionArgs } from '../generated.js'; import { type Context } from '../resolvers.js'; +import { forbiddenError, unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` type Mutation { @@ -46,11 +45,11 @@ const resolvers = { ); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } else if (!hasPermission(UserPermission.RUN_RETROACTION, user.role)) { - throw new ForbiddenError('User not authorized to create backtests.'); + throw forbiddenError('User not authorized to create backtests.'); } else if (!rule || user.orgId !== rule.orgId) { - throw new ForbiddenError('Invalid rule.'); + throw forbiddenError('Invalid rule.'); } return context.dataSources.ruleAPI.runRetroaction(params.input, user); diff --git a/server/graphql/modules/routingRule.ts b/server/graphql/modules/routingRule.ts index 8e2e389..fae19cf 100644 --- a/server/graphql/modules/routingRule.ts +++ b/server/graphql/modules/routingRule.ts @@ -1,5 +1,3 @@ -import { AuthenticationError } from 'apollo-server-express'; - import { hasPermission, UserPermission, @@ -18,6 +16,7 @@ import { type GQLRoutingRuleResolvers, } from '../generated.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` type RoutingRule { @@ -125,7 +124,7 @@ const RoutingRule: GQLRoutingRuleResolvers = { async destinationQueue(routingRule, _, context) { const user = context.getUser(); if (!user || user.orgId !== routingRule.orgId) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } const userCanEditMRTQueues = hasPermission( @@ -155,7 +154,7 @@ const RoutingRule: GQLRoutingRuleResolvers = { async itemTypes(routingRule, _, context) { const user = context.getUser(); if (!user || user.orgId !== routingRule.orgId) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } const itemTypes = @@ -177,7 +176,7 @@ const Mutation: GQLMutationResolvers = { const { itemTypeIds } = params.input; if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } if (!itemTypeIdsAreValid(itemTypeIds)) { @@ -216,7 +215,7 @@ const Mutation: GQLMutationResolvers = { const user = context.getUser(); const { itemTypeIds } = params.input; if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } if (itemTypeIds && !itemTypeIdsAreValid(itemTypeIds)) { @@ -261,7 +260,7 @@ const Mutation: GQLMutationResolvers = { async deleteRoutingRule(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } return context.services.ManualReviewToolService.deleteRoutingRule({ @@ -272,7 +271,7 @@ const Mutation: GQLMutationResolvers = { async reorderRoutingRules(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { order } = params.input; diff --git a/server/graphql/modules/rule.ts b/server/graphql/modules/rule.ts index 15a6e5a..ac5d62f 100644 --- a/server/graphql/modules/rule.ts +++ b/server/graphql/modules/rule.ts @@ -1,5 +1,4 @@ /* eslint-disable max-lines */ -import { AuthenticationError } from 'apollo-server-express'; import { isConditionSet } from '../../condition_evaluator/condition.js'; import { @@ -28,6 +27,7 @@ import { } from '../generated.js'; import { type Context, type ResolverMap } from '../resolvers.js'; import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` type Query { @@ -437,8 +437,6 @@ const typeDefs = /* GraphQL */ ` type: AggregationType! } - - type RuleHasRunningBacktestsError implements Error { title: String! status: Int! @@ -481,7 +479,7 @@ const Query: GQLQueryResolvers = { async rule(_, { id }, { dataSources, getUser }) { const user = await getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return dataSources.ruleAPI.getGraphQLRuleFromId(id, user.orgId); @@ -492,7 +490,7 @@ const Mutation: GQLMutationResolvers = { async createContentRule(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { @@ -518,7 +516,7 @@ const Mutation: GQLMutationResolvers = { try { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const rule = await context.dataSources.ruleAPI.updateContentRule({ @@ -548,7 +546,7 @@ const Mutation: GQLMutationResolvers = { async createUserRule(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { @@ -570,7 +568,7 @@ const Mutation: GQLMutationResolvers = { async updateUserRule(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { @@ -598,7 +596,7 @@ const Mutation: GQLMutationResolvers = { async deleteRule(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.ruleAPI.deleteRule({ @@ -612,7 +610,7 @@ const ConditionInputField: ResolverMap = { async name(conditionInputField, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (conditionInputField.type !== 'CONTENT_DERIVED_FIELD') { @@ -640,7 +638,7 @@ const ContentRule: GQLContentRuleResolvers = { async creator(rule, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return rule.getCreator(); @@ -648,7 +646,7 @@ const ContentRule: GQLContentRuleResolvers = { async itemTypes(rule, _, { services, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return services.ModerationConfigService.getItemTypesForRule({ orgId: user.orgId, @@ -658,7 +656,7 @@ const ContentRule: GQLContentRuleResolvers = { async actions(rule, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return rule.getActions(); @@ -666,7 +664,7 @@ const ContentRule: GQLContentRuleResolvers = { async policies(rule, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return rule.getPolicies(); @@ -674,7 +672,7 @@ const ContentRule: GQLContentRuleResolvers = { async backtests(rule, args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { ids } = args; @@ -685,7 +683,7 @@ const ContentRule: GQLContentRuleResolvers = { // insights resolver. But verify the rule is owned by the user's org const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required'); + throw unauthenticatedError('User required'); } if (rule.orgId !== user.orgId) { @@ -700,7 +698,7 @@ const UserRule: GQLUserRuleResolvers = { async creator(rule, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return rule.getCreator(); @@ -708,7 +706,7 @@ const UserRule: GQLUserRuleResolvers = { async actions(rule, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return rule.getActions(); @@ -716,7 +714,7 @@ const UserRule: GQLUserRuleResolvers = { async policies(rule, _, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return rule.getPolicies(); @@ -724,7 +722,7 @@ const UserRule: GQLUserRuleResolvers = { async backtests(rule, args, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const { ids } = args; @@ -735,7 +733,7 @@ const UserRule: GQLUserRuleResolvers = { // insights resolver. But verify the rule is owned by the user's org const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (rule.orgId !== user.orgId) { diff --git a/server/graphql/modules/signal.ts b/server/graphql/modules/signal.ts index 3d552fb..11d2b98 100644 --- a/server/graphql/modules/signal.ts +++ b/server/graphql/modules/signal.ts @@ -1,5 +1,5 @@ import { type SignalSubcategory } from '@roostorg/types'; -import { AuthenticationError } from 'apollo-server-express'; + import { type ReadonlyDeep } from 'type-fest'; import { getIntegrationRegistry } from '../../services/integrationRegistry/index.js'; @@ -16,6 +16,7 @@ import { type GQLSupportedLanguagesResolvers, } from '../generated.js'; import { type ResolverMap } from '../resolvers.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` enum SignalPricingStructureType { @@ -228,9 +229,7 @@ const Signal: GQLSignalResolvers = { const user = context.getUser(); if (!user) { - throw new AuthenticationError( - 'User required to load signal disabledInfo.', - ); + throw unauthenticatedError('User required to load signal disabledInfo.'); } // Non-null assertion below is safe because, if this resolver ran, it means diff --git a/server/graphql/modules/spotTest.ts b/server/graphql/modules/spotTest.ts index 63b6463..1ad18eb 100644 --- a/server/graphql/modules/spotTest.ts +++ b/server/graphql/modules/spotTest.ts @@ -1,10 +1,10 @@ -import { AuthenticationError } from 'apollo-server-core'; import { uid } from 'uid'; import { RuleEnvironment } from '../../rule_engine/RuleEngine.js'; import { rawItemSubmissionToItemSubmission } from '../../services/itemProcessingService/index.js'; import { jsonStringify } from '../../utils/encoding.js'; import type { GQLQueryResolvers } from '../generated.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` extend type Query { @@ -21,7 +21,7 @@ const Query: GQLQueryResolvers = { async spotTestRule(_, { ruleId, item }, { services, dataSources, getUser }) { const user = getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const [itemTypes, rule] = await Promise.all([ diff --git a/server/graphql/modules/textBank.ts b/server/graphql/modules/textBank.ts index e1ffd57..0c683e3 100644 --- a/server/graphql/modules/textBank.ts +++ b/server/graphql/modules/textBank.ts @@ -1,10 +1,9 @@ -import { AuthenticationError } from 'apollo-server-express'; - import { isCoopErrorOfType } from '../../utils/errors.js'; import { type GQLMutationResolvers, type GQLQueryResolvers, } from '../generated.js'; +import { unauthenticatedError } from '../utils/errors.js'; const typeDefs = /* GraphQL */ ` enum TextBankType { @@ -55,7 +54,7 @@ const Query: GQLQueryResolvers = { async textBank(_, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { @@ -79,7 +78,7 @@ const Mutation: GQLMutationResolvers = { async createTextBank(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { name, description, type, strings } = params.input; @@ -107,7 +106,7 @@ const Mutation: GQLMutationResolvers = { async updateTextBank(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { id, name, description, type, strings } = params.input; @@ -135,7 +134,7 @@ const Mutation: GQLMutationResolvers = { async deleteTextBank(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } try { diff --git a/server/graphql/modules/user.ts b/server/graphql/modules/user.ts index 8e6cb13..8a6ee09 100644 --- a/server/graphql/modules/user.ts +++ b/server/graphql/modules/user.ts @@ -1,4 +1,3 @@ -import { AuthenticationError, ForbiddenError } from 'apollo-server-express'; import jwt from 'jsonwebtoken'; import { @@ -10,6 +9,8 @@ import { } from '../generated.js'; import { gqlSuccessResult } from '../utils/gqlResult.js'; +import { forbiddenError, unauthenticatedError } from '../utils/errors.js'; + const typeDefs = /* GraphQL */ ` enum UserRole { ADMIN @@ -187,7 +188,7 @@ const Query: GQLQueryResolvers = { async user(_, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } const { orgId } = user; @@ -199,7 +200,7 @@ const Mutation: GQLMutationResolvers = { async updateAccountInfo(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } await context.dataSources.userAPI.updateAccountInfo(user, params); return true; // TODO: return the updated user instead. @@ -207,14 +208,14 @@ const Mutation: GQLMutationResolvers = { async changePassword(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.userAPI.changePassword(user, params.input); }, async deleteUser(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } return context.dataSources.userAPI.deleteUser({ @@ -225,7 +226,7 @@ const Mutation: GQLMutationResolvers = { async addFavoriteRule(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.dataSources.userAPI.addFavoriteRule( user.id, @@ -237,7 +238,7 @@ const Mutation: GQLMutationResolvers = { async removeFavoriteRule(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.dataSources.userAPI.removeFavoriteRule( user.id, @@ -249,7 +250,7 @@ const Mutation: GQLMutationResolvers = { async addFavoriteMRTQueue(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.ManualReviewToolService.addFavoriteQueueForUser({ userId: user.id, @@ -261,7 +262,7 @@ const Mutation: GQLMutationResolvers = { async removeFavoriteMRTQueue(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.ManualReviewToolService.removeFavoriteQueueForUser({ userId: user.id, @@ -273,7 +274,7 @@ const Mutation: GQLMutationResolvers = { async setModeratorSafetySettings(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.UserManagementService.upsertUserInterfaceSettings({ userId: user.id, @@ -287,7 +288,7 @@ const Mutation: GQLMutationResolvers = { async setMrtChartConfigurationSettings(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('User required.'); + throw unauthenticatedError('User required.'); } await context.services.UserManagementService.upsertUserInterfaceSettings({ userId: user.id, @@ -342,7 +343,7 @@ const User: GQLUserResolvers = { try { const authedUser = getUser(); if (!authedUser || user.id !== authedUser.id) { - throw new ForbiddenError('Must be signed in as this user to read JWT.'); + throw forbiddenError('Must be signed in as this user to read JWT.'); } const { email, firstName, lastName, orgId } = user; @@ -393,7 +394,7 @@ const User: GQLUserResolvers = { async reviewableQueues(_, { queueIds }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } const queues = diff --git a/server/graphql/resolvers.ts b/server/graphql/resolvers.ts index 25966fb..4ce59e2 100644 --- a/server/graphql/resolvers.ts +++ b/server/graphql/resolvers.ts @@ -1,5 +1,4 @@ import { mergeResolvers } from '@graphql-tools/merge'; -import { AuthenticationError } from 'apollo-server-core'; import { type GraphQLFieldResolver } from 'graphql'; import { type PassportContext } from 'graphql-passport'; @@ -39,6 +38,7 @@ import { resolvers as spotTestResolvers } from './modules/spotTest.js'; import { resolvers as textBankResolvers } from './modules/textBank.js'; import { resolvers as userResolvers } from './modules/user.js'; import { gqlErrorResult, gqlSuccessResult } from './utils/gqlResult.js'; +import { forbiddenError, unauthenticatedError } from './utils/errors.js'; // eslint-disable-next-line @typescript-eslint/no-restricted-types export type Context = PassportContext & { @@ -166,13 +166,11 @@ const Mutation: GQLMutationResolvers = { async generatePasswordResetToken(_, { userId }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to generate password reset tokens', - ); + throw forbiddenError('User does not have permission to generate password reset tokens'); } const token = @@ -184,7 +182,7 @@ const Mutation: GQLMutationResolvers = { async updateRole(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } await context.services.UserManagementService.updateUserRole({ @@ -203,13 +201,11 @@ const Mutation: GQLMutationResolvers = { async inviteUser(_, params, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to invite users', - ); + throw forbiddenError('User does not have permission to invite users'); } const token = await context.dataSources.orgAPI.inviteUser( @@ -221,13 +217,11 @@ const Mutation: GQLMutationResolvers = { async deleteInvite(_, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to delete invites', - ); + throw forbiddenError('User does not have permission to delete invites'); } return context.services.UserManagementService.deleteInvite(id, user.orgId); @@ -235,13 +229,11 @@ const Mutation: GQLMutationResolvers = { async approveUser(_, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to approve users', - ); + throw forbiddenError('User does not have permission to approve users'); } return context.dataSources.userAPI.approveUser(id, user.orgId); @@ -249,13 +241,11 @@ const Mutation: GQLMutationResolvers = { async rejectUser(_, { id }, context) { const user = context.getUser(); if (user == null) { - throw new AuthenticationError('Authenticated user required'); + throw unauthenticatedError('Authenticated user required'); } if (!user.getPermissions().includes('MANAGE_ORG')) { - throw new AuthenticationError( - 'User does not have permission to reject users', - ); + throw forbiddenError('User does not have permission to reject users'); } return context.dataSources.userAPI.rejectUser(id, user.orgId); diff --git a/server/graphql/utils/authorization.ts b/server/graphql/utils/authorization.ts index 4755294..13bbcce 100644 --- a/server/graphql/utils/authorization.ts +++ b/server/graphql/utils/authorization.ts @@ -1,5 +1,4 @@ import { getDirective } from '@graphql-tools/utils'; -import { AuthenticationError } from 'apollo-server-express'; import { defaultFieldResolver, type GraphQLFieldConfig, @@ -9,6 +8,8 @@ import { import type { Context } from '../resolvers.js'; +import { unauthenticatedError } from './errors.js'; + export function shouldSkipAuth( schema: GraphQLSchema, fieldConfig: GraphQLFieldConfig, @@ -34,7 +35,7 @@ export function authSchemaWrapper( info: GraphQLResolveInfo, ) { if (!context.getUser()) { - throw new AuthenticationError('No user in context.'); + throw unauthenticatedError('No user in context.'); } return originalResolver(source, args, context, info); }, diff --git a/server/graphql/utils/errors.ts b/server/graphql/utils/errors.ts new file mode 100644 index 0000000..7a77284 --- /dev/null +++ b/server/graphql/utils/errors.ts @@ -0,0 +1,10 @@ +import { GraphQLError } from 'graphql'; + +export const unauthenticatedError = (message: string) => + new GraphQLError(message, { extensions: { code: 'UNAUTHENTICATED' } }); + +export const forbiddenError = (message: string) => + new GraphQLError(message, { extensions: { code: 'FORBIDDEN' } }); + +export const userInputError = (message: string) => + new GraphQLError(message, { extensions: { code: 'BAD_USER_INPUT' } }); diff --git a/server/graphql/utils/paginationHandler.ts b/server/graphql/utils/paginationHandler.ts index e4babfe..b41aa5f 100644 --- a/server/graphql/utils/paginationHandler.ts +++ b/server/graphql/utils/paginationHandler.ts @@ -1,8 +1,9 @@ -import { UserInputError } from 'apollo-server-express'; import { type GraphQLFieldResolver as Resolver } from 'graphql'; import { type JSON } from '../../utils/json-schema-types.js'; +import { userInputError } from './errors.js'; + /** * A type describing the arguments a connection field receives in GraphQL. */ @@ -121,7 +122,7 @@ export function makeConnectionResolver< // https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#auto-id-error-cases const { takeFrom, pageSize } = (() => { if (first != null && last != null) { - throw new UserInputError(`Cannot specify both first and last`); + throw userInputError(`Cannot specify both first and last`); } else if (first != null) { return { takeFrom: 'start', pageSize: first } as const; } else if (last != null) { @@ -132,13 +133,11 @@ export function makeConnectionResolver< })(); if (pageSize <= 0) { - throw new UserInputError('Page size must be a positive number.'); + throw userInputError('Page size must be a positive number.'); } if (pageSize > maxPageSize) { - throw new UserInputError( - `Page size must be less than or equal to ${maxPageSize}.`, - ); + throw userInputError(`Page size must be less than or equal to ${maxPageSize}.`); } // Meanwhile, providing both a before and after cursor is also coherent @@ -147,7 +146,7 @@ export function makeConnectionResolver< // where the user explicitly provided only one cursor, but we synthesized // the other. if (before != null && after != null) { - throw new UserInputError('Combining before and after is not supported.'); + throw userInputError('Combining before and after is not supported.'); } // figure out the direction we're paginating in, diff --git a/server/iocContainer/services/gqlDataSources.ts b/server/iocContainer/services/gqlDataSources.ts index 0ba23a7..7891190 100644 --- a/server/iocContainer/services/gqlDataSources.ts +++ b/server/iocContainer/services/gqlDataSources.ts @@ -1,5 +1,4 @@ import type Bottle from '@ethanresnick/bottlejs'; -import { DataSource } from 'apollo-datasource'; import makeActionAPI, { type ActionAPI, @@ -69,7 +68,7 @@ function makeDataSources(deps: Dependencies) { orgAPI: deps.OrgAPIDataSource, ruleAPI: deps.RuleAPIDataSource, userAPI: deps.UserAPIDataSource, - notificationsAPI: new (class extends DataSource { + notificationsAPI: new (class { private service = deps.NotificationsService; public async getNotificationsForUser(id: string) { return this.service.getNotificationsForUser(id); diff --git a/server/package-lock.json b/server/package-lock.json index e66b5ff..3ab3b0d 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@apollo/server": "^5.5.0", + "@as-integrations/express4": "^1.1.2", "@aws-sdk/client-s3": "^3.1017.0", "@aws-sdk/client-secrets-manager": "^3.1017.0", "@aws-sdk/client-ses": "^3.1017.0", @@ -43,9 +45,6 @@ "@types/yargs": "^17.0.32", "ajv": "^8.18.0", "ajv-draft-04": "^1.0.0", - "apollo-datasource": "^3.3.0", - "apollo-server-core": "^3.11.1", - "apollo-server-express": "^3.10.3", "bcryptjs": "^2.4.3", "bullmq": "^5.0.0", "cassandra-driver": "^4.8.0", @@ -63,7 +62,7 @@ "formdata-node": "^6.0.3", "fuzzball": "^2.1.2", "generic-pool": "^3.8.2", - "graphql": "^16.0.1", + "graphql": "^16.13.2", "graphql-depth-limit": "^1.1.0", "graphql-passport": "^0.6.4", "graphql-scalars": "^1.19.0", @@ -105,6 +104,7 @@ "@eslint/js": "^9.39.4", "@faker-js/faker": "^7.5.0", "@types/cls-hooked": "^4.3.3", + "@types/cors": "^2.8.19", "@types/graphql-depth-limit": "^1.1.6", "@types/jest": "^29.2.4", "@types/js-yaml": "^4.0.5", @@ -143,6 +143,15 @@ "node": ">=0.10.0" } }, + "node_modules/@apollo/cache-control-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", + "integrity": "sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==", + "license": "MIT", + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, "node_modules/@apollo/protobufjs": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", @@ -167,123 +176,489 @@ "apollo-pbts": "bin/pbts" } }, - "node_modules/@apollo/usage-reporting-protobuf": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", - "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "node_modules/@apollo/server": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", + "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", + "license": "MIT", "dependencies": { - "@apollo/protobufjs": "1.2.7" + "@apollo/cache-control-types": "^1.0.3", + "@apollo/server-gateway-interface": "^2.0.0", + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.createhash": "^3.0.0", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.isnodelike": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0", + "@apollo/utils.usagereporting": "^2.1.0", + "@apollo/utils.withrequired": "^3.0.0", + "@graphql-tools/schema": "^10.0.0", + "async-retry": "^1.2.1", + "body-parser": "^2.2.2", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "finalhandler": "^2.1.0", + "loglevel": "^1.6.8", + "lru-cache": "^11.1.0", + "negotiator": "^1.0.0", + "uuid": "^11.1.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "graphql": "^16.11.0" } }, - "node_modules/@apollo/utils.dropunuseddefinitions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-1.1.0.tgz", - "integrity": "sha512-jU1XjMr6ec9pPoL+BFWzEPW7VHHulVdGKMkPAMiCigpVIT11VmCbnij0bWob8uS3ODJ65tZLYKAh/55vLw2rbg==", + "node_modules/@apollo/server-gateway-interface": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz", + "integrity": "sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==", + "license": "MIT", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server-gateway-interface/node_modules/@apollo/utils.keyvaluecache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz", + "integrity": "sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==", + "license": "MIT", + "dependencies": { + "@apollo/utils.logger": "^3.0.0", + "lru-cache": "^11.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@apollo/server-gateway-interface/node_modules/@apollo/utils.logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-3.0.0.tgz", + "integrity": "sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/server-gateway-interface/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=12.13.0" + "node": "20 || >=22" + } + }, + "node_modules/@apollo/server/node_modules/@apollo/utils.dropunuseddefinitions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz", + "integrity": "sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==", + "license": "MIT", + "engines": { + "node": ">=14" }, "peerDependencies": { "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollo/utils.keyvaluecache": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz", - "integrity": "sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg==", + "node_modules/@apollo/server/node_modules/@apollo/utils.keyvaluecache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz", + "integrity": "sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==", + "license": "MIT", "dependencies": { - "@apollo/utils.logger": "^1.0.0", - "lru-cache": "7.10.1 - 7.13.1" + "@apollo/utils.logger": "^3.0.0", + "lru-cache": "^11.0.0" + }, + "engines": { + "node": ">=20" } }, - "node_modules/@apollo/utils.logger": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-1.0.1.tgz", - "integrity": "sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA==" + "node_modules/@apollo/server/node_modules/@apollo/utils.logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-3.0.0.tgz", + "integrity": "sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==", + "license": "MIT", + "engines": { + "node": ">=16" + } }, - "node_modules/@apollo/utils.printwithreducedwhitespace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-1.1.0.tgz", - "integrity": "sha512-GfFSkAv3n1toDZ4V6u2d7L4xMwLA+lv+6hqXicMN9KELSJ9yy9RzuEXaX73c/Ry+GzRsBy/fdSUGayGqdHfT2Q==", + "node_modules/@apollo/server/node_modules/@apollo/utils.printwithreducedwhitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz", + "integrity": "sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==", + "license": "MIT", "engines": { - "node": ">=12.13.0" + "node": ">=14" }, "peerDependencies": { "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollo/utils.removealiases": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-1.0.0.tgz", - "integrity": "sha512-6cM8sEOJW2LaGjL/0vHV0GtRaSekrPQR4DiywaApQlL9EdROASZU5PsQibe2MWeZCOhNrPRuHh4wDMwPsWTn8A==", + "node_modules/@apollo/server/node_modules/@apollo/utils.removealiases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz", + "integrity": "sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==", + "license": "MIT", "engines": { - "node": ">=12.13.0" + "node": ">=14" }, "peerDependencies": { "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollo/utils.sortast": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-1.1.0.tgz", - "integrity": "sha512-VPlTsmUnOwzPK5yGZENN069y6uUHgeiSlpEhRnLFYwYNoJHsuJq2vXVwIaSmts015WTPa2fpz1inkLYByeuRQA==", + "node_modules/@apollo/server/node_modules/@apollo/utils.sortast": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz", + "integrity": "sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==", + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0" }, "engines": { - "node": ">=12.13.0" + "node": ">=14" }, "peerDependencies": { "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollo/utils.stripsensitiveliterals": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-1.2.0.tgz", - "integrity": "sha512-E41rDUzkz/cdikM5147d8nfCFVKovXxKBcjvLEQ7bjZm/cg9zEcXvS6vFY8ugTubI3fn6zoqo0CyU8zT+BGP9w==", + "node_modules/@apollo/server/node_modules/@apollo/utils.stripsensitiveliterals": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz", + "integrity": "sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==", + "license": "MIT", "engines": { - "node": ">=12.13.0" + "node": ">=14" }, "peerDependencies": { "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollo/utils.usagereporting": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-1.0.1.tgz", - "integrity": "sha512-6dk+0hZlnDbahDBB2mP/PZ5ybrtCJdLMbeNJD+TJpKyZmSY6bA3SjI8Cr2EM9QA+AdziywuWg+SgbWUF3/zQqQ==", + "node_modules/@apollo/server/node_modules/@apollo/utils.usagereporting": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz", + "integrity": "sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==", + "license": "MIT", "dependencies": { - "@apollo/usage-reporting-protobuf": "^4.0.0", - "@apollo/utils.dropunuseddefinitions": "^1.1.0", - "@apollo/utils.printwithreducedwhitespace": "^1.1.0", - "@apollo/utils.removealiases": "1.0.0", - "@apollo/utils.sortast": "^1.1.0", - "@apollo/utils.stripsensitiveliterals": "^1.2.0" + "@apollo/usage-reporting-protobuf": "^4.1.0", + "@apollo/utils.dropunuseddefinitions": "^2.0.1", + "@apollo/utils.printwithreducedwhitespace": "^2.0.1", + "@apollo/utils.removealiases": "2.0.1", + "@apollo/utils.sortast": "^2.0.1", + "@apollo/utils.stripsensitiveliterals": "^2.0.1" }, "engines": { - "node": ">=12.13.0" + "node": ">=14" }, "peerDependencies": { "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollographql/apollo-tools": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.4.tgz", - "integrity": "sha512-shM3q7rUbNyXVVRkQJQseXv6bnYM3BUma/eZhwXR4xsuM+bqWnJKvW7SAfRjP7LuSCocrexa5AXhjjawNHrIlw==", + "node_modules/@apollo/server/node_modules/@graphql-tools/merge": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.8.tgz", + "integrity": "sha512-25V7WDrODo1cPrmuUCrqf5qlMA4a/Ow4aHaqJ1MnTUaluwsV3UiqzCHWux3HSLb0H63mkoZiuOrU5xJhxRcoCg==", + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.1", + "tslib": "^2.4.0" + }, "engines": { - "node": ">=8", - "npm": ">=6" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@apollo/server/node_modules/@graphql-tools/schema": { + "version": "10.0.32", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.32.tgz", + "integrity": "sha512-kJ1Qn20MPnlaEVH37639E6rzQ1tEtr6XTUhNdR4EKydl+FijtLhWX2WLZbGnvrYuG8XUcMxsZU9mRRYYNvK02w==", + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.8", + "@graphql-tools/utils": "^11.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" }, "peerDependencies": { - "graphql": "^14.2.1 || ^15.0.0 || ^16.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@apollo/server/node_modules/@graphql-tools/utils": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.0.1.tgz", + "integrity": "sha512-pNyCOb95ab/z3zkkiPwIPYxigX7IcpyFVcgD1XACDEvg/7yGnKCESx3k/XHEeneKYx/aWKGzEh/uuf6M6Q8HOw==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@apollo/server/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@apollo/server/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@apollo/server/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@apollo/server/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@apollo/server/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@apollo/server/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@apollo/server/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@apollo/server/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@apollo/server/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@apollo/server/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@apollo/server/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@apollo/server/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@apollo/server/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@apollo/server/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" } }, - "node_modules/@apollographql/graphql-playground-html": { - "version": "1.6.29", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", - "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==", + "node_modules/@apollo/usage-reporting-protobuf": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", + "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "dependencies": { + "@apollo/protobufjs": "1.2.7" + } + }, + "node_modules/@apollo/utils.createhash": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz", + "integrity": "sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==", + "license": "MIT", "dependencies": { - "xss": "^1.0.8" + "@apollo/utils.isnodelike": "^3.0.0", + "sha.js": "^2.4.11" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.fetcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz", + "integrity": "sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.isnodelike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz", + "integrity": "sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.withrequired": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz", + "integrity": "sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@as-integrations/express4": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@as-integrations/express4/-/express4-1.1.2.tgz", + "integrity": "sha512-PGeMcwoOKdYnZ4LtsmM7aLNoel3tbK8wKnfyahdRau1qb7wLbuaXB35zg3w34Ov4bm3WJtO3yzd8Bw5jVE+aIQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@apollo/server": "^4.0.0 || ^5.0.0", + "express": "^4.0.0" } }, "node_modules/@assemblyscript/loader": { @@ -2240,42 +2615,6 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/mock": { - "version": "8.7.20", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.7.20.tgz", - "integrity": "sha512-ljcHSJWjC/ZyzpXd5cfNhPI7YljRVvabKHPzKjEs5ElxWu2cdlLGvyNYepApXDsM/OJG/2xuhGM+9GWu5gEAPQ==", - "dependencies": { - "@graphql-tools/schema": "^9.0.18", - "@graphql-tools/utils": "^9.2.1", - "fast-json-stable-stringify": "^2.1.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-tools/mock/node_modules/@graphql-tools/schema": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", - "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", - "dependencies": { - "@graphql-tools/merge": "^8.4.1", - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-tools/mock/node_modules/value-or-promise": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", - "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", - "engines": { - "node": ">=12" - } - }, "node_modules/@graphql-tools/schema": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.5.1.tgz", @@ -2974,11 +3313,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@josephg/resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", - "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -10480,14 +10814,6 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, - "node_modules/@types/accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -10589,9 +10915,14 @@ "dev": true }, "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/debug": { "version": "4.1.12", @@ -11811,6 +12142,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@xmldom/is-dom-node": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@xmldom/is-dom-node/-/is-dom-node-1.0.1.tgz", @@ -12015,212 +12358,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/apollo-datasource": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.2.tgz", - "integrity": "sha512-L5TiS8E2Hn/Yz7SSnWIVbZw0ZfEIXZCa5VUiVxD9P53JvSrf4aStvsFDlGWPvpIdCR+aly2CfoB79B9/JjKFqg==", - "deprecated": "The `apollo-datasource` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", - "dependencies": { - "@apollo/utils.keyvaluecache": "^1.0.1", - "apollo-server-env": "^4.2.1" - }, - "engines": { - "node": ">=12.0" - } - }, - "node_modules/apollo-reporting-protobuf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.4.0.tgz", - "integrity": "sha512-h0u3EbC/9RpihWOmcSsvTW2O6RXVaD/mPEjfrPkxRPTEPWqncsgOoRJw+wih4OqfH3PvTJvoEIf4LwKrUaqWog==", - "deprecated": "The `apollo-reporting-protobuf` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/usage-reporting-protobuf` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", - "dependencies": { - "@apollo/protobufjs": "1.2.6" - } - }, - "node_modules/apollo-reporting-protobuf/node_modules/@apollo/protobufjs": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.6.tgz", - "integrity": "sha512-Wqo1oSHNUj/jxmsVp4iR3I480p6qdqHikn38lKrFhfzcDJ7lwd7Ck7cHRl4JE81tWNArl77xhnG/OkZhxKBYOw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" - }, - "bin": { - "apollo-pbjs": "bin/pbjs", - "apollo-pbts": "bin/pbts" - } - }, - "node_modules/apollo-reporting-protobuf/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" - }, - "node_modules/apollo-server-core": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.13.0.tgz", - "integrity": "sha512-v/g6DR6KuHn9DYSdtQijz8dLOkP78I5JSVJzPkARhDbhpH74QNwrQ2PP2URAPPEDJ2EeZNQDX8PvbYkAKqg+kg==", - "dependencies": { - "@apollo/utils.keyvaluecache": "^1.0.1", - "@apollo/utils.logger": "^1.0.0", - "@apollo/utils.usagereporting": "^1.0.0", - "@apollographql/apollo-tools": "^0.5.3", - "@apollographql/graphql-playground-html": "1.6.29", - "@graphql-tools/mock": "^8.1.2", - "@graphql-tools/schema": "^8.0.0", - "@josephg/resolvable": "^1.0.0", - "apollo-datasource": "^3.3.2", - "apollo-reporting-protobuf": "^3.4.0", - "apollo-server-env": "^4.2.1", - "apollo-server-errors": "^3.3.1", - "apollo-server-plugin-base": "^3.7.2", - "apollo-server-types": "^3.8.0", - "async-retry": "^1.2.1", - "fast-json-stable-stringify": "^2.1.0", - "graphql-tag": "^2.11.0", - "loglevel": "^1.6.8", - "lru-cache": "^6.0.0", - "node-abort-controller": "^3.0.1", - "sha.js": "^2.4.11", - "uuid": "^9.0.0", - "whatwg-mimetype": "^3.0.0" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, - "node_modules/apollo-server-core/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/apollo-server-core/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/apollo-server-core/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/apollo-server-env": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.2.1.tgz", - "integrity": "sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g==", - "deprecated": "The `apollo-server-env` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/utils.fetcher` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", - "dependencies": { - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=12.0" - } - }, - "node_modules/apollo-server-errors": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz", - "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==", - "deprecated": "The `apollo-server-errors` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, - "node_modules/apollo-server-express": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.13.0.tgz", - "integrity": "sha512-iSxICNbDUyebOuM8EKb3xOrpIwOQgKxGbR2diSr4HP3IW8T3njKFOoMce50vr+moOCe1ev8BnLcw9SNbuUtf7g==", - "deprecated": "The `apollo-server-express` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", - "license": "MIT", - "dependencies": { - "@types/accepts": "^1.3.5", - "@types/body-parser": "1.19.2", - "@types/cors": "2.8.12", - "@types/express": "4.17.14", - "@types/express-serve-static-core": "4.17.31", - "accepts": "^1.3.5", - "apollo-server-core": "^3.13.0", - "apollo-server-types": "^3.8.0", - "body-parser": "^1.19.0", - "cors": "^2.8.5", - "parseurl": "^1.3.3" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "express": "^4.17.1", - "graphql": "^15.3.0 || ^16.0.0" - } - }, - "node_modules/apollo-server-express/node_modules/@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/apollo-server-plugin-base": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.7.2.tgz", - "integrity": "sha512-wE8dwGDvBOGehSsPTRZ8P/33Jan6/PmL0y0aN/1Z5a5GcbFhDaaJCjK5cav6npbbGL2DPKK0r6MPXi3k3N45aw==", - "deprecated": "The `apollo-server-plugin-base` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", - "dependencies": { - "apollo-server-types": "^3.8.0" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, - "node_modules/apollo-server-types": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.8.0.tgz", - "integrity": "sha512-ZI/8rTE4ww8BHktsVpb91Sdq7Cb71rdSkXELSwdSR0eXu600/sY+1UXhTWdiJvk+Eq5ljqoHLwLbY2+Clq2b9A==", - "deprecated": "The `apollo-server-types` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", - "dependencies": { - "@apollo/utils.keyvaluecache": "^1.0.1", - "@apollo/utils.logger": "^1.0.0", - "apollo-reporting-protobuf": "^3.4.0", - "apollo-server-env": "^4.2.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, "node_modules/are-docs-informative": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", @@ -13247,6 +13384,18 @@ "node": ">=12.0.0" } }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -13274,11 +13423,6 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, - "node_modules/cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" - }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -15516,9 +15660,10 @@ "dev": true }, "node_modules/graphql": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.2.tgz", - "integrity": "sha512-cvVIBILwuoSyD54U4cF/UXDh5yAobhNV/tPygI4lZhgOIJQE/WLWC4waBRb4I6bDVYb3OVx3lfHbaQOEoUD5sg==", + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -15594,20 +15739,6 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/graphql-tag": { - "version": "2.12.6", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", - "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, "node_modules/gtoken": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", @@ -17643,14 +17774,6 @@ "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==" }, - "node_modules/lru-cache": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz", - "integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==", - "engines": { - "node": ">=12" - } - }, "node_modules/luxon": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", @@ -21313,14 +21436,6 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "engines": { - "node": ">=12" - } - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -21589,26 +21704,6 @@ "node": ">=0.6.0" } }, - "node_modules/xss": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", - "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", - "dependencies": { - "commander": "^2.20.3", - "cssfilter": "0.0.10" - }, - "bin": { - "xss": "bin/xss" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/xss/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/server/package.json b/server/package.json index f042e28..344f2fb 100644 --- a/server/package.json +++ b/server/package.json @@ -23,6 +23,8 @@ "author": "Roostorg", "license": "ISC", "dependencies": { + "@apollo/server": "^5.5.0", + "@as-integrations/express4": "^1.1.2", "@aws-sdk/client-s3": "^3.1017.0", "@aws-sdk/client-secrets-manager": "^3.1017.0", "@aws-sdk/client-ses": "^3.1017.0", @@ -57,9 +59,6 @@ "@types/yargs": "^17.0.32", "ajv": "^8.18.0", "ajv-draft-04": "^1.0.0", - "apollo-datasource": "^3.3.0", - "apollo-server-core": "^3.11.1", - "apollo-server-express": "^3.10.3", "bcryptjs": "^2.4.3", "bullmq": "^5.0.0", "cassandra-driver": "^4.8.0", @@ -77,7 +76,7 @@ "formdata-node": "^6.0.3", "fuzzball": "^2.1.2", "generic-pool": "^3.8.2", - "graphql": "^16.0.1", + "graphql": "^16.13.2", "graphql-depth-limit": "^1.1.0", "graphql-passport": "^0.6.4", "graphql-scalars": "^1.19.0", @@ -119,6 +118,7 @@ "@eslint/js": "^9.39.4", "@faker-js/faker": "^7.5.0", "@types/cls-hooked": "^4.3.3", + "@types/cors": "^2.8.19", "@types/graphql-depth-limit": "^1.1.6", "@types/jest": "^29.2.4", "@types/js-yaml": "^4.0.5", @@ -163,9 +163,6 @@ "@googlemaps/google-maps-services-js@^3.3.16": { "retry-axios": "npm:@ethanresnick/retry-axios@2.6.1" }, - "apollo-server-express": { - "@types/express": "npm:@types/express@4.17.17" - }, "eslint-plugin-better-mutation": { "lodash": "^4.18.1" } -- 2.51.2