diff --git a/package.json b/package.json index 9fa9db991..50ebe8f6c 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "core-js-compat/semver": "^7.5.4", "eslint": "^9.4.0", "eslint-config-prettier": "^9.1.0", - "graphql": "^15.3.0", "levelup/bl": ">=1.2.3", "levelup/semver": ">=5.7.2", "mocha/serialize-javascript": ">=6.0.2", diff --git a/packages/frontend-2/lib/common/helpers/graphql.ts b/packages/frontend-2/lib/common/helpers/graphql.ts index b9308288d..18ae0d9e3 100644 --- a/packages/frontend-2/lib/common/helpers/graphql.ts +++ b/packages/frontend-2/lib/common/helpers/graphql.ts @@ -491,7 +491,7 @@ export const resolveGenericStatusCode = (errors: GraphQLErrors) => { if ( errors.some((e) => ['UNAUTHENTICATED', 'UNAUTHORIZED_ACCESS_ERROR'].includes( - e.extensions?.code || '' + (e.extensions?.code || '') as string ) ) ) @@ -499,7 +499,7 @@ export const resolveGenericStatusCode = (errors: GraphQLErrors) => { if ( errors.some((e) => ['NOT_FOUND_ERROR', 'STREAM_NOT_FOUND', 'AUTOMATION_NOT_FOUND'].includes( - e.extensions?.code || '' + (e.extensions?.code || '') as string ) ) ) diff --git a/packages/server/app.ts b/packages/server/app.ts index 3b792a7c7..b4c120013 100644 --- a/packages/server/app.ts +++ b/packages/server/app.ts @@ -21,17 +21,11 @@ import { import { errorLoggingMiddleware } from '@/logging/errorLogging' import prometheusClient from 'prom-client' -import { - ApolloServer, - ForbiddenError, - ApolloServerExpressConfig, - ApolloError -} from 'apollo-server-express' -import { - ApolloServerPluginLandingPageLocalDefault, - ApolloServerPluginUsageReportingDisabled, - ApolloServerPluginUsageReporting -} from 'apollo-server-core' +import { ApolloServer } from '@apollo/server' +import { expressMiddleware } from '@apollo/server/express4' +import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default' +import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting' +import { ApolloServerPluginUsageReportingDisabled } from '@apollo/server/plugin/disabled' import { ExecutionParams, SubscriptionServer } from 'subscriptions-transport-ws' import { execute, subscribe } from 'graphql' @@ -65,8 +59,13 @@ import { buildMocksConfig } from '@/modules/mocks' import { defaultErrorHandler } from '@/modules/core/rest/defaultErrorHandler' import { migrateDbToLatest } from '@/db/migrations' import { statusCodePlugin } from '@/modules/core/graph/plugins/statusCode' +import { ForbiddenError } from '@/modules/shared/errors' +import { loggingPlugin } from '@/modules/core/graph/plugins/logging' +import { isUserGraphqlError } from '@/modules/shared/helpers/graphqlHelper' -let graphqlServer: ApolloServer +const GRAPHQL_PATH = '/graphql' + +let graphqlServer: ApolloServer // eslint-disable-next-line @typescript-eslint/no-explicit-any type SubscriptionResponse = { errors?: GraphQLError[]; data?: any } @@ -93,10 +92,7 @@ function logSubscriptionOperation(params: { const errors = response?.errors || (error ? [error] : []) if (errors.length) { for (const error of errors) { - if ( - (error instanceof GraphQLError && error.extensions?.code === 'FORBIDDEN') || - error instanceof ApolloError - ) { + if (error instanceof GraphQLError && isUserGraphqlError(error)) { logger.info(error, errMsg) } else { logger.error(error, errMsg) @@ -112,10 +108,7 @@ function logSubscriptionOperation(params: { * is that graphql-ws uses an entirely different protocol, so the client-side has to change as well, and so old clients * will be unable to use any WebSocket/subscriptions functionality with the updated server */ -function buildApolloSubscriptionServer( - apolloServer: ApolloServer, - server: http.Server -): SubscriptionServer { +function buildApolloSubscriptionServer(server: http.Server): SubscriptionServer { const schema = ModulesSetup.graphSchema() // Init metrics @@ -153,7 +146,6 @@ function buildApolloSubscriptionServer( schema, execute, subscribe, - validationRules: apolloServer.requestOptions.validationRules, onConnect: async (connectionParams: Record) => { metricConnectCounter.inc() metricConnectedClients.inc() @@ -235,41 +227,37 @@ function buildApolloSubscriptionServer( }, { server, - path: apolloServer.graphqlPath + path: GRAPHQL_PATH } ) } /** * Create Apollo Server instance - * @param optionOverrides Optionally override ctor options - * @param subscriptionServerResolver If you expect to use subscriptions on this instance, - * pass in a callable that resolves the subscription server */ -export async function buildApolloServer( - optionOverrides?: Partial, - subscriptionServerResolver?: () => SubscriptionServer -): Promise { - const debug = optionOverrides?.debug || isDevEnv() || isTestEnv() +export async function buildApolloServer(options?: { + subscriptionServer?: SubscriptionServer +}): Promise> { + const includeStacktraceInErrorResponses = isDevEnv() || isTestEnv() + const subscriptionServer = options?.subscriptionServer const schema = ModulesSetup.graphSchema(await buildMocksConfig()) const server = new ApolloServer({ schema, - context: buildContext, plugins: [ statusCodePlugin, - require('@/logging/apolloPlugin'), + loggingPlugin, ApolloServerPluginLandingPageLocalDefault({ embed: true, includeCookies: true }), - ...(subscriptionServerResolver + ...(subscriptionServer ? [ { async serverWillStart() { return { async drainServer() { - subscriptionServerResolver().close() + subscriptionServer?.close() } } } @@ -289,9 +277,8 @@ export async function buildApolloServer( cache: 'bounded', persistedQueries: false, csrfPrevention: true, - formatError: buildErrorFormatter(debug), - debug, - ...optionOverrides + formatError: buildErrorFormatter({ includeStacktraceInErrorResponses }), + includeStacktraceInErrorResponses }) await server.start() @@ -350,13 +337,20 @@ export async function init() { // Initialize default modules, including rest api handlers await ModulesSetup.init(app) + // Init HTTP server & subscription server + const server = http.createServer(app) + const subscriptionServer = buildApolloSubscriptionServer(server) + // Initialize graphql server - // (Apollo Server v3 has an ugly API here - the ApolloServer ctor needs SubscriptionServer, - // and the SubscriptionServer ctor needs ApolloServer...hence the callback passed into buildApolloServer) - // eslint-disable-next-line prefer-const - let subscriptionServer: SubscriptionServer - graphqlServer = await buildApolloServer(undefined, () => subscriptionServer) - graphqlServer.applyMiddleware({ app }) + graphqlServer = await buildApolloServer({ + subscriptionServer + }) + app.use( + GRAPHQL_PATH, + expressMiddleware(graphqlServer, { + context: buildContext + }) + ) // Expose prometheus metrics app.get('/metrics', async (req, res) => { @@ -368,10 +362,6 @@ export async function init() { } }) - // Init HTTP server & subscription server - const server = http.createServer(app) - subscriptionServer = buildApolloSubscriptionServer(graphqlServer, server) - // At the very end adding default error handler middleware app.use(defaultErrorHandler) diff --git a/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts b/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts index 2fe6c5cc0..f2aa66df1 100644 --- a/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts +++ b/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts @@ -1,3 +1,4 @@ +import { buildApolloServer } from '@/app' import { deleteRequestById, getPendingAccessRequest @@ -28,16 +29,15 @@ import { useStreamAccessRequest } from '@/test/graphql/accessRequests' import { StreamRole } from '@/test/graphql/generated/graphql' +import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper' import { truncateTables } from '@/test/hooks' import { EmailSendingServiceMock } from '@/test/mocks/global' import { buildNotificationsStateTracker, NotificationsStateManager } from '@/test/notificationsHelper' -import { buildAuthenticatedApolloServer } from '@/test/serverHelper' import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper' import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper' -import { ApolloServer } from 'apollo-server-express' import { expect } from 'chai' import { noop } from 'lodash' @@ -55,7 +55,7 @@ const cleanup = async () => { } describe('Stream access requests', () => { - let apollo: ApolloServer + let apollo: ServerAndContext let notificationsStateManager: NotificationsStateManager const me: BasicTestUser = { @@ -105,7 +105,10 @@ describe('Stream access requests', () => { [otherGuysPublicStream, otherGuy], [myPrivateStream, me] ]) - apollo = await buildAuthenticatedApolloServer(me.id) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(me.id) + } notificationsStateManager = buildNotificationsStateTracker() }) diff --git a/packages/server/modules/auth/graph/resolvers/apps.js b/packages/server/modules/auth/graph/resolvers/apps.js index 975f90d7e..ab69f2f0e 100644 --- a/packages/server/modules/auth/graph/resolvers/apps.js +++ b/packages/server/modules/auth/graph/resolvers/apps.js @@ -1,6 +1,4 @@ -'use strict' -const { ForbiddenError } = require('apollo-server-express') - +const { ForbiddenError } = require('@/modules/shared/errors') const { getApp, getAllPublicApps, diff --git a/packages/server/modules/auth/rest/index.js b/packages/server/modules/auth/rest/index.js index 2967d7f70..4e6c9e853 100644 --- a/packages/server/modules/auth/rest/index.js +++ b/packages/server/modules/auth/rest/index.js @@ -10,8 +10,8 @@ const { validateToken, revokeTokenById } = require(`@/modules/core/services/toke const { revokeRefreshToken } = require(`@/modules/auth/services/apps`) const { validateScopes } = require(`@/modules/shared`) const { InvalidAccessCodeRequestError } = require('@/modules/auth/errors') -const { ForbiddenError } = require('apollo-server-errors') const { Scopes } = require('@speckle/shared') +const { ForbiddenError } = require('@/modules/shared/errors') // TODO: Secure these endpoints! module.exports = (app) => { diff --git a/packages/server/modules/blobstorage/graph/resolvers/index.ts b/packages/server/modules/blobstorage/graph/resolvers/index.ts index 04aed6c80..588e1cb75 100644 --- a/packages/server/modules/blobstorage/graph/resolvers/index.ts +++ b/packages/server/modules/blobstorage/graph/resolvers/index.ts @@ -13,9 +13,12 @@ import { StreamBlobsArgs } from '@/modules/core/graph/generated/graphql' import { StreamGraphQLReturn } from '@/modules/core/helpers/graphTypes' -import { NotFoundError, ResourceMismatch } from '@/modules/shared/errors' +import { + BadRequestError, + NotFoundError, + ResourceMismatch +} from '@/modules/shared/errors' import { Nullable } from '@speckle/shared' -import { UserInputError } from 'apollo-server-errors' const streamBlobResolvers = { async blobs(parent: StreamGraphQLReturn, args: StreamBlobsArgs | ProjectBlobsArgs) { @@ -47,7 +50,7 @@ const streamBlobResolvers = { })) as Nullable } catch (err: unknown) { if (err instanceof NotFoundError) return null - if (err instanceof ResourceMismatch) throw new UserInputError(err.message) + if (err instanceof ResourceMismatch) throw new BadRequestError(err.message) throw err } } diff --git a/packages/server/modules/blobstorage/tests/blobstorage.graph.spec.js b/packages/server/modules/blobstorage/tests/blobstorage.graph.spec.js index 15620084a..920e24def 100644 --- a/packages/server/modules/blobstorage/tests/blobstorage.graph.spec.js +++ b/packages/server/modules/blobstorage/tests/blobstorage.graph.spec.js @@ -1,18 +1,17 @@ const { buildApolloServer } = require('@/app') -const { addLoadersToCtx } = require('@/modules/shared/middleware') const { truncateTables } = require('@/test/hooks') -const { Roles, AllScopes } = require('@/modules/core/helpers/mainConstants') const { createStream } = require('@/modules/core/services/streams') const { createUser } = require('@/modules/core/services/users') -const crs = require('crypto-random-string') -const { gql } = require('apollo-server-express') +const { gql } = require('graphql-tag') const { createBlobs } = require('@/modules/blobstorage/tests/helpers') const { expect } = require('chai') const { Users, Streams } = require('@/modules/core/dbSchema') +const { createAuthedTestContext, executeOperation } = require('@/test/graphqlHelper') describe('Blobs graphql @blobstorage', () => { - /** @type {import('apollo-server-express').ApolloServer} */ - let apollo + /** @type {import('@/test/graphqlHelper').ServerAndContext} */ + let graphqlServer + const user = { name: 'Baron Von Blubba', email: 'zebarron@bubble.bobble', @@ -21,16 +20,10 @@ describe('Blobs graphql @blobstorage', () => { before(async () => { await truncateTables(['blob_storage', Users.name, Streams.name]) user.id = await createUser(user) - apollo = await buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: true, - userId: crs({ length: 10 }), - role: Roles.Server.User, - token: 'asd', - scopes: AllScopes - }) - }) + graphqlServer = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(user.id) + } }) it('Stream has blob metadata for a single blob', async () => { @@ -50,13 +43,12 @@ describe('Blobs graphql @blobstorage', () => { ` const streamId = await createStream({ ownerId: user.id }) const [blob] = await createBlobs({ streamId, number: 1 }) - const result = await apollo.executeOperation({ - query, - variables: { - streamId, - blobId: blob.id - } + + const result = await executeOperation(graphqlServer, query, { + streamId, + blobId: blob.id }) + const blobMetadata = result.data.stream.blob expect(blobMetadata.id).to.equal(blob.id) expect(blobMetadata.fileSize).to.equal(blob.fileSize) @@ -79,7 +71,7 @@ describe('Blobs graphql @blobstorage', () => { const number = 10 const fileSize = 123 await createBlobs({ streamId, number, fileSize }) - const result = await apollo.executeOperation({ query, variables: { streamId } }) + const result = await executeOperation(graphqlServer, query, { streamId }) expect(result.data.stream.blobs.totalCount).to.equal(number) expect(result.data.stream.blobs.totalSize).to.equal(number * fileSize) }) diff --git a/packages/server/modules/comments/graph/resolvers/comments.ts b/packages/server/modules/comments/graph/resolvers/comments.ts index fe69bd6d4..490e13c31 100644 --- a/packages/server/modules/comments/graph/resolvers/comments.ts +++ b/packages/server/modules/comments/graph/resolvers/comments.ts @@ -1,5 +1,4 @@ import { pubsub } from '@/modules/shared/utils/subscriptions' -import { ForbiddenError as ApolloForbiddenError } from 'apollo-server-express' import { ForbiddenError } from '@/modules/shared/errors' import { getStream } from '@/modules/core/services/streams' import { Roles } from '@/modules/core/helpers/mainConstants' @@ -77,7 +76,7 @@ const getStreamComment = async ( const comment = await getComment({ id: commentId, userId: ctx.userId }) if (comment?.streamId !== streamId) - throw new ApolloForbiddenError('You do not have access to this comment.') + throw new ForbiddenError('You do not have access to this comment.') return comment } @@ -285,21 +284,21 @@ export = { Stream: { async commentCount(parent, _args, context) { if (context.role === Roles.Server.ArchivedUser) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') return await context.loaders.streams.getCommentThreadCount.load(parent.id) } }, Commit: { async commentCount(parent, args, context) { if (context.role === Roles.Server.ArchivedUser) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') return await getResourceCommentCount({ resourceId: parent.id }) } }, Object: { async commentCount(parent, args, context) { if (context.role === Roles.Server.ArchivedUser) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') return await getResourceCommentCount({ resourceId: parent.id }) } }, @@ -368,17 +367,7 @@ export = { projectId: args.streamId, authCtx: context }) - // const stream = await getStream({ - // streamId: args.streamId, - // userId: context.userId - // }) - // if (!stream) { - // throw new ApolloError('Stream not found') - // } - // if (!stream.isPublic && !context.auth) { - // return false - // } await pubsub.publish(CommentSubscriptions.ViewerActivity, { userViewerActivity: args.data, streamId: args.streamId, @@ -396,7 +385,7 @@ export = { }) if (!stream?.allowPublicComments && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') await pubsub.publish(CommentSubscriptions.CommentThreadActivity, { commentThreadActivity: { type: 'reply-typing-status', data: args.data }, @@ -408,7 +397,7 @@ export = { async commentCreate(parent, args, context) { if (!context.userId) - throw new ApolloForbiddenError('Only registered users can comment.') + throw new ForbiddenError('Only registered users can comment.') const stream = await getStream({ streamId: args.input.streamId, @@ -416,7 +405,7 @@ export = { }) if (!stream?.allowPublicComments && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') const comment = await createComment({ userId: context.userId, @@ -441,13 +430,8 @@ export = { requireProjectRole: true }) const matchUser = !stream.role - try { - await editComment({ userId: context.userId!, input: args.input, matchUser }) - return true - } catch (err) { - if (err instanceof ForbiddenError) throw new ApolloForbiddenError(err.message) - throw err - } + await editComment({ userId: context.userId!, input: args.input, matchUser }) + return true }, // used for flagging a comment as viewed @@ -467,13 +451,7 @@ export = { requireProjectRole: true }) - let updatedComment - try { - updatedComment = await archiveComment({ ...args, userId: context.userId! }) // NOTE: permissions check inside service - } catch (err) { - if (err instanceof ForbiddenError) throw new ApolloForbiddenError(err.message) - throw err - } + const updatedComment = await archiveComment({ ...args, userId: context.userId! }) // NOTE: permissions check inside service await addCommentArchivedActivity({ streamId: args.streamId, @@ -488,7 +466,7 @@ export = { async commentReply(parent, args, context) { if (!context.userId) - throw new ApolloForbiddenError('Only registered users can comment.') + throw new ForbiddenError('Only registered users can comment.') const stream = await getStream({ streamId: args.input.streamId, @@ -496,7 +474,7 @@ export = { }) if (!stream?.allowPublicComments && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') const reply = await createCommentReply({ authorId: context.userId, @@ -528,7 +506,7 @@ export = { }) if (!stream?.allowPublicComments && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') // dont report users activity to himself if (context.userId && context.userId === payload.authorId) { @@ -552,7 +530,7 @@ export = { }) if (!stream?.allowPublicComments && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') // if we're listening for a stream's root comments events if (!variables.resourceIds) { @@ -601,7 +579,7 @@ export = { }) if (!stream?.allowPublicComments && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') return ( payload.streamId === variables.streamId && @@ -630,7 +608,7 @@ export = { ]) if (!stream?.isPublic && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') // dont report users activity to himself if ( @@ -665,7 +643,7 @@ export = { ]) if (!(stream?.isDiscoverable || stream?.isPublic) && !stream?.role) - throw new ApolloForbiddenError('You are not authorized.') + throw new ForbiddenError('You are not authorized.') if (!target.resourceIdString) { return true diff --git a/packages/server/modules/comments/tests/comments.graph.spec.js b/packages/server/modules/comments/tests/comments.graph.spec.js index 8a1165979..f47450873 100644 --- a/packages/server/modules/comments/tests/comments.graph.spec.js +++ b/packages/server/modules/comments/tests/comments.graph.spec.js @@ -2,15 +2,14 @@ const expect = require('chai').expect const crs = require('crypto-random-string') const { buildApolloServer } = require('@/app') -const { addLoadersToCtx } = require('@/modules/shared/middleware') const { beforeEachContext } = require('@/test/hooks') -const { Roles, AllScopes } = require('@/modules/core/helpers/mainConstants') +const { Roles } = require('@/modules/core/helpers/mainConstants') const { grantPermissionsStream, updateStream } = require('@/modules/core/services/streams') const { createUser } = require('@/modules/core/services/users') -const { gql } = require('apollo-server-express') +const { gql } = require('graphql-tag') const { createStream } = require('@/modules/core/services/streams') const { createObject } = require('@/modules/core/services/objects') const { createComment } = require('@/modules/comments/services') @@ -18,6 +17,11 @@ const { createCommitByBranchName } = require('@/modules/core/services/commits') const { convertBasicStringToDocument } = require('@/modules/core/services/richTextEditorService') +const { + createTestContext, + createAuthedTestContext, + executeOperation +} = require('@/test/graphqlHelper') function buildCommentInputFromString(textString) { return convertBasicStringToDocument(textString) @@ -27,7 +31,7 @@ const testForbiddenResponse = (result) => { expect(result.errors, 'This should have failed').to.exist expect(result.errors.length).to.be.above(0) expect(result.errors[0].extensions.code).to.match( - /(STREAM_INVALID_ACCESS_ERROR|FORBIDDEN)/ + /(STREAM_INVALID_ACCESS_ERROR|FORBIDDEN|UNAUTHORIZED_ACCESS_ERROR)/ ) } @@ -40,14 +44,32 @@ const testResult = (shouldSucceed, result, successTests) => { } } +/** + * @typedef {{ + * apollo: import('@/test/graphqlHelper').ServerAndContext, + * resources: { + * streamId: string, + * objectId: string, + * commentId: string, + * testActorId: string + * }, + * shouldSucceed: boolean, + * streamId: string + * }} TestContext + */ + +/** + * @param {TestContext} param0 + */ const writeComment = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($input: CommentCreateInput!) { commentCreate(input: $input) } `, - variables: { + { input: { streamId: resources.streamId, text: buildCommentInputFromString('foo'), @@ -56,16 +78,20 @@ const writeComment = async ({ apollo, resources, shouldSucceed }) => { resources: [{ resourceId: resources.streamId, resourceType: 'stream' }] } } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.commentCreate).to.be.string expect(res.data.commentCreate.length).to.equal(10) }) } +/** + * @param {TestContext} param0 + */ const broadcastViewerActivity = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($streamId: String!, $resourceId: String!, $data: JSONObject) { userViewerActivityBroadcast( streamId: $streamId @@ -74,20 +100,24 @@ const broadcastViewerActivity = async ({ apollo, resources, shouldSucceed }) => ) } `, - variables: { + { streamId: resources.streamId, data: {}, resourceId: resources.objectId } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.userViewerActivityBroadcast).to.be.true }) } +/** + * @param {TestContext} param0 + */ const broadcastCommentActivity = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($streamId: String!, $commentId: String!, $data: JSONObject) { userCommentThreadActivityBroadcast( streamId: $streamId @@ -96,36 +126,43 @@ const broadcastCommentActivity = async ({ apollo, resources, shouldSucceed }) => ) } `, - variables: { + { streamId: resources.streamId, data: {}, commentId: resources.commentId } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.userCommentThreadActivityBroadcast).to.be.true }) } +/** + * @param {TestContext} param0 + */ const viewAComment = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($streamId: String!, $commentId: String!) { commentView(streamId: $streamId, commentId: $commentId) } `, - variables: { + { streamId: resources.streamId, commentId: resources.commentId } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.commentView).to.be.true }) } +/** + * @param {TestContext} param0 + */ const archiveMyComment = async ({ apollo, resources, shouldSucceed }) => { - const context = await apollo.context() + const context = apollo.context const { id: commentId } = await createComment({ userId: context.userId, input: { @@ -139,40 +176,47 @@ const archiveMyComment = async ({ apollo, resources, shouldSucceed }) => { ] } }) - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($streamId: String!, $commentId: String!) { commentArchive(streamId: $streamId, commentId: $commentId) } `, - variables: { streamId: resources.streamId, commentId } - }) + { streamId: resources.streamId, commentId } + ) testResult(shouldSucceed, res, (res) => { expect(res.data.commentArchive).to.be.true }) } +/** + * @param {TestContext} param0 + */ const archiveOthersComment = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($streamId: String!, $commentId: String!) { commentArchive(streamId: $streamId, commentId: $commentId) } `, - variables: { + { streamId: resources.streamId, commentId: resources.commentId } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.commentArchive).to.be.true }) } +/** + * @param {TestContext} param0 + */ const editMyComment = async ({ apollo, resources, shouldSucceed }) => { - const context = await apollo.context() const { id: commentId } = await createComment({ - userId: context.userId, + userId: apollo.context.userId, input: { streamId: resources.streamId, text: buildCommentInputFromString('i wrote this myself'), @@ -184,13 +228,14 @@ const editMyComment = async ({ apollo, resources, shouldSucceed }) => { ] } }) - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($input: CommentEditInput!) { commentEdit(input: $input) } `, - variables: { + { input: { streamId: resources.streamId, id: commentId, @@ -198,20 +243,24 @@ const editMyComment = async ({ apollo, resources, shouldSucceed }) => { blobIds: [] } } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.commentEdit).to.be.true }) } +/** + * @param {TestContext} param0 + */ const editOthersComment = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($input: CommentEditInput!) { commentEdit(input: $input) } `, - variables: { + { input: { streamId: resources.streamId, id: resources.commentId, @@ -221,20 +270,24 @@ const editOthersComment = async ({ apollo, resources, shouldSucceed }) => { blobIds: [] } } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.commentEdit).to.be.true }) } +/** + * @param {TestContext} param0 + */ const replyToAComment = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` mutation ($input: ReplyCreateInput!) { commentReply(input: $input) } `, - variables: { + { input: { streamId: resources.streamId, parentComment: resources.commentId, @@ -245,16 +298,20 @@ const replyToAComment = async ({ apollo, resources, shouldSucceed }) => { data: {} } } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.commentReply).to.be.string expect(res.data.commentReply.length).to.equal(10) }) } +/** + * @param {TestContext} param0 + */ const queryComment = async ({ apollo, resources, shouldSucceed }) => { - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` query ($id: String!, $streamId: String!) { comment(id: $id, streamId: $streamId) { id @@ -270,16 +327,20 @@ const queryComment = async ({ apollo, resources, shouldSucceed }) => { } } `, - variables: { + { id: resources.commentId, streamId: resources.streamId } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.comment.id).to.exist expect(res.data.comment.id).to.equal(resources.commentId) }) } + +/** + * @param {TestContext} param0 + */ const queryComments = async ({ apollo, resources, shouldSucceed }) => { const object = { foo: 123, @@ -304,8 +365,9 @@ const queryComments = async ({ apollo, resources, shouldSucceed }) => { ) ) - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` query ($streamId: String!, $resources: [ResourceIdentifierInput]) { comments(streamId: $streamId, resources: $resources) { totalCount @@ -318,20 +380,23 @@ const queryComments = async ({ apollo, resources, shouldSucceed }) => { } } `, - variables: { + { streamId: resources.streamId, resources: [ // i expected this to work as intersection, but it works as union { resourceId: objectId, resourceType: 'object' } ] } - }) + ) testResult(shouldSucceed, res, (res) => { expect(res.data.comments.totalCount).to.be.equal(numberOfComments) expect(res.data.comments.items.map((i) => i.id)).to.be.equalInAnyOrder(commentIds) }) } +/** + * @param {TestContext} param0 + */ const queryStreamCommentCount = async ({ apollo, resources, shouldSucceed }) => { await createComment({ userId: resources.testActorId, @@ -344,8 +409,9 @@ const queryStreamCommentCount = async ({ apollo, resources, shouldSucceed }) => } }) - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` query ($id: String!) { stream(id: $id) { id @@ -353,13 +419,16 @@ const queryStreamCommentCount = async ({ apollo, resources, shouldSucceed }) => } } `, - variables: { id: resources.streamId } - }) + { id: resources.streamId } + ) testResult(shouldSucceed, res, (res) => { expect(res.data.stream.commentCount).to.be.greaterThanOrEqual(1) }) } +/** + * @param {TestContext} param0 + */ const queryObjectCommentCount = async ({ apollo, resources, shouldSucceed }) => { const objectId = await createObject({ streamId: resources.streamId, @@ -379,8 +448,9 @@ const queryObjectCommentCount = async ({ apollo, resources, shouldSucceed }) => } }) - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` query ($id: String!, $objectId: String!) { stream(id: $id) { object(id: $objectId) { @@ -389,13 +459,16 @@ const queryObjectCommentCount = async ({ apollo, resources, shouldSucceed }) => } } `, - variables: { id: resources.streamId, objectId } - }) + { id: resources.streamId, objectId } + ) testResult(shouldSucceed, res, (res) => { expect(res.data.stream.object.commentCount).to.equal(1) }) } +/** + * @param {TestContext} param0 + */ const queryCommitCommentCount = async ({ apollo, resources, shouldSucceed }) => { const objectId = await createObject({ streamId: resources.streamId, @@ -422,8 +495,9 @@ const queryCommitCommentCount = async ({ apollo, resources, shouldSucceed }) => } }) - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` query ($id: String!, $commitId: String!) { stream(id: $id) { commit(id: $commitId) { @@ -432,13 +506,16 @@ const queryCommitCommentCount = async ({ apollo, resources, shouldSucceed }) => } } `, - variables: { id: resources.streamId, commitId } - }) + { id: resources.streamId, commitId } + ) testResult(shouldSucceed, res, (res) => { expect(res.data.stream.commit.commentCount).to.equal(1) }) } +/** + * @param {TestContext} param0 + */ const queryCommitCollectionCommentCount = async ({ apollo, resources, @@ -469,8 +546,9 @@ const queryCommitCollectionCommentCount = async ({ } }) - const res = await apollo.executeOperation({ - query: gql` + const res = await executeOperation( + apollo, + gql` query ($id: String!) { otherUser(id: $id) { commits { @@ -481,8 +559,8 @@ const queryCommitCollectionCommentCount = async ({ } } `, - variables: { id: resources.testActorId } - }) + { id: resources.testActorId } + ) testResult(shouldSucceed, res, (res) => { res.data.otherUser.commits.items .map((i) => i.commentCount) @@ -830,19 +908,20 @@ describe('Graphql @comments', () => { userContext.streamData.forEach((streamContext) => { const stream = streamContext.stream let resources + /** + * @type {import('@/test/graphqlHelper').ServerAndContext} + */ let apollo before(async () => { - apollo = await buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: true, - userId: user?.id, - role: user?.role, - token: 'asd', - scopes: AllScopes - }) - }) + apollo = { + apollo: await buildApolloServer(), + context: user + ? createAuthedTestContext(user.id, { + ...(user.role ? { role: user.role } : {}) + }) + : createTestContext() + } if (user && stream.role) { await grantPermissionsStream({ diff --git a/packages/server/modules/comments/tests/comments.spec.js b/packages/server/modules/comments/tests/comments.spec.js index 621e958e9..f719a6599 100644 --- a/packages/server/modules/comments/tests/comments.spec.js +++ b/packages/server/modules/comments/tests/comments.spec.js @@ -35,8 +35,7 @@ const { } = require('@/modules/comments/services/commentTextService') const { range } = require('lodash') const { buildApolloServer } = require('@/app') -const { addLoadersToCtx } = require('@/modules/shared/middleware') -const { Roles, AllScopes } = require('@/modules/core/helpers/mainConstants') +const { AllScopes } = require('@/modules/core/helpers/mainConstants') const { createAuthTokenForUser } = require('@/test/authHelper') const { uploadBlob } = require('@/test/blobHelper') const { Comments } = require('@/modules/core/dbSchema') @@ -47,6 +46,7 @@ const { } = require('@/test/notificationsHelper') const { NotificationType } = require('@/modules/notifications/helpers/types') const { EmailSendingServiceMock } = require('@/test/mocks/global') +const { createAuthedTestContext } = require('@/test/graphqlHelper') function buildCommentInputFromString(textString) { return convertBasicStringToDocument(textString) @@ -999,7 +999,7 @@ describe('Comments @comments', () => { }) describe('when authenticated', () => { - /** @type {import('apollo-server-express').ApolloServer} */ + /** @type {import('@/test/graphqlHelper').ServerAndContext} */ let apollo let userToken let blob1 @@ -1008,16 +1008,10 @@ describe('Comments @comments', () => { const scopes = AllScopes // Init apollo instance w/ authenticated context - apollo = await buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: true, - userId: user.id, - role: Roles.Server.User, - token: 'asd', - scopes - }) - }) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(user.id) + } // Init token for authenticating w/ REST API userToken = await createAuthTokenForUser(user.id, scopes) diff --git a/packages/server/logging/apolloPlugin.js b/packages/server/modules/core/graph/plugins/logging.ts similarity index 53% rename from packages/server/logging/apolloPlugin.js rename to packages/server/modules/core/graph/plugins/logging.ts index 78192db03..e17411dd2 100644 --- a/packages/server/logging/apolloPlugin.js +++ b/packages/server/modules/core/graph/plugins/logging.ts @@ -1,10 +1,29 @@ /* eslint-disable camelcase */ -/* istanbul ignore file */ -const { ApolloError } = require('apollo-server-express') -const prometheusClient = require('prom-client') -const { graphqlLogger } = require('@/logging/logging') -const { redactSensitiveVariables } = require('@/logging/loggingHelper') -const { GraphQLError } = require('graphql') +import prometheusClient from 'prom-client' +import { graphqlLogger } from '@/logging/logging' +import { redactSensitiveVariables } from '@/logging/loggingHelper' +import { FieldNode, GraphQLError, SelectionNode } from 'graphql' +import { ApolloServerPlugin } from '@apollo/server' +import { GraphQLContext } from '@/modules/shared/helpers/typeHelper' +import { isUserGraphqlError } from '@/modules/shared/helpers/graphqlHelper' + +type ApolloLoggingPluginTransaction = { + start: number + op: string + name: string + finish: () => void +} + +declare module '@apollo/server' { + interface GraphQLRequest { + /** + * Set and used in our apollo logging plugin + */ + transaction?: ApolloLoggingPluginTransaction + } +} + +const isFieldNode = (node: SelectionNode): node is FieldNode => node.kind === 'Field' const metricCallCount = new prometheusClient.Counter({ name: 'speckle_server_apollo_calls', @@ -12,19 +31,33 @@ const metricCallCount = new prometheusClient.Counter({ labelNames: ['actionName'] }) -/** @type {import('apollo-server-core').PluginDefinition} */ -module.exports = { - // eslint-disable-next-line no-unused-vars - requestDidStart(ctx) { +export const loggingPlugin: ApolloServerPlugin = { + requestDidStart: async () => { const apolloRequestStart = Date.now() return { - didResolveOperation(ctx) { - let logger = ctx.context.log || graphqlLogger - const auth = ctx.context + didResolveOperation: async (ctx) => { + let logger = ctx.contextValue.log || graphqlLogger + + if (!ctx.operation) { + logger.debug('Attempted to log a GQL operation without an operation') + return + } + + const firstSelectedField = + ctx.operation.selectionSet.selections.find(isFieldNode) + if (!firstSelectedField) { + logger.debug( + 'Attempted to log a GQL operation without a top-level field selection' + ) + return + } + + const auth = ctx.contextValue const userId = auth?.userId - const op = `GQL ${ctx.operation.operation} ${ctx.operation.selectionSet.selections[0].name.value}` - const name = `GQL ${ctx.operation.selectionSet.selections[0].name.value}` + const actionName = `${ctx.operation.operation} ${firstSelectedField.name.value}` + const op = `GQL ${actionName}` + const name = `GQL ${firstSelectedField.name.value}` const kind = ctx.operation.operation const query = ctx.request.query const variables = ctx.request.variables @@ -43,23 +76,23 @@ module.exports = { op, name, finish: () => { - //TODO add tracing with opentelemetry + // TODO: add tracing with opentelemetry } } try { - const actionName = `${ctx.operation.operation} ${ctx.operation.selectionSet.selections[0].name.value}` logger = logger.child({ actionName }) metricCallCount.labels(actionName).inc() } catch (e) { logger.error({ err: e, transaction }, 'Error while defining action name') } + ctx.request.http ctx.request.transaction = transaction - ctx.context.log = logger + ctx.contextValue.log = logger }, - didEncounterErrors(ctx) { - let logger = ctx.context.log || graphqlLogger + didEncounterErrors: async (ctx) => { + let logger = ctx.contextValue.log || graphqlLogger logger = logger.child({ apollo_query_duration_ms: Date.now() - apolloRequestStart }) @@ -77,10 +110,7 @@ module.exports = { graphql_variables: variables }) } - if ( - (err instanceof GraphQLError && err.extensions?.code === 'FORBIDDEN') || - err instanceof ApolloError - ) { + if (err instanceof GraphQLError && isUserGraphqlError(err)) { logger.info( { err }, '{graphql_operation_value} failed after {apollo_query_duration_ms} ms' @@ -93,12 +123,13 @@ module.exports = { } } }, - willSendResponse(ctx) { - const logger = ctx.context.log || graphqlLogger + willSendResponse: async (ctx) => { + const logger = ctx.contextValue.log || graphqlLogger if (ctx.request.transaction) { ctx.request.transaction.finish() } + logger.info( { apollo_query_duration_ms: Date.now() - apolloRequestStart diff --git a/packages/server/modules/core/graph/plugins/statusCode.ts b/packages/server/modules/core/graph/plugins/statusCode.ts index 4d31201af..c66294517 100644 --- a/packages/server/modules/core/graph/plugins/statusCode.ts +++ b/packages/server/modules/core/graph/plugins/statusCode.ts @@ -1,8 +1,9 @@ import { RateLimitError } from '@/modules/core/errors/ratelimit' import { BaseError } from '@/modules/shared/errors' import { Nullable } from '@speckle/shared' -import type { ApolloServerPlugin } from 'apollo-server-plugin-base' +import type { ApolloServerPlugin } from '@apollo/server' import type { GraphQLError } from 'graphql' +import { GraphQLContext } from '@/modules/shared/helpers/typeHelper' const getErrorCode = (e: GraphQLError): Nullable => { const extensionsCode = e.extensions?.code as string @@ -15,7 +16,7 @@ const getErrorCode = (e: GraphQLError): Nullable => { return infoCode } -export const statusCodePlugin: ApolloServerPlugin = { +export const statusCodePlugin: ApolloServerPlugin = { requestDidStart: async () => { return { willSendResponse: async (reqCtx) => { diff --git a/packages/server/modules/core/graph/resolvers/apitoken.js b/packages/server/modules/core/graph/resolvers/apitoken.js index 66fe34596..24193d300 100644 --- a/packages/server/modules/core/graph/resolvers/apitoken.js +++ b/packages/server/modules/core/graph/resolvers/apitoken.js @@ -1,6 +1,4 @@ -'use strict' - -const { ForbiddenError } = require('apollo-server-express') +const { ForbiddenError } = require('@/modules/shared/errors') const { createPersonalAccessToken, revokeToken, diff --git a/packages/server/modules/core/graph/resolvers/commits.js b/packages/server/modules/core/graph/resolvers/commits.js index 4b086f2e5..e90166e09 100644 --- a/packages/server/modules/core/graph/resolvers/commits.js +++ b/packages/server/modules/core/graph/resolvers/commits.js @@ -1,7 +1,4 @@ -'use strict' - const { CommitNotFoundError } = require('@/modules/core/errors/commit') -const { UserInputError } = require('apollo-server-express') const { withFilter } = require('graphql-subscriptions') const { pubsub, @@ -41,6 +38,7 @@ const { const { StreamInvalidAccessError } = require('@/modules/core/errors/stream') const { Roles } = require('@speckle/shared') const { toProjectIdWhitelist } = require('@/modules/core/helpers/token') +const { BadRequestError } = require('@/modules/shared/errors') // subscription events const COMMIT_CREATED = CommitPubsubEvents.CommitCreated @@ -61,7 +59,7 @@ const getUserCommits = async (publicOnly, userId, args, streamIdWhitelist) => { streamIdWhitelist }) if (args.limit && args.limit > 100) - throw new UserInputError( + throw new BadRequestError( 'Cannot return more than 100 items, please use pagination.' ) const { commits: items, cursor } = await getCommitsByUserId({ diff --git a/packages/server/modules/core/graph/resolvers/streams.ts b/packages/server/modules/core/graph/resolvers/streams.ts index 04566d792..1a29f72d9 100644 --- a/packages/server/modules/core/graph/resolvers/streams.ts +++ b/packages/server/modules/core/graph/resolvers/streams.ts @@ -1,4 +1,3 @@ -import { UserInputError } from 'apollo-server-express' import { getStream, getStreams, @@ -49,6 +48,7 @@ import { queryAllResourceInvitesFactory } from '@/modules/serverinvites/reposito import db from '@/db/knex' import { getInvitationTargetUsersFactory } from '@/modules/serverinvites/services/retrieval' import { getUsers } from '@/modules/core/repositories/users' +import { BadRequestError } from '@/modules/shared/errors' const getUserStreamsCore = async ( forOtherUser: boolean, @@ -125,7 +125,7 @@ export = { async adminStreams(parent, args, ctx) { if (args.limit && args.limit > 50) - throw new UserInputError('Cannot return more than 50 items at a time.') + throw new BadRequestError('Cannot return more than 50 items at a time.') const { streams, totalCount } = await getStreams({ offset: args.offset, @@ -200,7 +200,7 @@ export = { const { limit, cursor } = args if (userId !== requestedUserId) - throw new UserInputError("Cannot view another user's favorite streams") + throw new BadRequestError("Cannot view another user's favorite streams") return await getFavoriteStreamsCollection({ userId, @@ -305,12 +305,14 @@ export = { const { streamId, favorited } = args const { userId, resourceAccessRules } = ctx - return await favoriteStream({ + const stream = await favoriteStream({ userId: userId!, streamId, favorited, userResourceAccessRules: resourceAccessRules }) + + return stream }, async streamLeave(_parent, args, ctx) { diff --git a/packages/server/modules/core/graph/resolvers/users.js b/packages/server/modules/core/graph/resolvers/users.js index 396d89dd2..afcfc5491 100644 --- a/packages/server/modules/core/graph/resolvers/users.js +++ b/packages/server/modules/core/graph/resolvers/users.js @@ -1,5 +1,3 @@ -'use strict' -const { UserInputError } = require('apollo-server-express') const { getUser, getUserByEmail, @@ -28,6 +26,7 @@ const { findServerInvitesFactory } = require('@/modules/serverinvites/repositories/serverInvites') const db = require('@/db/knex') +const { BadRequestError } = require('@/modules/shared/errors') /** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */ module.exports = { @@ -60,7 +59,7 @@ module.exports = { else await validateScopes(context.scopes, Scopes.Users.Read) if (!args.id && !context.userId) { - throw new UserInputError('You must provide an user id.') + throw new BadRequestError('You must provide an user id.') } return await getUser(args.id || context.userId) @@ -81,10 +80,10 @@ module.exports = { await validateScopes(context.scopes, Scopes.Users.Read) if (args.query.length < 3) - throw new UserInputError('Search query must be at least 3 carachters.') + throw new BadRequestError('Search query must be at least 3 carachters.') if (args.limit && args.limit > 100) - throw new UserInputError( + throw new BadRequestError( 'Cannot return more than 100 items, please use pagination.' ) @@ -173,7 +172,7 @@ module.exports = { const user = await getUser(context.userId) if (args.userConfirmation.email !== user.email) { - throw new UserInputError('Malformed input: emails do not match.') + throw new BadRequestError('Malformed input: emails do not match.') } // The below are not really needed anymore as we've added the hasRole and hasScope diff --git a/packages/server/modules/core/graph/setup.js b/packages/server/modules/core/graph/setup.ts similarity index 54% rename from packages/server/modules/core/graph/setup.js rename to packages/server/modules/core/graph/setup.ts index 49a0baca3..2fb1a16a4 100644 --- a/packages/server/modules/core/graph/setup.js +++ b/packages/server/modules/core/graph/setup.ts @@ -1,7 +1,9 @@ -const _ = require('lodash') -const VError = require('verror') -const { ZodError } = require('zod') -const { fromZodError } = require('zod-validation-error') +import { ApolloServerOptions, BaseContext } from '@apollo/server' +import { GraphQLError } from 'graphql' +import _ from 'lodash' +import { VError } from 'verror' +import { ZodError } from 'zod' +import { fromZodError } from 'zod-validation-error' /** * Some VError implementation details that we want to remove from object representations @@ -11,32 +13,36 @@ const VERROR_TRASH_PROPS = ['jse_shortmsg', 'jse_cause', 'jse_info'] /** * Builds apollo server error formatter - * @param {boolean} debug - * @returns {(e: import('graphql').GraphQLError) => import('graphql').GraphQLFormattedError} */ -function buildErrorFormatter(debug) { +export function buildErrorFormatter(params: { + includeStacktraceInErrorResponses: boolean +}): ApolloServerOptions['formatError'] { + const { includeStacktraceInErrorResponses } = params + // TODO: Add support for client-aware errors and obfuscate everything else - return function (error) { - const debugMode = debug - const realError = error.originalError ? error.originalError : error + return function (formattedError, error) { + let realError = error || formattedError + if (realError instanceof GraphQLError && realError.originalError) { + realError = realError.originalError + } // If error is a ZodError, convert its message to something more readable if (realError instanceof ZodError) { return { - ...error, + ...formattedError, message: fromZodError(realError).message, - extensions: { ...error.extensions, code: 'BAD_REQUEST' } + extensions: { ...formattedError.extensions, code: 'BAD_REQUEST' } } } // If error isn't a VError child, don't do anything extra if (!(realError instanceof VError)) { - return error + return formattedError } // Converting VError based error to Apollo's format const extensions = { - ...(error.extensions || {}), + ...(formattedError.extensions || {}), ...(VError.info(realError) || {}) } @@ -47,7 +53,7 @@ function buildErrorFormatter(debug) { if (extensions.exception) { extensions.exception = _.omit(extensions.exception, VERROR_TRASH_PROPS) - if (debugMode) { + if (includeStacktraceInErrorResponses) { extensions.exception.stacktrace = VError.fullStack(realError) } else { delete extensions.exception.stacktrace @@ -55,14 +61,10 @@ function buildErrorFormatter(debug) { } return { - message: error.message, - locations: error.locations, - path: error.path, + message: formattedError.message, + locations: formattedError.locations, + path: formattedError.path, extensions } } } - -module.exports = { - buildErrorFormatter -} diff --git a/packages/server/modules/core/services/branch/retrieval.ts b/packages/server/modules/core/services/branch/retrieval.ts index 79e4f1b27..c75d2f607 100644 --- a/packages/server/modules/core/services/branch/retrieval.ts +++ b/packages/server/modules/core/services/branch/retrieval.ts @@ -4,7 +4,6 @@ import { ProjectModelsTreeArgs, StreamBranchesArgs } from '@/modules/core/graph/generated/graphql' -import { UserInputError } from 'apollo-server-core' import { getBranchesByStreamId } from '@/modules/core/services/branches' import { getStructuredProjectModels, @@ -19,6 +18,7 @@ import { last } from 'lodash' import { Merge } from 'type-fest' import { ModelsTreeItemGraphQLReturn } from '@/modules/core/helpers/graphTypes' import { getMaximumProjectModelsPerPage } from '@/modules/shared/helpers/envHelper' +import { BadRequestError } from '@/modules/shared/errors' export async function getStructuredStreamModels(streamId: string) { return getStructuredProjectModels(streamId) @@ -30,7 +30,7 @@ export async function getPaginatedStreamBranches( ) { const maxProjectModelsPerPage = getMaximumProjectModelsPerPage() if (params.limit && params.limit > maxProjectModelsPerPage) - throw new UserInputError( + throw new BadRequestError( `Cannot return more than ${maxProjectModelsPerPage} items, please use pagination.` ) const { items, cursor, totalCount } = await getBranchesByStreamId({ diff --git a/packages/server/modules/core/services/commit/retrieval.ts b/packages/server/modules/core/services/commit/retrieval.ts index 10276ff60..ce8ffc69b 100644 --- a/packages/server/modules/core/services/commit/retrieval.ts +++ b/packages/server/modules/core/services/commit/retrieval.ts @@ -13,14 +13,14 @@ import { getCommitsByStreamId, getCommitsTotalCountByStreamId } from '@/modules/core/services/commits' -import { UserInputError } from 'apollo-server-core' +import { BadRequestError } from '@/modules/shared/errors' export async function getPaginatedStreamCommits( streamId: string, params: StreamCommitsArgs ) { if (params.limit && params.limit > 100) - throw new UserInputError( + throw new BadRequestError( 'Cannot return more than 100 items, please use pagination.' ) const { commits: items, cursor } = await getCommitsByStreamId({ @@ -41,7 +41,7 @@ export async function getPaginatedBranchCommits( params: PaginatedBranchCommitsParams & { filter?: Nullable } ) { if (params.limit && params.limit > 100) - throw new UserInputError( + throw new BadRequestError( 'Cannot return more than 100 items, please use pagination.' ) diff --git a/packages/server/modules/core/services/streams/streamAccessService.js b/packages/server/modules/core/services/streams/streamAccessService.js index fcd2970fd..8f5511549 100644 --- a/packages/server/modules/core/services/streams/streamAccessService.js +++ b/packages/server/modules/core/services/streams/streamAccessService.js @@ -1,8 +1,11 @@ const { authorizeResolver } = require(`@/modules/shared`) const { Roles } = require('@/modules/core/helpers/mainConstants') -const { LogicError } = require('@/modules/shared/errors') -const { ForbiddenError, UserInputError } = require('apollo-server-express') +const { + LogicError, + ForbiddenError, + BadRequestError +} = require('@/modules/shared/errors') const { StreamInvalidAccessError, StreamAccessUpdateError @@ -170,7 +173,7 @@ async function addOrUpdateStreamCollaborator( if (role === Roles.Stream.Owner) { const userServerRole = await ServerAcl.knex().where({ userId }).first() if (userServerRole.role === Roles.Server.Guest) - throw new UserInputError('Server guests cannot own streams') + throw new BadRequestError('Server guests cannot own streams') } const stream = await grantStreamPermissions({ diff --git a/packages/server/modules/core/tests/batchCommits.spec.ts b/packages/server/modules/core/tests/batchCommits.spec.ts index 6f02621e3..9746dc8e7 100644 --- a/packages/server/modules/core/tests/batchCommits.spec.ts +++ b/packages/server/modules/core/tests/batchCommits.spec.ts @@ -1,3 +1,4 @@ +import { buildApolloServer } from '@/app' import { Commits, Streams, Users } from '@/modules/core/dbSchema' import { Roles } from '@/modules/core/helpers/mainConstants' import { getCommits } from '@/modules/core/repositories/commits' @@ -5,14 +6,14 @@ import { createBranch } from '@/modules/core/services/branches' import { addOrUpdateStreamCollaborator } from '@/modules/core/services/streams/streamAccessService' import { BasicTestUser, createTestUsers } from '@/test/authHelper' import { deleteCommits, moveCommits } from '@/test/graphql/commits' -import { truncateTables } from '@/test/hooks' import { - buildAuthenticatedApolloServer, - buildUnauthenticatedApolloServer -} from '@/test/serverHelper' + createAuthedTestContext, + createTestContext, + ServerAndContext +} from '@/test/graphqlHelper' +import { truncateTables } from '@/test/hooks' import { BasicTestCommit, createTestCommits } from '@/test/speckle-helpers/commitHelper' import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper' -import { ApolloServer } from 'apollo-server-express' import { expect } from 'chai' import { times } from 'lodash' import { describe } from 'mocha' @@ -120,7 +121,7 @@ describe('Batch commits', () => { ] const buildBatchActionInvoker = - (apollo: ApolloServer) => (type: BatchActionType, commitIds: string[]) => { + (apollo: ServerAndContext) => (type: BatchActionType, commitIds: string[]) => { if (type === BatchActionType.Delete) { return deleteCommits(apollo, { input: { commitIds } }) } else if (type === BatchActionType.Move) { @@ -135,11 +136,14 @@ describe('Batch commits', () => { type BatchActionInvoker = ReturnType describe('when authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext let invokeBatchAction: BatchActionInvoker before(async () => { - apollo = await buildAuthenticatedApolloServer(me.id) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(me.id) + } invokeBatchAction = buildBatchActionInvoker(apollo) }) @@ -268,11 +272,14 @@ describe('Batch commits', () => { }) describe('when not authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext let invokeBatchAction: BatchActionInvoker before(async () => { - apollo = await buildUnauthenticatedApolloServer() + apollo = { + apollo: await buildApolloServer(), + context: createTestContext() + } invokeBatchAction = buildBatchActionInvoker(apollo) }) diff --git a/packages/server/modules/core/tests/commitsGraphql.spec.ts b/packages/server/modules/core/tests/commitsGraphql.spec.ts index 60471a213..0ea67d498 100644 --- a/packages/server/modules/core/tests/commitsGraphql.spec.ts +++ b/packages/server/modules/core/tests/commitsGraphql.spec.ts @@ -1,14 +1,14 @@ +import { buildApolloServer } from '@/app' import { Commits, Streams, Users } from '@/modules/core/dbSchema' import { Roles } from '@/modules/core/helpers/mainConstants' import { addOrUpdateStreamCollaborator } from '@/modules/core/services/streams/streamAccessService' import { Nullable } from '@/modules/shared/helpers/typeHelper' import { BasicTestUser, createTestUsers } from '@/test/authHelper' import { readOtherUsersCommits, readOwnCommits } from '@/test/graphql/commits' +import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper' import { truncateTables } from '@/test/hooks' -import { buildAuthenticatedApolloServer } from '@/test/serverHelper' import { createTestCommit } from '@/test/speckle-helpers/commitHelper' import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper' -import { ApolloServer } from 'apollo-server-express' import { expect } from 'chai' describe('Commits (GraphQL)', () => { @@ -94,10 +94,13 @@ describe('Commits (GraphQL)', () => { }) describe('when user authenticated', async () => { - let apollo: ApolloServer + let apollo: ServerAndContext before(async () => { - apollo = await buildAuthenticatedApolloServer(me.id) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(me.id) + } }) describe('and reading user commits', async () => { diff --git a/packages/server/modules/core/tests/discoverableStreams.spec.ts b/packages/server/modules/core/tests/discoverableStreams.spec.ts index 0cb1d0883..21dfcd4d1 100644 --- a/packages/server/modules/core/tests/discoverableStreams.spec.ts +++ b/packages/server/modules/core/tests/discoverableStreams.spec.ts @@ -1,3 +1,4 @@ +import { buildApolloServer } from '@/app' import { Streams, Users } from '@/modules/core/dbSchema' import { getStream, setStreamFavorited } from '@/modules/core/repositories/streams' import { Nullable, Optional } from '@/modules/shared/helpers/typeHelper' @@ -11,14 +12,14 @@ import { readDiscoverableStreams, updateStream } from '@/test/graphql/streams' -import { truncateTables } from '@/test/hooks' import { - buildAuthenticatedApolloServer, - buildUnauthenticatedApolloServer -} from '@/test/serverHelper' + createAuthedTestContext, + createTestContext, + ServerAndContext +} from '@/test/graphqlHelper' +import { truncateTables } from '@/test/hooks' import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper' import { wait } from '@speckle/shared' -import { ApolloServer } from 'apollo-server-express' import { expect } from 'chai' import dayjs from 'dayjs' import { shuffle } from 'lodash' @@ -28,7 +29,7 @@ const READABLE_DISCOVERABLE_STREAM_COUNT = 15 const cleanup = async () => await truncateTables([Streams.name, Users.name]) describe('Discoverable streams', () => { - let apollo: ApolloServer + let apollo: ServerAndContext const me: BasicTestUser = { name: 'itsaa meeee', @@ -111,7 +112,10 @@ describe('Discoverable streams', () => { } } - apollo = await buildUnauthenticatedApolloServer() + apollo = { + apollo: await buildApolloServer(), + context: createTestContext() + } }) after(async () => { @@ -234,10 +238,13 @@ describe('Discoverable streams', () => { }) describe('when authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext before(async () => { - apollo = await buildAuthenticatedApolloServer(me.id) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(me.id) + } }) it('can be retrieved with role properly filled out', async () => { diff --git a/packages/server/modules/core/tests/favoriteStreams.spec.js b/packages/server/modules/core/tests/favoriteStreams.spec.js index abb3680a5..5780cb84d 100644 --- a/packages/server/modules/core/tests/favoriteStreams.spec.js +++ b/packages/server/modules/core/tests/favoriteStreams.spec.js @@ -3,13 +3,16 @@ const expect = require('chai').expect const { buildApolloServer } = require('@/app') const { StreamFavorites, Streams, Users } = require('@/modules/core/dbSchema') -const { Roles, AllScopes } = require('@/modules/core/helpers/mainConstants') const { createStream } = require('@/modules/core/services/streams') const { createUser } = require('@/modules/core/services/users') -const { addLoadersToCtx } = require('@/modules/shared/middleware') const { truncateTables } = require('@/test/hooks') -const { gql } = require('apollo-server-express') +const { gql } = require('graphql-tag') const { sleep } = require('@/test/helpers') +const { + createAuthedTestContext, + createTestContext, + executeOperation +} = require('@/test/graphqlHelper') /** * Cleaning up relevant tables @@ -140,26 +143,17 @@ describe('Favorite streams', () => { }) describe('when authenticated', () => { - /** @type {import('apollo-server-express').ApolloServer} */ + /** @type {import('@/test/graphqlHelper').ServerAndContext} */ let apollo const favoriteStream = async (sid, favorited) => - await apollo.executeOperation({ - query: favoriteMutationGql, - variables: { sid, favorited } - }) + await executeOperation(apollo, favoriteMutationGql, { sid, favorited }) before(async () => { - apollo = await buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: true, - userId: me.id, - role: Roles.Server.User, - token: 'asd', - scopes: AllScopes - }) - }) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(me.id) + } // Drop all favorites to ensure we don't favorite already favorited streams await StreamFavorites.knex().truncate() @@ -242,10 +236,7 @@ describe('Favorite streams', () => { ] const getFavorites = async (cursor, limit = 10) => - await apollo.executeOperation({ - query: favoriteStreamsQueryGql, - variables: { cursor, limit } - }) + await executeOperation(apollo, favoriteStreamsQueryGql, { cursor, limit }) const favoritedStreamIds = () => favoritableStreams.map((s) => s.id) @@ -267,10 +258,11 @@ describe('Favorite streams', () => { }) it("throw error if trying to get another user's favorite stream collection", async () => { - const { data, errors } = await apollo.executeOperation({ - query: anotherUserFavoriteStreamsQueryGql, - variables: { limit: 10, uid: otherGuy.id } - }) + const { data, errors } = await executeOperation( + apollo, + anotherUserFavoriteStreamsQueryGql, + { limit: 10, uid: otherGuy.id } + ) expect(data).to.be.ok expect(data.otherUser?.favoriteStreams).to.not.be.ok @@ -327,23 +319,16 @@ describe('Favorite streams', () => { oldNewQueryDataset.forEach(({ display, isNew }) => { it(`return total favorites count for user (${display} query)`, async () => { // "Log in" with other user - const apollo = await buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: true, - userId: otherGuy.id, - role: Roles.Server.User, - token: 'asd', - scopes: AllScopes - }) - }) + const apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(otherGuy.id) + } - const { data, errors } = await apollo.executeOperation({ - query: isNew - ? totalOwnedStreamsFavoritesNew - : totalOwnedStreamsFavoritesOld, - variables: { uid: me.id } - }) + const { data, errors } = await executeOperation( + apollo, + isNew ? totalOwnedStreamsFavoritesNew : totalOwnedStreamsFavoritesOld, + { uid: me.id } + ) expect(errors).to.not.be.ok @@ -356,19 +341,20 @@ describe('Favorite streams', () => { }) describe('when not authenticated', () => { - /** @type {import('apollo-server-express').ApolloServer} */ + /** @type {import('@/test/graphqlHelper').ServerAndContext} */ let apollo before(async () => { - apollo = await buildApolloServer({ - context: () => ({}) - }) + apollo = { + apollo: await buildApolloServer(), + context: createTestContext() + } }) it("can't be favorited", async () => { - const result = await apollo.executeOperation({ - query: favoriteMutationGql, - variables: { sid: myPubStream.id, favorited: true } + const result = await executeOperation(apollo, favoriteMutationGql, { + sid: myPubStream.id, + favorited: true }) expect(result.data.streamFavorite).to.not.be.ok @@ -377,9 +363,7 @@ describe('Favorite streams', () => { }) it("can't be retrieved", async () => { - const result = await apollo.executeOperation({ - query: favoriteStreamsQueryGql - }) + const result = await executeOperation(apollo, favoriteStreamsQueryGql) expect(result.data.activeUser).to.be.null expect(result.errors).to.not.be.ok diff --git a/packages/server/modules/core/tests/generic.spec.js b/packages/server/modules/core/tests/generic.spec.js index bbcf10047..94f4f6733 100644 --- a/packages/server/modules/core/tests/generic.spec.js +++ b/packages/server/modules/core/tests/generic.spec.js @@ -15,9 +15,9 @@ const { createUser } = require('@/modules/core/services/users') const { validateScopes, authorizeResolver } = require('@/modules/shared') const { buildContext } = require('@/modules/shared/middleware') -const { ForbiddenError } = require('apollo-server-express') const { Roles, Scopes } = require('@speckle/shared') const { throwForNotHavingServerRole } = require('@/modules/shared/authz') +const { ForbiddenError } = require('@/modules/shared/errors') describe('Generic AuthN & AuthZ controller tests', () => { before(async () => { diff --git a/packages/server/modules/core/tests/graph.spec.js b/packages/server/modules/core/tests/graph.spec.js index e934d0080..d198b9eba 100644 --- a/packages/server/modules/core/tests/graph.spec.js +++ b/packages/server/modules/core/tests/graph.spec.js @@ -303,7 +303,7 @@ describe('GraphQL API Core @core-api', () => { }) expect(goodTokenScopesBadEmail.body.errors).to.exist expect(goodTokenScopesBadEmail.body.errors[0].extensions?.code).to.equal( - 'BAD_USER_INPUT' + 'BAD_REQUEST_ERROR' ) const goodTokenScopesGoodEmail = await sendRequest(userDelete.token, { query: @@ -626,7 +626,9 @@ describe('GraphQL API Core @core-api', () => { query: '{ adminStreams(limit: 200) { totalCount items { id name } } }' }) expect(streamResults.body.errors).to.exist - expect(streamResults.body.errors[0].extensions.code).to.equal('BAD_USER_INPUT') + expect(streamResults.body.errors[0].extensions.code).to.equal( + 'BAD_REQUEST_ERROR' + ) streamResults = await sendRequest(userA.token, { query: '{ adminStreams(limit: 2) { totalCount items { id name } } }' @@ -1269,14 +1271,14 @@ describe('GraphQL API Core @core-api', () => { let res = await sendRequest(userB.token, { query: queryLim }) expect(res).to.be.json expect(res.body.errors).to.exist - expect(res.body.errors[0].extensions.code).to.equal('BAD_USER_INPUT') + expect(res.body.errors[0].extensions.code).to.equal('BAD_REQUEST_ERROR') const queryPagination = 'query { userSearch( query: "matteo", limit: 200 ) { cursor items { id name } } } ' res = await sendRequest(userB.token, { query: queryPagination }) expect(res).to.be.json expect(res.body.errors).to.exist - expect(res.body.errors[0].extensions.code).to.equal('BAD_USER_INPUT') + expect(res.body.errors[0].extensions.code).to.equal('BAD_REQUEST_ERROR') }) }) diff --git a/packages/server/modules/core/tests/graphSubs.spec.js b/packages/server/modules/core/tests/graphSubs.spec.js index c7fc80ad1..b9f1a4c76 100644 --- a/packages/server/modules/core/tests/graphSubs.spec.js +++ b/packages/server/modules/core/tests/graphSubs.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect const request = require('supertest') -const { gql } = require('apollo-server-express') +const { gql } = require('graphql-tag') const { WebSocketLink } = require('@apollo/client/link/ws') const { execute } = require('@apollo/client/core') diff --git a/packages/server/modules/core/tests/helpers/graphql.ts b/packages/server/modules/core/tests/helpers/graphql.ts index 6c7d859bc..8114b13e4 100644 --- a/packages/server/modules/core/tests/helpers/graphql.ts +++ b/packages/server/modules/core/tests/helpers/graphql.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import { gql } from 'graphql-tag' export const createObjectMutation = gql` mutation CreateObject($input: ObjectCreateInput!) { diff --git a/packages/server/modules/core/tests/streams.spec.ts b/packages/server/modules/core/tests/streams.spec.ts index 851510506..6841c4bef 100644 --- a/packages/server/modules/core/tests/streams.spec.ts +++ b/packages/server/modules/core/tests/streams.spec.ts @@ -22,10 +22,6 @@ import { isStreamCollaborator } from '@/modules/core/services/streams/streamAccessService' import { Roles } from '@/modules/core/helpers/mainConstants' -import { - buildAuthenticatedApolloServer, - buildUnauthenticatedApolloServer -} from '@/test/serverHelper' import { getLimitedUserStreams, getUserStreams, @@ -43,7 +39,6 @@ import { } from '@/modules/core/repositories/streams' import { has, times } from 'lodash' import { Streams } from '@/modules/core/dbSchema' -import { ApolloServer } from 'apollo-server-express' import { Nullable } from '@/modules/shared/helpers/typeHelper' import { sleep } from '@/test/helpers' import dayjs, { Dayjs } from 'dayjs' @@ -53,6 +48,12 @@ import { } from '@/test/graphql/generated/graphql' import { Get } from 'type-fest' import { changeUserRole } from '@/modules/core/services/users' +import { + createAuthedTestContext, + createTestContext, + ServerAndContext +} from '@/test/graphqlHelper' +import { buildApolloServer } from '@/app' describe('Streams @core-streams', () => { const userOne: BasicTestUser = { @@ -210,7 +211,10 @@ describe('Streams @core-streams', () => { userOne.id ) - const apollo = await buildAuthenticatedApolloServer(userTwo.id) + const apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(userTwo.id) + } const { data, errors } = await leaveStream(apollo, { streamId }) expect(errors).to.be.not.ok @@ -445,7 +449,7 @@ describe('Streams @core-streams', () => { * Base test for testing paginated & unpaginated User.streams query in various circumstances */ const testPaginatedUserStreams = async ( - apollo: ApolloServer, + apollo: ServerAndContext, pagination: boolean, userId: string, isOtherUser: boolean, @@ -534,12 +538,15 @@ describe('Streams @core-streams', () => { } describe('and user is authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext let activeUserId: string before(async () => { activeUserId = userOne.id - apollo = await buildAuthenticatedApolloServer(activeUserId) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(activeUserId) + } }) paginationDataset.forEach(({ display, pagination }) => { @@ -562,10 +569,13 @@ describe('Streams @core-streams', () => { }) describe('and user is not authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext before(async () => { - apollo = await buildUnauthenticatedApolloServer() + apollo = { + apollo: await buildApolloServer(), + context: createTestContext() + } }) userLimitedUserDataSet.forEach(({ display, limitedUser }) => { @@ -573,8 +583,17 @@ describe('Streams @core-streams', () => { const results = limitedUser ? await getLimitedUserStreams(apollo, { userId: userOne.id }) : await getUserStreams(apollo, { userId: userOne.id }) + + const user = results.data + ? 'otherUser' in results.data + ? results.data.otherUser + : 'user' in results.data + ? results.data.user + : null + : null + expect(results).to.haveGraphQLErrors() - expect(results.data?.otherUser || results.data?.user).to.be.not.ok + expect(user).to.be.not.ok }) }) }) diff --git a/packages/server/modules/core/tests/usersAdminList.spec.ts b/packages/server/modules/core/tests/usersAdminList.spec.ts index 0d76e1c0e..3119416ef 100644 --- a/packages/server/modules/core/tests/usersAdminList.spec.ts +++ b/packages/server/modules/core/tests/usersAdminList.spec.ts @@ -6,12 +6,11 @@ import { times, clamp } from 'lodash' import { createStreamInviteDirectly } from '@/test/speckle-helpers/inviteHelper' import { getAdminUsersList } from '@/test/graphql/users' import { buildApolloServer } from '@/app' -import { addLoadersToCtx } from '@/modules/shared/middleware' -import { Roles, AllScopes } from '@/modules/core/helpers/mainConstants' +import { Roles } from '@/modules/core/helpers/mainConstants' import { expect } from 'chai' -import { ApolloServer } from 'apollo-server-express' import { Optional } from '@/modules/shared/helpers/typeHelper' import { wait } from '@speckle/shared' +import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper' // To ensure that the invites are created in the correct order, we need to wait a bit between each creation const WAIT_TIMEOUT = 5 @@ -59,7 +58,7 @@ describe('[Admin users list]', () => { const totalCount = USER_COUNT + SERVER_INVITE_COUNT + STREAM_INVITE_COUNT const totalInviteCount = SERVER_INVITE_COUNT + STREAM_INVITE_COUNT - let apollo: ApolloServer + let apollo: ServerAndContext let orderedUserIds: string[] = [] let orderedInviteIds: string[] = [] @@ -176,16 +175,10 @@ describe('[Admin users list]', () => { orderedInviteIds = await getOrderedInviteIds() orderedUserIds = await getOrderedUserIds() - apollo = await buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: true, - userId: me.id, - role: Roles.Server.Admin, - token: 'asd', - scopes: AllScopes - }) - }) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(me.id!, { role: Roles.Server.Admin }) + } }) after(async () => { diff --git a/packages/server/modules/core/tests/usersGraphql.spec.ts b/packages/server/modules/core/tests/usersGraphql.spec.ts index 9a55c1505..4eb24b195 100644 --- a/packages/server/modules/core/tests/usersGraphql.spec.ts +++ b/packages/server/modules/core/tests/usersGraphql.spec.ts @@ -2,11 +2,6 @@ import { Users } from '@/modules/core/dbSchema' import { BasicTestUser, createTestUsers } from '@/test/authHelper' import { getActiveUser, getOtherUser } from '@/test/graphql/users' import { beforeEachContext, truncateTables } from '@/test/hooks' -import { - buildAuthenticatedApolloServer, - buildUnauthenticatedApolloServer -} from '@/test/serverHelper' -import { ApolloServer } from 'apollo-server-express' import { expect } from 'chai' import { createUser } from '@/modules/core/services/users' import { @@ -20,7 +15,12 @@ import { } from '@/modules/core/repositories/userEmails' import { db } from '@/db/knex' import { before } from 'mocha' -import { testApolloServer } from '@/test/graphqlHelper' +import { + createAuthedTestContext, + createTestContext, + ServerAndContext, + testApolloServer +} from '@/test/graphqlHelper' import { GetActiveUserEmailsDocument } from '@/test/graphql/generated/graphql' import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails' import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing' @@ -29,6 +29,7 @@ import { updateAllInviteTargetsFactory } from '@/modules/serverinvites/repositories/serverInvites' import { requestNewEmailVerification } from '@/modules/emails/services/verification/request' +import { buildApolloServer } from '@/app' const createUserEmail = validateAndCreateUserEmailFactory({ createUserEmail: createUserEmailFactory({ db }), @@ -64,10 +65,13 @@ describe('Users (GraphQL)', () => { }) describe('when unauthenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext before(async () => { - apollo = await buildUnauthenticatedApolloServer() + apollo = { + apollo: await buildApolloServer(), + context: createTestContext() + } }) it('activeUser returns null', async () => { @@ -88,10 +92,13 @@ describe('Users (GraphQL)', () => { }) describe('when authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext before(async () => { - apollo = await buildAuthenticatedApolloServer(me.id) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(me.id) + } }) it('activeUser returns authenticated user info', async () => { diff --git a/packages/server/modules/emails/tests/verifications.spec.ts b/packages/server/modules/emails/tests/verifications.spec.ts index 23adfb08d..38b1485be 100644 --- a/packages/server/modules/emails/tests/verifications.spec.ts +++ b/packages/server/modules/emails/tests/verifications.spec.ts @@ -1,11 +1,6 @@ import { EmailVerifications, UserEmails, Users } from '@/modules/core/dbSchema' import { BasicTestUser, createTestUser, createTestUsers } from '@/test/authHelper' import { buildApp, truncateTables } from '@/test/hooks' -import { - buildAuthenticatedApolloServer, - buildUnauthenticatedApolloServer -} from '@/test/serverHelper' -import { ApolloServer } from 'apollo-server-express' import request from 'supertest' import { expect } from 'chai' import { deleteVerifications, getPendingToken } from '@/modules/emails/repositories' @@ -19,6 +14,12 @@ import { Express } from 'express' import { getUser } from '@/modules/core/repositories/users' import dayjs from 'dayjs' import { EmailSendingServiceMock } from '@/test/mocks/global' +import { + createAuthedTestContext, + createTestContext, + ServerAndContext +} from '@/test/graphqlHelper' +import { buildApolloServer } from '@/app' const mailerMock = EmailSendingServiceMock @@ -71,10 +72,13 @@ describe('Email verifications @emails', () => { }) describe('when authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext before(async () => { - apollo = await buildAuthenticatedApolloServer(userA.id) + apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(userA.id) + } }) it('pending verification is reported correctly', async () => { @@ -101,7 +105,10 @@ describe('Email verifications @emails', () => { describe('and requesting verification', () => { const invokeRequestVerification = async (user: BasicTestUser) => { - const apollo = await buildAuthenticatedApolloServer(user.id) + const apollo = { + apollo: await buildApolloServer(), + context: createAuthedTestContext(user.id) + } return await requestVerification(apollo, {}) } @@ -147,10 +154,13 @@ describe('Email verifications @emails', () => { }) describe('when not authenticated', () => { - let apollo: ApolloServer + let apollo: ServerAndContext before(async () => { - apollo = await buildUnauthenticatedApolloServer() + apollo = { + apollo: await buildApolloServer(), + context: createTestContext() + } }) it('cant request an account verification', async () => { diff --git a/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts b/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts index 54cee48e1..5ff3caa6c 100644 --- a/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts +++ b/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts @@ -57,7 +57,7 @@ describe('FileUploads @fileuploads', () => { after(async () => { process.env['CANONICAL_URL'] = existingCanonicalUrl - await server.close() + await server?.close() }) describe('Uploads files', () => { diff --git a/packages/server/modules/index.js b/packages/server/modules/index.js index e9274696d..d7e33eb98 100644 --- a/packages/server/modules/index.js +++ b/packages/server/modules/index.js @@ -141,7 +141,7 @@ exports.graphDataloadersBuilders = () => { /** * GQL components will be loaded even from disabled modules to avoid schema complexity, so ensure * that resolvers return valid values even if the module is disabled - * @returns {Pick & { directiveBuilders: Record}} + * @returns {Pick & { directiveBuilders: Record}} */ const graphComponents = () => { // Base query and mutation to allow for type extension by modules. diff --git a/packages/server/modules/mocks.ts b/packages/server/modules/mocks.ts index 8c4181304..7a17bba5e 100644 --- a/packages/server/modules/mocks.ts +++ b/packages/server/modules/mocks.ts @@ -83,7 +83,7 @@ const buildBaseConfig = async (): Promise => { /** * Define mocking config in dev env - * https://www.apollographql.com/docs/apollo-server/v3/testing/mocking + * https://www.apollographql.com/docs/apollo-server/testing/mocking */ export async function buildMocksConfig(): Promise<{ mocks: boolean | IMocks diff --git a/packages/server/modules/serverinvites/tests/invites.spec.ts b/packages/server/modules/serverinvites/tests/invites.spec.ts index 8b92c8dbc..fb59efea8 100644 --- a/packages/server/modules/serverinvites/tests/invites.spec.ts +++ b/packages/server/modules/serverinvites/tests/invites.spec.ts @@ -227,7 +227,7 @@ describe('[Stream & Server Invites]', () => { streamId: otherGuyAlreadyInvitedStream.id }) - expect(data?.serverInviteCreate).to.be.not.ok + expect(data?.streamInviteCreate).to.be.not.ok expect(errors).to.be.ok expect(errors!.map((e) => e.message).join('|')).to.contain( 'user is already a collaborator' diff --git a/packages/server/modules/shared/helpers/graphqlHelper.ts b/packages/server/modules/shared/helpers/graphqlHelper.ts index b52624579..18395c1c5 100644 --- a/packages/server/modules/shared/helpers/graphqlHelper.ts +++ b/packages/server/modules/shared/helpers/graphqlHelper.ts @@ -2,6 +2,15 @@ import { AuthContext } from '@/modules/shared/authz' import { base64Decode, base64Encode } from '@/modules/shared/helpers/cryptoHelper' import DataLoader from 'dataloader' import dayjs, { Dayjs } from 'dayjs' +import { ApolloServerErrorCode } from '@apollo/server/errors' +import { GraphQLError } from 'graphql' +import { + BadRequestError, + ForbiddenError, + InvalidArgumentError, + NotFoundError, + UnauthorizedError +} from '@/modules/shared/errors' /** * Encode cursor to turn it into an opaque & obfuscated value @@ -65,3 +74,25 @@ export const defineRequestDataloaders = < ): RequestDataLoadersBuilder => { return builder } + +/** + * Is a lower significance error, caused by user error (and thus - not a bug in our code) + */ +export const isUserGraphqlError = (error: GraphQLError): boolean => { + const userCodes = [ + ForbiddenError.code, + UnauthorizedError.code, + BadRequestError.code, + NotFoundError.code, + InvalidArgumentError.code, + ApolloServerErrorCode.BAD_REQUEST, + ApolloServerErrorCode.BAD_USER_INPUT, + ApolloServerErrorCode.GRAPHQL_PARSE_FAILED, + ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED, + ApolloServerErrorCode.OPERATION_RESOLUTION_FAILURE, + ApolloServerErrorCode.PERSISTED_QUERY_NOT_FOUND, + ApolloServerErrorCode.PERSISTED_QUERY_NOT_SUPPORTED + ] + const code = error.extensions?.code as string + return userCodes.includes(code) +} diff --git a/packages/server/modules/shared/helpers/typeHelper.ts b/packages/server/modules/shared/helpers/typeHelper.ts index 842481c9b..51f67f68c 100644 --- a/packages/server/modules/shared/helpers/typeHelper.ts +++ b/packages/server/modules/shared/helpers/typeHelper.ts @@ -10,6 +10,7 @@ import { AuthContext } from '@/modules/shared/authz' import { Express } from 'express' import { ConditionalKeys, SetRequired } from 'type-fest' import pino from 'pino' +import { BaseContext } from '@apollo/server' export type MarkNullableOptional = SetRequired< Partial, @@ -40,14 +41,15 @@ export type SpeckleModule = Record MaybeAsync } & T -export type GraphQLContext = AuthContext & { - /** - * Request-scoped GraphQL dataloaders - * @see https://github.com/graphql/dataloader - */ - loaders: RequestDataLoaders +export type GraphQLContext = BaseContext & + AuthContext & { + /** + * Request-scoped GraphQL dataloaders + * @see https://github.com/graphql/dataloader + */ + loaders: RequestDataLoaders - log: pino.Logger -} + log: pino.Logger + } export { Nullable, Optional, MaybeNullOrUndefined, MaybeAsync, MaybeFalsy } diff --git a/packages/server/modules/shared/index.js b/packages/server/modules/shared/index.js index bd5f55e9d..3c3c0d9ca 100644 --- a/packages/server/modules/shared/index.js +++ b/packages/server/modules/shared/index.js @@ -1,6 +1,4 @@ -'use strict' const knex = require(`@/db/knex`) -const { ForbiddenError } = require('apollo-server-express') const { pubsub, StreamSubscriptions, @@ -17,6 +15,7 @@ const { isResourceAllowed } = require('@/modules/core/helpers/token') const db = require('@/db/knex') +const { ForbiddenError } = require('@/modules/shared/errors') const ServerAcl = () => ServerAclSchema.knex() /** diff --git a/packages/server/modules/shared/middleware/index.ts b/packages/server/modules/shared/middleware/index.ts index 238f7ddf8..02c07d0ae 100644 --- a/packages/server/modules/shared/middleware/index.ts +++ b/packages/server/modules/shared/middleware/index.ts @@ -156,7 +156,7 @@ export async function buildContext({ cleanLoadersEarly }: { req: MaybeNullOrUndefined - token: Nullable + token?: Nullable cleanLoadersEarly?: boolean }): Promise { const ctx = diff --git a/packages/server/modules/webhooks/graph/resolvers/webhooks.js b/packages/server/modules/webhooks/graph/resolvers/webhooks.js index 2676b9485..3bbe8869f 100644 --- a/packages/server/modules/webhooks/graph/resolvers/webhooks.js +++ b/packages/server/modules/webhooks/graph/resolvers/webhooks.js @@ -1,5 +1,3 @@ -const { ForbiddenError } = require('apollo-server-express') - const { authorizeResolver } = require('@/modules/shared') const { deleteWebhook, @@ -10,6 +8,7 @@ const { const { Roles } = require('@speckle/shared') const { getWebhookByIdFactory } = require('../../repositories/webhooks') const { db } = require('@/db/knex') +const { ForbiddenError } = require('@/modules/shared/errors') const streamWebhooksResolver = async (parent, args, context) => { await authorizeResolver( diff --git a/packages/server/modules/workspaces/graph/directives/hasWorkspaceRole.ts b/packages/server/modules/workspaces/graph/directives/hasWorkspaceRole.ts index 9a05948e1..cd721cbf6 100644 --- a/packages/server/modules/workspaces/graph/directives/hasWorkspaceRole.ts +++ b/packages/server/modules/workspaces/graph/directives/hasWorkspaceRole.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return */ import { GraphqlDirectiveBuilder } from '@/modules/core/graph/helpers/directiveHelper' import { authorizeResolver } from '@/modules/shared' import { ForbiddenError } from '@/modules/shared/errors' diff --git a/packages/server/modules/workspaces/tests/helpers/graphql.ts b/packages/server/modules/workspaces/tests/helpers/graphql.ts index 0ede291a5..abd042746 100644 --- a/packages/server/modules/workspaces/tests/helpers/graphql.ts +++ b/packages/server/modules/workspaces/tests/helpers/graphql.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import { gql } from 'graphql-tag' export const basicWorkspaceFragment = gql` fragment BasicWorkspace on Workspace { diff --git a/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts index 9545855f7..e0e439134 100644 --- a/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts +++ b/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts @@ -56,7 +56,6 @@ import { createTestStreams, leaveStream } from '@/test/speckle-helpers/streamHelper' -import { ForbiddenError } from 'apollo-server-express' import { Workspaces } from '@/modules/workspaces/helpers/db' import { generateRegistrationParams, @@ -78,6 +77,7 @@ import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/em import { createRandomPassword } from '@/modules/core/helpers/testHelpers' import { addOrUpdateStreamCollaborator } from '@/modules/core/services/streams/streamAccessService' import { WorkspaceProtectedError } from '@/modules/workspaces/errors/workspace' +import { ForbiddenError } from '@/modules/shared/errors' enum InviteByTarget { Email = 'email', diff --git a/packages/server/package.json b/packages/server/package.json index 2b810de8c..57f7eb858 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -38,15 +38,16 @@ }, "dependencies": { "@apollo/client": "^3.7.0", + "@apollo/server": "^4.11.0", "@aws-sdk/client-s3": "^3.276.0", "@aws-sdk/lib-storage": "^3.100.0", "@godaddy/terminus": "^4.9.0", - "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/mock": "^9.0.4", + "@graphql-tools/schema": "^10.0.6", "@mailchimp/mailchimp_marketing": "^3.0.80", "@speckle/objectloader": "workspace:^", "@speckle/shared": "workspace:^", "ajv": "^8.12.0", - "apollo-server-express": "^3.10.2", "bcrypt": "^5.0.0", "bull": "^4.8.5", "busboy": "^1.4.0", @@ -65,10 +66,10 @@ "express-async-errors": "^3.1.1", "express-prom-bundle": "^6.6.0", "express-session": "^1.17.1", - "graphql": "^15", "graphql-redis-subscriptions": "^2.2.2", "graphql-scalars": "^1.18.0", "graphql-subscriptions": "^2.0.0", + "graphql-tag": "^2.12.6", "ioredis": "^5.2.2", "jose": "^5.6.3", "knex": "^2.4.1", @@ -129,6 +130,7 @@ "@types/compression": "^1.7.2", "@types/connect-redis": "^0.0.23", "@types/cookie-parser": "^1.4.7", + "@types/cors": "^2.8.17", "@types/debug": "^4.1.7", "@types/deep-equal-in-any-order": "^1.0.1", "@types/ejs": "^3.1.1", @@ -159,7 +161,6 @@ "@types/zxcvbn": "^4.4.1", "@typescript-eslint/eslint-plugin": "^5.39.0", "@typescript-eslint/parser": "^5.39.0", - "apollo-server-plugin-base": "^3.7.2", "axios": "^1.7.4", "chai": "^4.2.0", "chai-as-promised": "^7.1.2", @@ -170,6 +171,7 @@ "enforce-unique": "^1.3.0", "eslint": "^8.11.0", "eslint-config-prettier": "^8.5.0", + "graphql": "^16.6.0", "http-proxy-middleware": "v3.0.0-beta.0", "ioredis-mock": "^8.9.0", "mocha": "^10.1.0", diff --git a/packages/server/test/graphql/accessRequests.ts b/packages/server/test/graphql/accessRequests.ts index a3184e60f..111f51f23 100644 --- a/packages/server/test/graphql/accessRequests.ts +++ b/packages/server/test/graphql/accessRequests.ts @@ -10,8 +10,8 @@ import { UseStreamAccessRequestMutation, UseStreamAccessRequestMutationVariables } from '@/test/graphql/generated/graphql' -import { executeOperation } from '@/test/graphqlHelper' -import { ApolloServer, gql } from 'apollo-server-express' +import { executeOperation, ExecuteOperationServer } from '@/test/graphqlHelper' +import { gql } from 'graphql-tag' const basicStreamAccessRequestFragment = gql` fragment BasicStreamAccessRequestFields on StreamAccessRequest { @@ -89,7 +89,7 @@ const useStreamAccessRequestMutation = gql` ` export const createStreamAccessRequest = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: CreateStreamAccessRequestMutationVariables ) => executeOperation< @@ -98,7 +98,7 @@ export const createStreamAccessRequest = ( >(apollo, createStreamAccessRequestMutation, variables) export const getStreamAccessRequest = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetStreamAccessRequestQueryVariables ) => executeOperation( @@ -108,7 +108,7 @@ export const getStreamAccessRequest = ( ) export const getFullStreamAccessRequest = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetFullStreamAccessRequestQueryVariables ) => executeOperation< @@ -117,7 +117,7 @@ export const getFullStreamAccessRequest = ( >(apollo, getFullStreamAccessRequestQuery, variables) export const getPendingStreamAccessRequests = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetPendingStreamAccessRequestsQueryVariables ) => executeOperation< @@ -126,7 +126,7 @@ export const getPendingStreamAccessRequests = ( >(apollo, getPendingStreamAccessRequestsQuery, variables) export const useStreamAccessRequest = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: UseStreamAccessRequestMutationVariables ) => executeOperation< diff --git a/packages/server/test/graphql/apiTokens.ts b/packages/server/test/graphql/apiTokens.ts index 92235f43a..6d66c78dc 100644 --- a/packages/server/test/graphql/apiTokens.ts +++ b/packages/server/test/graphql/apiTokens.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import { gql } from 'graphql-tag' export const createTokenMutation = gql` mutation CreateToken($token: ApiTokenCreateInput!) { diff --git a/packages/server/test/graphql/automate.ts b/packages/server/test/graphql/automate.ts index 7ff0d65ee..2d041bb45 100644 --- a/packages/server/test/graphql/automate.ts +++ b/packages/server/test/graphql/automate.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import { gql } from 'graphql-tag' export const automateFunctionFragment = gql` fragment TestAutomateFunction on AutomateFunction { diff --git a/packages/server/test/graphql/comments.ts b/packages/server/test/graphql/comments.ts index 29cdb435d..706499e94 100644 --- a/packages/server/test/graphql/comments.ts +++ b/packages/server/test/graphql/comments.ts @@ -8,8 +8,8 @@ import { GetCommentsQuery, GetCommentsQueryVariables } from '@/test/graphql/generated/graphql' -import { executeOperation } from '@/test/graphqlHelper' -import { ApolloServer, gql } from 'apollo-server-express' +import { executeOperation, ExecuteOperationServer } from '@/test/graphqlHelper' +import { gql } from 'graphql-tag' const commentWithRepliesFragment = gql` fragment CommentWithReplies on Comment { @@ -76,7 +76,7 @@ const getCommentsQuery = gql` ` export const createComment = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: CreateCommentMutationVariables ) => executeOperation( @@ -86,7 +86,7 @@ export const createComment = ( ) export const createReply = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: CreateReplyMutationVariables ) => executeOperation( @@ -95,7 +95,10 @@ export const createReply = ( variables ) -export const getComment = (apollo: ApolloServer, variables: GetCommentQueryVariables) => +export const getComment = ( + apollo: ExecuteOperationServer, + variables: GetCommentQueryVariables +) => executeOperation( apollo, getCommentQuery, @@ -103,7 +106,7 @@ export const getComment = (apollo: ApolloServer, variables: GetCommentQueryVaria ) export const getComments = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetCommentsQueryVariables ) => executeOperation( diff --git a/packages/server/test/graphql/commits.ts b/packages/server/test/graphql/commits.ts index 841f5ffc1..94aa8656f 100644 --- a/packages/server/test/graphql/commits.ts +++ b/packages/server/test/graphql/commits.ts @@ -10,8 +10,8 @@ import { ReadStreamBranchCommitsQuery, ReadStreamBranchCommitsQueryVariables } from '@/test/graphql/generated/graphql' -import { executeOperation } from '@/test/graphqlHelper' -import { ApolloServer, gql } from 'apollo-server-express' +import { executeOperation, ExecuteOperationServer } from '@/test/graphqlHelper' +import gql from 'graphql-tag' const baseCommitFieldsFragment = gql` fragment BaseCommitFields on Commit { @@ -108,7 +108,7 @@ const deleteCommitsMutation = gql` ` export const readOwnCommits = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: ReadOwnCommitsQueryVariables ) => executeOperation( @@ -118,7 +118,7 @@ export const readOwnCommits = ( ) export const readOtherUsersCommits = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: ReadOtherUsersCommitsQueryVariables ) => executeOperation( @@ -128,7 +128,7 @@ export const readOtherUsersCommits = ( ) export const readStreamBranchCommits = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: ReadStreamBranchCommitsQueryVariables ) => executeOperation( @@ -138,7 +138,7 @@ export const readStreamBranchCommits = ( ) export const moveCommits = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: MoveCommitsMutationVariables ) => executeOperation( @@ -148,7 +148,7 @@ export const moveCommits = ( ) export const deleteCommits = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: DeleteCommitsMutationVariables ) => executeOperation( diff --git a/packages/server/test/graphql/models.ts b/packages/server/test/graphql/models.ts index 82459405b..5a38d3ddd 100644 --- a/packages/server/test/graphql/models.ts +++ b/packages/server/test/graphql/models.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' export const createProjectModelQuery = gql` mutation CreateProjectModel($input: CreateModelInput!) { diff --git a/packages/server/test/graphql/projectAccessRequests.ts b/packages/server/test/graphql/projectAccessRequests.ts index 751f7813f..e4e81664a 100644 --- a/packages/server/test/graphql/projectAccessRequests.ts +++ b/packages/server/test/graphql/projectAccessRequests.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' const basicProjectAccessRequestFragment = gql` fragment BasicProjectAccessRequestFields on ProjectAccessRequest { diff --git a/packages/server/test/graphql/projectComments.ts b/packages/server/test/graphql/projectComments.ts index 3ee81c4a0..97e868f9f 100644 --- a/packages/server/test/graphql/projectComments.ts +++ b/packages/server/test/graphql/projectComments.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' export const createProjectCommentMutation = gql` mutation CreateProjectComment($input: CreateCommentInput!) { diff --git a/packages/server/test/graphql/projects.ts b/packages/server/test/graphql/projects.ts index 0212a4b4c..999b4e5b8 100644 --- a/packages/server/test/graphql/projects.ts +++ b/packages/server/test/graphql/projects.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' export const basicProjectFieldsFragment = gql` fragment BasicProjectFields on Project { diff --git a/packages/server/test/graphql/serverInvites.ts b/packages/server/test/graphql/serverInvites.ts index d166a5623..3220913a5 100644 --- a/packages/server/test/graphql/serverInvites.ts +++ b/packages/server/test/graphql/serverInvites.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' export const createServerInviteMutation = gql` mutation CreateServerInvite($input: ServerInviteCreateInput!) { diff --git a/packages/server/test/graphql/streams.ts b/packages/server/test/graphql/streams.ts index 5637758b0..a4035088a 100644 --- a/packages/server/test/graphql/streams.ts +++ b/packages/server/test/graphql/streams.ts @@ -14,8 +14,8 @@ import { GetLimitedUserStreamsQuery, GetLimitedUserStreamsQueryVariables } from '@/test/graphql/generated/graphql' -import { executeOperation } from '@/test/graphqlHelper' -import { ApolloServer, gql } from 'apollo-server-express' +import { executeOperation, ExecuteOperationServer } from '@/test/graphqlHelper' +import gql from 'graphql-tag' export const basicStreamFieldsFragment = gql` fragment BasicStreamFields on Stream { @@ -129,7 +129,7 @@ const getLimitedUserStreamsQuery = gql` ` export const leaveStream = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: LeaveStreamMutationVariables ) => executeOperation( @@ -139,7 +139,7 @@ export const leaveStream = ( ) export const createStream = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: CreateStreamMutationVariables ) => executeOperation( @@ -149,7 +149,7 @@ export const createStream = ( ) export const updateStream = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: UpdateStreamMutationVariables ) => executeOperation( @@ -158,7 +158,10 @@ export const updateStream = ( variables ) -export const readStream = (apollo: ApolloServer, variables: ReadStreamQueryVariables) => +export const readStream = ( + apollo: ExecuteOperationServer, + variables: ReadStreamQueryVariables +) => executeOperation( apollo, readStreamQuery, @@ -166,7 +169,7 @@ export const readStream = (apollo: ApolloServer, variables: ReadStreamQueryVaria ) export const readDiscoverableStreams = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: ReadDiscoverableStreamsQueryVariables ) => executeOperation( @@ -176,7 +179,7 @@ export const readDiscoverableStreams = ( ) export const getUserStreams = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetUserStreamsQueryVariables ) => executeOperation( @@ -186,7 +189,7 @@ export const getUserStreams = ( ) export const getLimitedUserStreams = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetLimitedUserStreamsQueryVariables ) => executeOperation( diff --git a/packages/server/test/graphql/userEmails.ts b/packages/server/test/graphql/userEmails.ts index 68b124be2..9543f9cb7 100644 --- a/packages/server/test/graphql/userEmails.ts +++ b/packages/server/test/graphql/userEmails.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' export const userWithEmailsFragment = gql` fragment UserWithEmails on User { diff --git a/packages/server/test/graphql/users.ts b/packages/server/test/graphql/users.ts index 64cec806b..1421600fa 100644 --- a/packages/server/test/graphql/users.ts +++ b/packages/server/test/graphql/users.ts @@ -1,4 +1,3 @@ -import { ApolloServer, gql } from 'apollo-server-express' import { GetActiveUserQuery, GetActiveUserQueryVariables, @@ -11,7 +10,8 @@ import { RequestVerificationMutation, RequestVerificationMutationVariables } from '@/test/graphql/generated/graphql' -import { executeOperation } from '@/test/graphqlHelper' +import { executeOperation, ExecuteOperationServer } from '@/test/graphqlHelper' +import gql from 'graphql-tag' const baseUserFieldsFragment = gql` fragment BaseUserFields on User { @@ -99,14 +99,14 @@ const requestVerificationMutation = gql` } ` -export const getActiveUser = (apollo: ApolloServer) => +export const getActiveUser = (apollo: ExecuteOperationServer) => executeOperation( apollo, getActiveUserQuery ) export const getOtherUser = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetOtherUserQueryVariables ) => executeOperation( @@ -116,7 +116,7 @@ export const getOtherUser = ( ) export async function getAdminUsersList( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetAdminUsersQueryVariables ) { return await executeOperation( @@ -127,7 +127,7 @@ export async function getAdminUsersList( } export const getPendingEmailVerificationStatus = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables: GetPendingEmailVerificationStatusQueryVariables ) => executeOperation< @@ -136,7 +136,7 @@ export const getPendingEmailVerificationStatus = ( >(apollo, getPendingEmailVerificationStatusQuery, variables) export const requestVerification = ( - apollo: ApolloServer, + apollo: ExecuteOperationServer, variables?: RequestVerificationMutationVariables ) => executeOperation( diff --git a/packages/server/test/graphql/versions.ts b/packages/server/test/graphql/versions.ts index c8ab20e89..1f357b07c 100644 --- a/packages/server/test/graphql/versions.ts +++ b/packages/server/test/graphql/versions.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' export const createProjectVersionMutation = gql` mutation CreateProjectVersion($input: CreateVersionInput!) { diff --git a/packages/server/test/graphql/workspaces.ts b/packages/server/test/graphql/workspaces.ts index 357ac63f8..dc83906fb 100644 --- a/packages/server/test/graphql/workspaces.ts +++ b/packages/server/test/graphql/workspaces.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server-express' +import gql from 'graphql-tag' export const workspaceFragment = gql` fragment TestWorkspace on Workspace { diff --git a/packages/server/test/graphqlHelper.ts b/packages/server/test/graphqlHelper.ts index cdafa6232..0772d2015 100644 --- a/packages/server/test/graphqlHelper.ts +++ b/packages/server/test/graphqlHelper.ts @@ -1,36 +1,77 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ApolloServer } from 'apollo-server-express' -import { GraphQLResponse } from 'apollo-server-types' import { DocumentNode } from 'graphql' -import { GraphQLContext, Nullable } from '@/modules/shared/helpers/typeHelper' +import { GraphQLContext } from '@/modules/shared/helpers/typeHelper' import { TypedDocumentNode } from '@graphql-typed-document-node/core' import { buildApolloServer } from '@/app' import { addLoadersToCtx } from '@/modules/shared/middleware' -import { buildUnauthenticatedApolloServer } from '@/test/serverHelper' import { Roles } from '@/modules/core/helpers/mainConstants' -import { AllScopes } from '@speckle/shared' +import { AllScopes, MaybeNullOrUndefined } from '@speckle/shared' import { expect } from 'chai' +import { ApolloServer, GraphQLResponse } from '@apollo/server' -type TypedGraphqlResponse> = GraphQLResponse & { - data: Nullable +type TypedGraphqlResponse> = GraphQLResponse + +export const getResponseResults = >( + res: GraphQLResponse +) => { + const body = res.body + if (body.kind === 'incremental') { + return { + data: body.initialResult.data as MaybeNullOrUndefined, + errors: body.initialResult.errors + } + } else { + return { + data: body.singleResult.data as MaybeNullOrUndefined, + errors: body.singleResult.errors + } + } } +export type ExecuteOperationResponse> = { + res: TypedGraphqlResponse +} & ReturnType> + +export type ServerAndContext = { + apollo: ApolloServer + context?: MaybeNullOrUndefined +} +export type ExecuteOperationServer = ServerAndContext + /** * Use this to execute GQL operations from tests against an Apollo instance and get * a properly typed response + * @deprecated Use `testApolloServer` instead */ export async function executeOperation< R extends Record = Record, V extends Record = Record >( - apollo: ApolloServer, + apollo: ExecuteOperationServer, query: DocumentNode, - variables?: V -): Promise> { - return (await apollo.executeOperation({ - query, - variables - })) as TypedGraphqlResponse + variables?: V, + context?: GraphQLContext +): Promise> { + const server: ApolloServer = apollo.apollo + const contextValue = context || apollo.context || createTestContext() + + const res = (await server.executeOperation( + { + query, + variables + }, + { contextValue } + )) as TypedGraphqlResponse + + const results = getResponseResults(res) + + // Replicate clearing dataloaders after each request + contextValue.loaders.clearAll() + + return { + ...results, + res + } } /** @@ -51,6 +92,19 @@ export const createTestContext = ( ...(ctx || {}) }) +export const createAuthedTestContext = ( + userId: string, + ctxOverrides?: Partial[0]> +): GraphQLContext => + addLoadersToCtx({ + auth: true, + userId, + role: Roles.Server.User, + token: 'asd', + scopes: AllScopes, + ...(ctxOverrides || {}) + }) + /** * Utilities that make it easier to test against an Apollo Server instance */ @@ -61,21 +115,16 @@ export const testApolloServer = async (params?: { */ authUserId?: string }) => { - const initialCtx: GraphQLContext | undefined = params?.authUserId - ? createTestContext({ - auth: true, - userId: params.authUserId, - role: Roles.Server.User, - token: 'asd', - scopes: AllScopes - }) - : params?.context + let baseCtx: GraphQLContext + if (params?.authUserId) { + baseCtx = createAuthedTestContext(params.authUserId) + } else if (params?.context) { + baseCtx = params.context + } else { + baseCtx = createTestContext() + } - const instance = initialCtx - ? await buildApolloServer({ - context: initialCtx - }) - : await buildUnauthenticatedApolloServer() + const instance = await buildApolloServer() /** * Execute an operation against Apollo and get a properly typed response @@ -96,26 +145,31 @@ export const testApolloServer = async (params?: { */ assertNoErrors: boolean }> - ): Promise> => { - const realInstance = options?.context - ? await buildApolloServer({ - context: createTestContext({ - ...(initialCtx || {}), - ...options.context - }) + ): Promise> => { + const ctx = options?.context + ? createTestContext({ + ...(baseCtx || {}), + ...options.context }) - : instance + : baseCtx - const res = (await realInstance.executeOperation({ - query, - variables - })) as TypedGraphqlResponse + const res = (await instance.executeOperation( + { + query, + variables + }, + { contextValue: ctx } + )) as TypedGraphqlResponse if (options?.assertNoErrors) { expect(res).to.not.haveGraphQLErrors() } - return res + const results = getResponseResults(res) + return { + ...results, + res + } } return { execute, server: instance } diff --git a/packages/server/test/plugins/graphql.ts b/packages/server/test/plugins/graphql.ts index 7234d2a9b..d78927071 100644 --- a/packages/server/test/plugins/graphql.ts +++ b/packages/server/test/plugins/graphql.ts @@ -1,5 +1,5 @@ import { Optional } from '@/modules/shared/helpers/typeHelper' -import { GraphQLResponse } from 'apollo-server-core' +import { ExecuteOperationResponse } from '@/test/graphqlHelper' import { AssertionError } from 'chai' import { isString } from 'lodash' @@ -21,7 +21,8 @@ const graphqlChaiPlugin: Chai.ChaiPlugin = (_chai, utils) => { Assertion.prototype, 'haveGraphQLErrors', function ( - this: ChaiPluginThis, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this: ChaiPluginThis>, messageOrOptions?: string | ({ message: string } | { code: string }) ) { const message = isString(messageOrOptions) diff --git a/packages/server/test/serverHelper.ts b/packages/server/test/serverHelper.ts index 5904b6922..5e9aea0f6 100644 --- a/packages/server/test/serverHelper.ts +++ b/packages/server/test/serverHelper.ts @@ -1,40 +1,5 @@ -import { buildApolloServer } from '@/app' -import { Roles, AllScopes } from '@/modules/core/helpers/mainConstants' -import { addLoadersToCtx } from '@/modules/shared/middleware' import net from 'net' -/** - * Build an ApolloServer instance with an authenticated context - */ -export function buildAuthenticatedApolloServer( - userId: string, - role = Roles.Server.User, - scopes = AllScopes -) { - return buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: true, - userId, - role, - token: 'asd', - scopes - }) - }) -} - -/** - * Build an unauthenticated ApolloServer instance - */ -export function buildUnauthenticatedApolloServer() { - return buildApolloServer({ - context: () => - addLoadersToCtx({ - auth: false - }) - }) -} - export async function getFreeServerPort() { return new Promise((res) => { const srv = net.createServer() diff --git a/yarn.lock b/yarn.lock index 8d51828b4..f68f61355 100644 --- a/yarn.lock +++ b/yarn.lock @@ -85,6 +85,15 @@ __metadata: languageName: node linkType: hard +"@apollo/cache-control-types@npm:^1.0.3": + version: 1.0.3 + resolution: "@apollo/cache-control-types@npm:1.0.3" + peerDependencies: + graphql: 14.x || 15.x || 16.x + checksum: 10/a588e52bfa51e37a1dcd667469c827cbd1145df131650478fa7c7e6f9b01eb71ce9147f94be60c5b3ee0d4f83fd3304da1a78a342ae254c12cfc18e6e3f1e615 + languageName: node + linkType: hard + "@apollo/client@npm:^3.6.6, @apollo/client@npm:^3.7.0, @apollo/client@npm:^3.7.14, @apollo/client@npm:^3.8.0, @apollo/client@npm:^3.9.6": version: 3.9.7 resolution: "@apollo/client@npm:3.9.7" @@ -122,9 +131,9 @@ __metadata: languageName: node linkType: hard -"@apollo/protobufjs@npm:1.2.4": - version: 1.2.4 - resolution: "@apollo/protobufjs@npm:1.2.4" +"@apollo/protobufjs@npm:1.2.7": + version: 1.2.7 + resolution: "@apollo/protobufjs@npm:1.2.7" dependencies: "@protobufjs/aspromise": "npm:^1.1.2" "@protobufjs/base64": "npm:^1.1.2" @@ -137,36 +146,11 @@ __metadata: "@protobufjs/pool": "npm:^1.1.0" "@protobufjs/utf8": "npm:^1.1.0" "@types/long": "npm:^4.0.0" - "@types/node": "npm:^10.1.0" long: "npm:^4.0.0" bin: apollo-pbjs: bin/pbjs apollo-pbts: bin/pbts - checksum: 10/9cd57a469f5dd4c7901060019cec2ff31e1c3e5bec9eb2f0eaa22af166a7aae605c7976204810818614fb1f7fba36dd0e3b0197b8187af7f427aacd4c30b00f5 - languageName: node - linkType: hard - -"@apollo/protobufjs@npm:1.2.6": - version: 1.2.6 - resolution: "@apollo/protobufjs@npm:1.2.6" - dependencies: - "@protobufjs/aspromise": "npm:^1.1.2" - "@protobufjs/base64": "npm:^1.1.2" - "@protobufjs/codegen": "npm:^2.0.4" - "@protobufjs/eventemitter": "npm:^1.1.0" - "@protobufjs/fetch": "npm:^1.1.0" - "@protobufjs/float": "npm:^1.0.2" - "@protobufjs/inquire": "npm:^1.1.0" - "@protobufjs/path": "npm:^1.1.2" - "@protobufjs/pool": "npm:^1.1.0" - "@protobufjs/utf8": "npm:^1.1.0" - "@types/long": "npm:^4.0.0" - "@types/node": "npm:^10.1.0" - long: "npm:^4.0.0" - bin: - apollo-pbjs: bin/pbjs - apollo-pbts: bin/pbts - checksum: 10/a134200f943983edbb46cdf358a5e4364599870dc9be4dbce043dfa842903cb0ad4f16e1d99f06f49cf00fbfb6ad95dbac101c9187ce723c968eeb27e6c66822 + checksum: 10/9b2c2d5daa5221397bc9cf37f3fa8a45dc6f217783d0fe51eca12895f88f8a5d1b66efba2e288657a1c2da5b2e20fe0eb649a440ceeb30bfc5a3af75ccea832d languageName: node linkType: hard @@ -185,101 +169,171 @@ __metadata: languageName: node linkType: hard -"@apollo/utils.dropunuseddefinitions@npm:^1.1.0": - version: 1.1.0 - resolution: "@apollo/utils.dropunuseddefinitions@npm:1.1.0" - peerDependencies: - graphql: 14.x || 15.x || 16.x - checksum: 10/b66e07086ea65bcb94d84cfd5e6d90d0406c4e7f602c9a5e793c2001273380a4f61c287f60ee1d81d47d49d3a62ef3f0afb8049243540d3021ff445869124094 - languageName: node - linkType: hard - -"@apollo/utils.keyvaluecache@npm:^1.0.1": - version: 1.0.1 - resolution: "@apollo/utils.keyvaluecache@npm:1.0.1" +"@apollo/server-gateway-interface@npm:^1.1.1": + version: 1.1.1 + resolution: "@apollo/server-gateway-interface@npm:1.1.1" dependencies: - "@apollo/utils.logger": "npm:^1.0.0" + "@apollo/usage-reporting-protobuf": "npm:^4.1.1" + "@apollo/utils.fetcher": "npm:^2.0.0" + "@apollo/utils.keyvaluecache": "npm:^2.1.0" + "@apollo/utils.logger": "npm:^2.0.0" + peerDependencies: + graphql: 14.x || 15.x || 16.x + checksum: 10/af0e95399297aa403c32ffff08c6dfa91a70aae73d5954f36e357f045cdb7e89f3bb4c3e70816d244f8f18af21d257bc79e934dd8bbaa1214c5f6d42a6a825d0 + languageName: node + linkType: hard + +"@apollo/server@npm:^4.11.0": + version: 4.11.0 + resolution: "@apollo/server@npm:4.11.0" + dependencies: + "@apollo/cache-control-types": "npm:^1.0.3" + "@apollo/server-gateway-interface": "npm:^1.1.1" + "@apollo/usage-reporting-protobuf": "npm:^4.1.1" + "@apollo/utils.createhash": "npm:^2.0.0" + "@apollo/utils.fetcher": "npm:^2.0.0" + "@apollo/utils.isnodelike": "npm:^2.0.0" + "@apollo/utils.keyvaluecache": "npm:^2.1.0" + "@apollo/utils.logger": "npm:^2.0.0" + "@apollo/utils.usagereporting": "npm:^2.1.0" + "@apollo/utils.withrequired": "npm:^2.0.0" + "@graphql-tools/schema": "npm:^9.0.0" + "@types/express": "npm:^4.17.13" + "@types/express-serve-static-core": "npm:^4.17.30" + "@types/node-fetch": "npm:^2.6.1" + async-retry: "npm:^1.2.1" + cors: "npm:^2.8.5" + express: "npm:^4.17.1" + loglevel: "npm:^1.6.8" lru-cache: "npm:^7.10.1" - checksum: 10/3a5ac26b3db86076cfa9154e535563993da5d541449a2403dfb5e2fca6fab03af3ad71ed375fe051b301b856d1bb2089f94e8aede4ff34d05828a38830c6c593 + negotiator: "npm:^0.6.3" + node-abort-controller: "npm:^3.1.1" + node-fetch: "npm:^2.6.7" + uuid: "npm:^9.0.0" + whatwg-mimetype: "npm:^3.0.0" + peerDependencies: + graphql: ^16.6.0 + checksum: 10/2f4d20dfcab2261d7c090d81bebccfd56ead1b9740e964fb2b7bd65058bff57b9bc175c4a3b2eb0c7d5fc8a7cc4ea2685ac6d0d2a147244964fa4c432db7c30f languageName: node linkType: hard -"@apollo/utils.logger@npm:^1.0.0": - version: 1.0.0 - resolution: "@apollo/utils.logger@npm:1.0.0" - checksum: 10/9be2b269d6d1cf2235c7b49a5edbb36c87589facf79516521df66d0c782709b7301a1365693b6e15d77f482d231bbb0fea4c2ae42faac7068cc4e014ce338c68 +"@apollo/usage-reporting-protobuf@npm:^4.1.0, @apollo/usage-reporting-protobuf@npm:^4.1.1": + version: 4.1.1 + resolution: "@apollo/usage-reporting-protobuf@npm:4.1.1" + dependencies: + "@apollo/protobufjs": "npm:1.2.7" + checksum: 10/07679e0058d0f67200bcbb05405697d4052dd6d921b8ed717878d75c60efe5af4dd1c387f9e72be17d050967b3c334ee3eab8954c4dc40aed0f1013eb30fb251 languageName: node linkType: hard -"@apollo/utils.printwithreducedwhitespace@npm:^1.1.0": - version: 1.1.0 - resolution: "@apollo/utils.printwithreducedwhitespace@npm:1.1.0" +"@apollo/utils.createhash@npm:^2.0.0": + version: 2.0.1 + resolution: "@apollo/utils.createhash@npm:2.0.1" + dependencies: + "@apollo/utils.isnodelike": "npm:^2.0.1" + sha.js: "npm:^2.4.11" + checksum: 10/9e3ba58fd44f7900133a2219b0b66c0656a9c729f7a2ed1a459af8f4149925f0602d9766e57a0cc2acb8d24623f5c34ebad0faac0004cd59060fd6b1c91d5029 + languageName: node + linkType: hard + +"@apollo/utils.dropunuseddefinitions@npm:^2.0.1": + version: 2.0.1 + resolution: "@apollo/utils.dropunuseddefinitions@npm:2.0.1" peerDependencies: graphql: 14.x || 15.x || 16.x - checksum: 10/86536751681c64f35a2d37b0c2f69a39d91ea0e4f0c3c993d9f76fa109f85e9d306e6994bd6e38eef1e4e5b83245125aaa125ecc94e185d90b3255f06a538503 + checksum: 10/c12166f2551fb44045a8210317b7776abc263136bd07bfe3c6eecdb050468590fc73e524efc437cad21cc4cfcd1efc3e110285025150c2073a4b303934898ac1 languageName: node linkType: hard -"@apollo/utils.removealiases@npm:1.0.0": - version: 1.0.0 - resolution: "@apollo/utils.removealiases@npm:1.0.0" +"@apollo/utils.fetcher@npm:^2.0.0": + version: 2.0.1 + resolution: "@apollo/utils.fetcher@npm:2.0.1" + checksum: 10/e173d215c3544dade7b4a08733234d5180973c79e8e738e9e2530f2067e8731a5faa7f15176f4ca91f3cc95a4c70166a686c7382a6c6100f56ad5befcd613f9f + languageName: node + linkType: hard + +"@apollo/utils.isnodelike@npm:^2.0.0, @apollo/utils.isnodelike@npm:^2.0.1": + version: 2.0.1 + resolution: "@apollo/utils.isnodelike@npm:2.0.1" + checksum: 10/c2e858186a60cccb7e4fc53e8b97b2a4d5470cd4975ad9cccd29e57a23eff1aa3a0c03edceb13c423632224ce2c327c6f1bb8bd77dc3fb039316bba5750536ec + languageName: node + linkType: hard + +"@apollo/utils.keyvaluecache@npm:^2.1.0": + version: 2.1.1 + resolution: "@apollo/utils.keyvaluecache@npm:2.1.1" + dependencies: + "@apollo/utils.logger": "npm:^2.0.1" + lru-cache: "npm:^7.14.1" + checksum: 10/9a6bc7c4645415329a93e77861cb1a9874b2171b741a3a667c277c6339f2ba46fb40011982e7b0993b118af1cc02e59e58fcbe7033ca6216cefec01e7b8eeda6 + languageName: node + linkType: hard + +"@apollo/utils.logger@npm:^2.0.0, @apollo/utils.logger@npm:^2.0.1": + version: 2.0.1 + resolution: "@apollo/utils.logger@npm:2.0.1" + checksum: 10/f975c81fcc7e54669b975031349f292930dc4cc3dd6bdc58bc7fe2159e0398a7d18b28860ee324c23722b005848e258094a143d20f6989fde5837379240b0066 + languageName: node + linkType: hard + +"@apollo/utils.printwithreducedwhitespace@npm:^2.0.1": + version: 2.0.1 + resolution: "@apollo/utils.printwithreducedwhitespace@npm:2.0.1" peerDependencies: graphql: 14.x || 15.x || 16.x - checksum: 10/fda30ad4ee1fbf012e4289b9963b8b75a102eadbdfa5e558dc923cfc68df42eff6e232dc20c34b7e7563e5aac7ae3781d17919cd8f5eccb90c4225a274b2af93 + checksum: 10/16cd191e66f3801b15deb581426cd1f55066bb824c32d63fe9de9c255bea2e2b6ee1ffc88873607830d2df0f3b4d9a14c707b709f205062e21a502f08f40d513 languageName: node linkType: hard -"@apollo/utils.sortast@npm:^1.1.0": - version: 1.1.0 - resolution: "@apollo/utils.sortast@npm:1.1.0" +"@apollo/utils.removealiases@npm:2.0.1": + version: 2.0.1 + resolution: "@apollo/utils.removealiases@npm:2.0.1" + peerDependencies: + graphql: 14.x || 15.x || 16.x + checksum: 10/2f3f925b239bce49fe9d80bb9fbb551992c8d9180af160e780faf1c88971a30ef16b842e82e1f27a0e1f8c649af0a442ef95f6838d4cde6148939ec73d9464f6 + languageName: node + linkType: hard + +"@apollo/utils.sortast@npm:^2.0.1": + version: 2.0.1 + resolution: "@apollo/utils.sortast@npm:2.0.1" dependencies: lodash.sortby: "npm:^4.7.0" peerDependencies: graphql: 14.x || 15.x || 16.x - checksum: 10/5ec695d8c91efd82ad75cb3e27662644c71e22be71793908135b38965be6fe1f229c24fd2f4fed1bc1829b84bec2a1f6470817a83c633d95292db7635a625471 + checksum: 10/b71245558ebd64bf93b98aec933d4b5f5758e0fecf7915728d94725ed4201fb2515e2af92fe01a595638147e5e0ef50a27ab5323d9b76eeb126769fb1e58f051 languageName: node linkType: hard -"@apollo/utils.stripsensitiveliterals@npm:^1.2.0": - version: 1.2.0 - resolution: "@apollo/utils.stripsensitiveliterals@npm:1.2.0" +"@apollo/utils.stripsensitiveliterals@npm:^2.0.1": + version: 2.0.1 + resolution: "@apollo/utils.stripsensitiveliterals@npm:2.0.1" peerDependencies: graphql: 14.x || 15.x || 16.x - checksum: 10/5910186a30be23fac59652d350e83a8a7a53adb9146ed545906f6893ad9c8d380752e679348ee210ae01fa39cc0487692b632e960003dcedc2282bd28de2aa01 + checksum: 10/a3f74af0626f89d61f7ed1d25194f6b77006a06653399eecaea0b246cf685a85465091f2dc70280b127871b5c1eda7ded799ce176271c2612946acdc9453d388 languageName: node linkType: hard -"@apollo/utils.usagereporting@npm:^1.0.0": - version: 1.0.0 - resolution: "@apollo/utils.usagereporting@npm:1.0.0" +"@apollo/utils.usagereporting@npm:^2.1.0": + version: 2.1.0 + resolution: "@apollo/utils.usagereporting@npm:2.1.0" dependencies: - "@apollo/utils.dropunuseddefinitions": "npm:^1.1.0" - "@apollo/utils.printwithreducedwhitespace": "npm:^1.1.0" - "@apollo/utils.removealiases": "npm:1.0.0" - "@apollo/utils.sortast": "npm:^1.1.0" - "@apollo/utils.stripsensitiveliterals": "npm:^1.2.0" - apollo-reporting-protobuf: "npm:^3.3.1" + "@apollo/usage-reporting-protobuf": "npm:^4.1.0" + "@apollo/utils.dropunuseddefinitions": "npm:^2.0.1" + "@apollo/utils.printwithreducedwhitespace": "npm:^2.0.1" + "@apollo/utils.removealiases": "npm:2.0.1" + "@apollo/utils.sortast": "npm:^2.0.1" + "@apollo/utils.stripsensitiveliterals": "npm:^2.0.1" peerDependencies: graphql: 14.x || 15.x || 16.x - checksum: 10/e243fa4495e77bfbe5cfcf5bff1f3f7a26493eac1db9b98104263906c24f93dd64ed67fa4308f6868fef960d08d718c07508c15c6668ee8e78fa05565b438158 + checksum: 10/8af4b23000a4c35ba568e6a532e4120ab0e55b291c7b902f2d10a51aad877d0438b80c019296436870ee265edcc8881521fb9a0829796f23a3b2cb73449ac890 languageName: node linkType: hard -"@apollographql/apollo-tools@npm:^0.5.3": - version: 0.5.4 - resolution: "@apollographql/apollo-tools@npm:0.5.4" - peerDependencies: - graphql: ^14.2.1 || ^15.0.0 || ^16.0.0 - checksum: 10/4f69566d23ffb77ffedd87c679dcab608400f297e4cd5423151977b917737c427015485a8e0436feeb5154574171742ab626fb1a8f5ae2739070757976fd49f2 - languageName: node - linkType: hard - -"@apollographql/graphql-playground-html@npm:1.6.29": - version: 1.6.29 - resolution: "@apollographql/graphql-playground-html@npm:1.6.29" - dependencies: - xss: "npm:^1.0.8" - checksum: 10/5e45cdc122dbc18c71f89fd9be8c19d1e35417ea27d3915206438d351f7775894957cd5b8bb378921bb96a8f6e6a9d182ce3d674abaddefd36a3a7e9cf6f1e68 +"@apollo/utils.withrequired@npm:^2.0.0": + version: 2.0.1 + resolution: "@apollo/utils.withrequired@npm:2.0.1" + checksum: 10/ddd3a72d0f13e6283128d1aae787b65f8ef0bf2f2cf351e143c479f0838679e72d82f42f653b6baadd33a092854fc9cb9dd8af4a45938ee25b718274cef408ee languageName: node linkType: hard @@ -10500,27 +10554,15 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/merge@npm:8.3.1": - version: 8.3.1 - resolution: "@graphql-tools/merge@npm:8.3.1" +"@graphql-tools/merge@npm:^8.4.1": + version: 8.4.2 + resolution: "@graphql-tools/merge@npm:8.4.2" dependencies: - "@graphql-tools/utils": "npm:8.9.0" + "@graphql-tools/utils": "npm:^9.2.1" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/9354a68aa1b851ee72d2d727a3a264279f1e5ed95100f6c6e7e0a2ad7449943d2ebe6fce43b4873a15e5c3e9df52ea9d23ff51ffc1f73c417c4ccf368f8486ab - languageName: node - linkType: hard - -"@graphql-tools/merge@npm:8.3.6": - version: 8.3.6 - resolution: "@graphql-tools/merge@npm:8.3.6" - dependencies: - "@graphql-tools/utils": "npm:8.12.0" - tslib: "npm:^2.4.0" - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/511136c82cd40bcaa4570068859db7537b67fed0dd91918eb6447c10fed633beb790f39d846b3a3b982556a8f5f2aba860c920f2477023db92a1faac91ad36ac + checksum: 10/62a4e93812e11d083c17f7763f4333a29dbe99fddbff705ff5942a0bdbb9dcd14f668bd76bd3edda485534d5d1a7f09bac311b979196b6149df11d8968a83723 languageName: node linkType: hard @@ -10536,17 +10578,29 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/mock@npm:^8.1.2": - version: 8.7.6 - resolution: "@graphql-tools/mock@npm:8.7.6" +"@graphql-tools/merge@npm:^9.0.6": + version: 9.0.7 + resolution: "@graphql-tools/merge@npm:9.0.7" dependencies: - "@graphql-tools/schema": "npm:9.0.4" - "@graphql-tools/utils": "npm:8.12.0" + "@graphql-tools/utils": "npm:^10.5.4" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/afe9b11f2d1e43dd3fce8e510aa20846137acfb6cb6d9362e5cfe954f2daaa5c169b77c3013a21f3263c690dee2f3b3e235e305e49c3cc398bdb166ddf2d082b + languageName: node + linkType: hard + +"@graphql-tools/mock@npm:^9.0.4": + version: 9.0.4 + resolution: "@graphql-tools/mock@npm:9.0.4" + dependencies: + "@graphql-tools/schema": "npm:^10.0.4" + "@graphql-tools/utils": "npm:^10.2.1" fast-json-stable-stringify: "npm:^2.1.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/8acad4f9491993b7fd517f8491529eac0df24df257e42351d6fdaa9a6cf41936ec7015b1fc8c9219c6629fa60e9aca346578d98d75bf41005ce18063464bdfb3 + checksum: 10/fca1716a70177da0361d68330676e5af37671b5dc16868202fe19f40cc4fc0df043d926923e7ad771ec2914ff360c83ef5624fc149637f3fbd10e82f2a7175f5 languageName: node linkType: hard @@ -10600,20 +10654,6 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/schema@npm:9.0.4": - version: 9.0.4 - resolution: "@graphql-tools/schema@npm:9.0.4" - dependencies: - "@graphql-tools/merge": "npm:8.3.6" - "@graphql-tools/utils": "npm:8.12.0" - tslib: "npm:^2.4.0" - value-or-promise: "npm:1.0.11" - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/8478d77b9d585e7f411563494a6eff4fd4fad5d507284b2384f57e3cf5b98f5ac875034b633afc902fe4accbcf5da1a629f460a4c27ca41bd3ce7dc16eb96bfe - languageName: node - linkType: hard - "@graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.3, @graphql-tools/schema@npm:^10.0.4": version: 10.0.4 resolution: "@graphql-tools/schema@npm:10.0.4" @@ -10628,17 +10668,31 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/schema@npm:^8.0.0": - version: 8.5.1 - resolution: "@graphql-tools/schema@npm:8.5.1" +"@graphql-tools/schema@npm:^10.0.6": + version: 10.0.6 + resolution: "@graphql-tools/schema@npm:10.0.6" dependencies: - "@graphql-tools/merge": "npm:8.3.1" - "@graphql-tools/utils": "npm:8.9.0" + "@graphql-tools/merge": "npm:^9.0.6" + "@graphql-tools/utils": "npm:^10.5.4" tslib: "npm:^2.4.0" - value-or-promise: "npm:1.0.11" + value-or-promise: "npm:^1.0.12" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/98f57502cc67ee48157bcf6f26c334e27b0673ec6f5a35c1a5bc1901772063c8bfdca435f81664ab1a41f9274b43dc78aa12791feee83546640d0a034b38c836 + checksum: 10/f667ed6bf8c8419cee8929c19b1b31c55894526a25f36463d106baf31b19cee8b297281eef07de7e3d94173d03a8ade4570a6e338b1482a27f8eae09d9ade4a8 + languageName: node + linkType: hard + +"@graphql-tools/schema@npm:^9.0.0": + version: 9.0.19 + resolution: "@graphql-tools/schema@npm:9.0.19" + dependencies: + "@graphql-tools/merge": "npm:^8.4.1" + "@graphql-tools/utils": "npm:^9.2.1" + tslib: "npm:^2.4.0" + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/762811fe08ec67000b190305783677ea086e6b300a1882f46b804bdf790e32de986bef7bbd574ddd4114393ca9b97422cc604390652537d4595eba7dde825259 languageName: node linkType: hard @@ -10665,28 +10719,6 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:8.12.0": - version: 8.12.0 - resolution: "@graphql-tools/utils@npm:8.12.0" - dependencies: - tslib: "npm:^2.4.0" - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/9a3f377a7b8c069af4160ba76338a9ebfbe15c2c7290b3eb4bb11787a741abcda239e30af4e13be8cf899da38a64a5444ba993fa1dd2717165233dd66f4846d1 - languageName: node - linkType: hard - -"@graphql-tools/utils@npm:8.9.0": - version: 8.9.0 - resolution: "@graphql-tools/utils@npm:8.9.0" - dependencies: - tslib: "npm:^2.4.0" - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/de5930b33664c53f0d22781bb16b4e029afaad165539faf80bd520adfad969c024891db672a2ff96195d8d1185bac66b284ebde67938e554d04c0798453da002 - languageName: node - linkType: hard - "@graphql-tools/utils@npm:^10.0.0": version: 10.0.11 resolution: "@graphql-tools/utils@npm:10.0.11" @@ -10715,6 +10747,32 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/utils@npm:^10.5.4": + version: 10.5.4 + resolution: "@graphql-tools/utils@npm:10.5.4" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + cross-inspect: "npm:1.0.1" + dset: "npm:^3.1.2" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/e6750800be215cbd8b4cc67019ba91b881c6093a4ac71258eeef9f1f3101b1e81e862435fd5b05cb40d94d40e396d0a3629ad6613226d7a4f75b8c5329a03aa7 + languageName: node + linkType: hard + +"@graphql-tools/utils@npm:^9.2.1": + version: 9.2.1 + resolution: "@graphql-tools/utils@npm:9.2.1" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/b1665043c2180a74d1e071f9f495ce16b2f46eeed1b319a290ae58f699629fe0a47b619c4f9be89135ff20b1c34fe6cc27e86570cf1e2cff07d3ae204f3d170d + languageName: node + linkType: hard + "@graphql-tools/wrap@npm:^10.0.2": version: 10.0.5 resolution: "@graphql-tools/wrap@npm:10.0.5" @@ -11171,13 +11229,6 @@ __metadata: languageName: node linkType: hard -"@josephg/resolvable@npm:^1.0.0": - version: 1.0.1 - resolution: "@josephg/resolvable@npm:1.0.1" - checksum: 10/64eb763b5138bdae4fb59c0c0e89ed261b690917ae6bd777b533257668f151b8868698fb73dfd7665746ad07c7c917fe89ccfdf2404048d39f373f57f1a14e34 - languageName: node - linkType: hard - "@jridgewell/gen-mapping@npm:^0.1.0": version: 0.1.1 resolution: "@jridgewell/gen-mapping@npm:0.1.1" @@ -15350,6 +15401,7 @@ __metadata: dependencies: "@apollo/client": "npm:^3.7.0" "@apollo/rover": "npm:^0.23.0" + "@apollo/server": "npm:^4.11.0" "@aws-sdk/client-s3": "npm:^3.276.0" "@aws-sdk/lib-storage": "npm:^3.100.0" "@bull-board/express": "npm:^4.2.2" @@ -15360,7 +15412,8 @@ __metadata: "@graphql-codegen/typescript": "npm:^4.0.7" "@graphql-codegen/typescript-operations": "npm:^4.2.1" "@graphql-codegen/typescript-resolvers": "npm:^4.1.0" - "@graphql-tools/schema": "npm:^10.0.4" + "@graphql-tools/mock": "npm:^9.0.4" + "@graphql-tools/schema": "npm:^10.0.6" "@mailchimp/mailchimp_marketing": "npm:^3.0.80" "@parcel/watcher": "npm:^2.4.1" "@speckle/objectloader": "workspace:^" @@ -15374,6 +15427,7 @@ __metadata: "@types/compression": "npm:^1.7.2" "@types/connect-redis": "npm:^0.0.23" "@types/cookie-parser": "npm:^1.4.7" + "@types/cors": "npm:^2.8.17" "@types/debug": "npm:^4.1.7" "@types/deep-equal-in-any-order": "npm:^1.0.1" "@types/ejs": "npm:^3.1.1" @@ -15405,8 +15459,6 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^5.39.0" "@typescript-eslint/parser": "npm:^5.39.0" ajv: "npm:^8.12.0" - apollo-server-express: "npm:^3.10.2" - apollo-server-plugin-base: "npm:^3.7.2" axios: "npm:^1.7.4" bcrypt: "npm:^5.0.0" bull: "npm:^4.8.5" @@ -15435,10 +15487,11 @@ __metadata: express-async-errors: "npm:^3.1.1" express-prom-bundle: "npm:^6.6.0" express-session: "npm:^1.17.1" - graphql: "npm:^15" + graphql: "npm:^16.6.0" graphql-redis-subscriptions: "npm:^2.2.2" graphql-scalars: "npm:^1.18.0" graphql-subscriptions: "npm:^2.0.0" + graphql-tag: "npm:^2.12.6" http-proxy-middleware: "npm:v3.0.0-beta.0" ioredis: "npm:^5.2.2" ioredis-mock: "npm:^8.9.0" @@ -17719,15 +17772,6 @@ __metadata: languageName: node linkType: hard -"@types/accepts@npm:^1.3.5": - version: 1.3.5 - resolution: "@types/accepts@npm:1.3.5" - dependencies: - "@types/node": "npm:*" - checksum: 10/3984edd631d9e308ef10286454a05e2388812a740d404abf93522a3bc3d10032ae6a60816e8cc4ae1bc96367db39e543d3ef862944cea53d1eea48be1f624fc2 - languageName: node - linkType: hard - "@types/apollo-upload-client@npm:^17.0.1": version: 17.0.1 resolution: "@types/apollo-upload-client@npm:17.0.1" @@ -17834,7 +17878,7 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*, @types/body-parser@npm:1.19.2": +"@types/body-parser@npm:*": version: 1.19.2 resolution: "@types/body-parser@npm:1.19.2" dependencies: @@ -17971,10 +18015,12 @@ __metadata: languageName: node linkType: hard -"@types/cors@npm:2.8.12": - version: 2.8.12 - resolution: "@types/cors@npm:2.8.12" - checksum: 10/8c45f112c7d1d2d831b4b266f2e6ed33a1887a35dcbfe2a18b28370751fababb7cd045e745ef84a523c33a25932678097bf79afaa367c6cb3fa0daa7a6438257 +"@types/cors@npm:^2.8.17": + version: 2.8.17 + resolution: "@types/cors@npm:2.8.17" + dependencies: + "@types/node": "npm:*" + checksum: 10/469bd85e29a35977099a3745c78e489916011169a664e97c4c3d6538143b0a16e4cc72b05b407dc008df3892ed7bf595f9b7c0f1f4680e169565ee9d64966bde languageName: node linkType: hard @@ -18141,14 +18187,15 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:4.17.30": - version: 4.17.30 - resolution: "@types/express-serve-static-core@npm:4.17.30" +"@types/express-serve-static-core@npm:^4.17.30": + version: 4.19.5 + resolution: "@types/express-serve-static-core@npm:4.19.5" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" - checksum: 10/1074c5769ae052fcb7ea0a2d4a807090a832ec6f8e1ca9105cd949f6dbc99a745a0d3e5b8c8fb64e7105b4a23d8283aa50fd3234d3b7f821899efbed3675b179 + "@types/send": "npm:*" + checksum: 10/49350c6315eeb7d640e13e6138ba6005121b3b610b1e25746fccd5b86b559be810a4ba384b9bd7eee288975b5bd8cf67c1772c646254b812beaa488774eb5513 languageName: node linkType: hard @@ -18161,7 +18208,7 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:4.17.13, @types/express@npm:^4.17.13": +"@types/express@npm:*, @types/express@npm:^4.17.13": version: 4.17.13 resolution: "@types/express@npm:4.17.13" dependencies: @@ -18583,6 +18630,16 @@ __metadata: languageName: node linkType: hard +"@types/node-fetch@npm:^2.6.1": + version: 2.6.11 + resolution: "@types/node-fetch@npm:2.6.11" + dependencies: + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 10/c416df8f182ec3826278ea42557fda08f169a48a05e60722d9c8edd4e5b2076ae281c6b6601ad406035b7201f885b0257983b61c26b3f9eb0f41192a807b5de5 + languageName: node + linkType: hard + "@types/node-forge@npm:^1.3.0": version: 1.3.11 resolution: "@types/node-forge@npm:1.3.11" @@ -18608,13 +18665,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^10.1.0": - version: 10.17.60 - resolution: "@types/node@npm:10.17.60" - checksum: 10/f9161493b3284b1d41d5d594c2768625acdd9e33f992f71ccde47861916e662e2ae438d2cc5f1b285053391a31b52a7564ecedc22d485610d236bfad9c7e6a1c - languageName: node - linkType: hard - "@types/node@npm:^13.7.0": version: 13.13.52 resolution: "@types/node@npm:13.13.52" @@ -19016,6 +19066,16 @@ __metadata: languageName: node linkType: hard +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10/28320a2aa1eb704f7d96a65272a07c0bf3ae7ed5509c2c96ea5e33238980f71deeed51d3631927a77d5250e4091b3e66bce53b42d770873282c6a20bb8b0280d + languageName: node + linkType: hard + "@types/serve-index@npm:^1.9.1": version: 1.9.1 resolution: "@types/serve-index@npm:1.9.1" @@ -22638,146 +22698,6 @@ __metadata: languageName: node linkType: hard -"apollo-datasource@npm:^3.3.2": - version: 3.3.2 - resolution: "apollo-datasource@npm:3.3.2" - dependencies: - "@apollo/utils.keyvaluecache": "npm:^1.0.1" - apollo-server-env: "npm:^4.2.1" - checksum: 10/70244e792655b357213b92e9dd0e8ca553724857847c9bedb53a1dbf7a92fc0d8b05a60d77203d6c30331599b44c5d7cc5f4d94c934465fa05b146b681ed2293 - languageName: node - linkType: hard - -"apollo-reporting-protobuf@npm:^3.3.1, apollo-reporting-protobuf@npm:^3.3.2": - version: 3.3.2 - resolution: "apollo-reporting-protobuf@npm:3.3.2" - dependencies: - "@apollo/protobufjs": "npm:1.2.4" - checksum: 10/92b527b062a7ca65c6a19a01899a5d45e9271301fb5c5841e3e90e420d125cce5bf0e1575bc4bb1064720793a1ff270acb0387cf82d5decb3d6a08ecc767eb65 - languageName: node - linkType: hard - -"apollo-reporting-protobuf@npm:^3.4.0": - version: 3.4.0 - resolution: "apollo-reporting-protobuf@npm:3.4.0" - dependencies: - "@apollo/protobufjs": "npm:1.2.6" - checksum: 10/d6c731c1e07f952770166c71222a34ea97dd90f4b1d74f3261caa1542e1fb81a591c74586cd973c28c12e8bb9aa54ff0de411698f2311978f7144f98258c1a0b - languageName: node - linkType: hard - -"apollo-server-core@npm:^3.10.2": - version: 3.12.1 - resolution: "apollo-server-core@npm:3.12.1" - dependencies: - "@apollo/utils.keyvaluecache": "npm:^1.0.1" - "@apollo/utils.logger": "npm:^1.0.0" - "@apollo/utils.usagereporting": "npm:^1.0.0" - "@apollographql/apollo-tools": "npm:^0.5.3" - "@apollographql/graphql-playground-html": "npm:1.6.29" - "@graphql-tools/mock": "npm:^8.1.2" - "@graphql-tools/schema": "npm:^8.0.0" - "@josephg/resolvable": "npm:^1.0.0" - apollo-datasource: "npm:^3.3.2" - apollo-reporting-protobuf: "npm:^3.4.0" - apollo-server-env: "npm:^4.2.1" - apollo-server-errors: "npm:^3.3.1" - apollo-server-plugin-base: "npm:^3.7.2" - apollo-server-types: "npm:^3.8.0" - async-retry: "npm:^1.2.1" - fast-json-stable-stringify: "npm:^2.1.0" - graphql-tag: "npm:^2.11.0" - loglevel: "npm:^1.6.8" - lru-cache: "npm:^6.0.0" - node-abort-controller: "npm:^3.0.1" - sha.js: "npm:^2.4.11" - uuid: "npm:^9.0.0" - whatwg-mimetype: "npm:^3.0.0" - peerDependencies: - graphql: ^15.3.0 || ^16.0.0 - checksum: 10/2e273d60a91ef7faf85687445bcb5f1affcfcd85a363968d570c82a57ce533076780be14e8d55c4d3ed7168fa19167960830bce5525436cc78a5e5e71752c473 - languageName: node - linkType: hard - -"apollo-server-env@npm:^4.2.1": - version: 4.2.1 - resolution: "apollo-server-env@npm:4.2.1" - dependencies: - node-fetch: "npm:^2.6.7" - checksum: 10/9172288c89c2ebb2a02d58542f807896de1ca0ba80c430f09242f2fa9ece40d7ecb8f9527357ba5e1d9997c64c364e7a9716e4f5485c5fb4938f69627bf1cea4 - languageName: node - linkType: hard - -"apollo-server-errors@npm:^3.3.1": - version: 3.3.1 - resolution: "apollo-server-errors@npm:3.3.1" - peerDependencies: - graphql: ^15.3.0 || ^16.0.0 - checksum: 10/5090af0280e40ce9b3b042fca6bd195d84c8507d8fe0b16b7fd52501b8f88cb2b5cca99882a9c5253f04c78b94ff2d350eb148867e20b0ddd6211071bb17f553 - languageName: node - linkType: hard - -"apollo-server-express@npm:^3.10.2": - version: 3.10.2 - resolution: "apollo-server-express@npm:3.10.2" - dependencies: - "@types/accepts": "npm:^1.3.5" - "@types/body-parser": "npm:1.19.2" - "@types/cors": "npm:2.8.12" - "@types/express": "npm:4.17.13" - "@types/express-serve-static-core": "npm:4.17.30" - accepts: "npm:^1.3.5" - apollo-server-core: "npm:^3.10.2" - apollo-server-types: "npm:^3.6.2" - body-parser: "npm:^1.19.0" - cors: "npm:^2.8.5" - parseurl: "npm:^1.3.3" - peerDependencies: - express: ^4.17.1 - graphql: ^15.3.0 || ^16.0.0 - checksum: 10/a26a8fd794824c49ca4c866347fea3f3553a59e58a14dc1ed2af737173eeda7a62ce69b99aaaaf2d7e3ba20eeb222583c1e45db88f6539e80978d2a3e1e337b9 - languageName: node - linkType: hard - -"apollo-server-plugin-base@npm:^3.7.2": - version: 3.7.2 - resolution: "apollo-server-plugin-base@npm:3.7.2" - dependencies: - apollo-server-types: "npm:^3.8.0" - peerDependencies: - graphql: ^15.3.0 || ^16.0.0 - checksum: 10/b2599f51e66dce930208c1c6f6b4394e3bde6c635e971a80d677b33e7d3d6c2050453ede99bde66281e4d6d6675094b6fb50a5ec30d16e04bee13d7570ad2715 - languageName: node - linkType: hard - -"apollo-server-types@npm:^3.6.2": - version: 3.6.2 - resolution: "apollo-server-types@npm:3.6.2" - dependencies: - "@apollo/utils.keyvaluecache": "npm:^1.0.1" - "@apollo/utils.logger": "npm:^1.0.0" - apollo-reporting-protobuf: "npm:^3.3.2" - apollo-server-env: "npm:^4.2.1" - peerDependencies: - graphql: ^15.3.0 || ^16.0.0 - checksum: 10/c513b2890ee453beb859041540a6b35cb6db6c8a7060f30a572b7531d6b998b8fe99519966a9052b4fd7c962d6bd1a62ab61036b933db8eaa1981a54f92e2785 - languageName: node - linkType: hard - -"apollo-server-types@npm:^3.8.0": - version: 3.8.0 - resolution: "apollo-server-types@npm:3.8.0" - dependencies: - "@apollo/utils.keyvaluecache": "npm:^1.0.1" - "@apollo/utils.logger": "npm:^1.0.0" - apollo-reporting-protobuf: "npm:^3.4.0" - apollo-server-env: "npm:^4.2.1" - peerDependencies: - graphql: ^15.3.0 || ^16.0.0 - checksum: 10/c802fecba27ff5f0b45fc4a3d6c88e18c39c6e5ba5785db067588d4e0c7d56aba6f4dc69171b07ac6348e9e313b036c57c178af58b8b3414331517e0b280324e - languageName: node - linkType: hard - "apollo-upload-client@npm:^17.0.0": version: 17.0.0 resolution: "apollo-upload-client@npm:17.0.0" @@ -23978,26 +23898,6 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^1.19.0": - version: 1.20.0 - resolution: "body-parser@npm:1.20.0" - dependencies: - bytes: "npm:3.1.2" - content-type: "npm:~1.0.4" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - on-finished: "npm:2.4.1" - qs: "npm:6.10.3" - raw-body: "npm:2.5.1" - type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: 10/63fe82c27fdacac51d26665c3d13d4c6e48d1c3e9efe1fbc0fd18801aa9a598ab1023b09298ae4b3d0a7598d55902d793f7fa1b5551da99c16eabfed9b022a51 - languageName: node - linkType: hard - "bonjour-service@npm:^1.0.11": version: 1.0.12 resolution: "bonjour-service@npm:1.0.12" @@ -25618,7 +25518,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.20.3": +"commander@npm:^2.19.0, commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 10/90c5b6898610cd075984c58c4f88418a4fb44af08c1b1415e9854c03171bec31b336b7f3e4cefe33de994b3f12b03c5e2d638da4316df83593b9e82554e7e95b @@ -26442,6 +26342,15 @@ __metadata: languageName: node linkType: hard +"cross-inspect@npm:1.0.1": + version: 1.0.1 + resolution: "cross-inspect@npm:1.0.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/7c1e02e0a9670b62416a3ea1df7ae880fdad3aa0a857de8932c4e5f8acd71298c7e3db9da8e9da603f5692cd1879938f5e72e34a9f5d1345987bef656d117fc1 + languageName: node + linkType: hard + "cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -26646,13 +26555,6 @@ __metadata: languageName: node linkType: hard -"cssfilter@npm:0.0.10": - version: 0.0.10 - resolution: "cssfilter@npm:0.0.10" - checksum: 10/1e45182f42de848f092f50a313113c28a88e4ac98333bf1603ee1c3b200384a3bc83c12e35cd61135e3b0f218295f600d51120ca1f926b7958b2d3262d711214 - languageName: node - linkType: hard - "cssnano-preset-default@npm:^6.0.1": version: 6.0.1 resolution: "cssnano-preset-default@npm:6.0.1" @@ -29923,7 +29825,7 @@ __metadata: languageName: node linkType: hard -"express@npm:>=4.19.2, express@npm:^4.17.3, express@npm:^4.19.2": +"express@npm:>=4.19.2, express@npm:^4.17.1, express@npm:^4.17.3, express@npm:^4.19.2": version: 4.19.2 resolution: "express@npm:4.19.2" dependencies: @@ -31988,10 +31890,17 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^15.3.0": - version: 15.8.0 - resolution: "graphql@npm:15.8.0" - checksum: 10/f8d830287a9028d6779b59c437e0ade63a713b47521b02b60316df1761b805b1a7ce03be88053d224b7f78f5d1d1a786d287ab229cd158b42ebeea9e86daaba5 +"graphql@npm:14 - 16, graphql@npm:^16.6.0": + version: 16.9.0 + resolution: "graphql@npm:16.9.0" + checksum: 10/5833f82bb6c31bec120bbf9cd400eda873e1bb7ef5c17974fa262cd82dc68728fda5d4cb859dc8aaa4c4fe4f6fe1103a9c47efc01a12c02ae5cb581d8e4029e2 + languageName: node + linkType: hard + +"graphql@npm:^15.0.0": + version: 15.9.0 + resolution: "graphql@npm:15.9.0" + checksum: 10/ce1f50672bcb369395d07a47048bcbb429ed1ce06dbcafb7a0999df791cb7aa7206be21497907973dbc8a01df3cd7f632f43c583f248538f186f5adfa1a0d1c5 languageName: node linkType: hard @@ -39064,10 +38973,10 @@ __metadata: languageName: node linkType: hard -"node-abort-controller@npm:^3.0.1": - version: 3.0.1 - resolution: "node-abort-controller@npm:3.0.1" - checksum: 10/7437b015830a2f714692fd372c01ce5c8c66f332a205455f58ddc8b3228314e588a20abd34a2b037c9cc438ced74e75492c7fc04f4dc0cf7bf0c0ac4160175e3 +"node-abort-controller@npm:^3.1.1": + version: 3.1.1 + resolution: "node-abort-controller@npm:3.1.1" + checksum: 10/0a2cdb7ec0aeaf3cb31e1ca0e192f5add48f1c5c9c9ed822129f9dddbd9432f69b7425982f94ce803c56a2104884530aa67cd57696e5774b2e5b8ec2f58de042 languageName: node linkType: hard @@ -43622,15 +43531,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.3, qs@npm:^6.4.0, qs@npm:^6.5.1": - version: 6.10.3 - resolution: "qs@npm:6.10.3" - dependencies: - side-channel: "npm:^1.0.4" - checksum: 10/73d07bfd77f07bec3750dca5e6d165cba0c87ce3e4688bb26e5e462e725ab1289ecdb69164b0b4a4d1b913e2a3ae6b22acbb8b2feb5c8f31bd76f2380f3dc23d - languageName: node - linkType: hard - "qs@npm:6.11.0, qs@npm:^6.10.0": version: 6.11.0 resolution: "qs@npm:6.11.0" @@ -43640,6 +43540,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.4.0, qs@npm:^6.5.1": + version: 6.10.3 + resolution: "qs@npm:6.10.3" + dependencies: + side-channel: "npm:^1.0.4" + checksum: 10/73d07bfd77f07bec3750dca5e6d165cba0c87ce3e4688bb26e5e462e725ab1289ecdb69164b0b4a4d1b913e2a3ae6b22acbb8b2feb5c8f31bd76f2380f3dc23d + languageName: node + linkType: hard + "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -43757,18 +43666,6 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.1": - version: 2.5.1 - resolution: "raw-body@npm:2.5.1" - dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 10/280bedc12db3490ecd06f740bdcf66093a07535374b51331242382c0e130bb273ebb611b7bc4cba1b4b4e016cc7b1f4b05a6df885a6af39c2bc3b94c02291c84 - languageName: node - linkType: hard - "raw-body@npm:2.5.2": version: 2.5.2 resolution: "raw-body@npm:2.5.2" @@ -50102,7 +49999,7 @@ __metadata: languageName: node linkType: hard -"value-or-promise@npm:1.0.11, value-or-promise@npm:^1.0.11": +"value-or-promise@npm:^1.0.11": version: 1.0.11 resolution: "value-or-promise@npm:1.0.11" checksum: 10/9bd1cf82be5b59ec4a7ee9fa17ca7b3f16165c3ea33ebabe514f7a20e4f88dd11f912900f0279760618eb7fbd5e3bb2a4cf4b351b5fd8e8da69aa2719725e54a @@ -52130,18 +52027,6 @@ __metadata: languageName: node linkType: hard -"xss@npm:^1.0.8": - version: 1.0.11 - resolution: "xss@npm:1.0.11" - dependencies: - commander: "npm:^2.20.3" - cssfilter: "npm:0.0.10" - bin: - xss: bin/xss - checksum: 10/00f0e85d7b7e5e1b2908366866637f8aa0707c7f433837c553e45f94bd824897e89b4332a092fcf2bfe8f66f196e0c6570a0069ed60c24f0e26a566bbb5b51e2 - languageName: node - linkType: hard - "xtend@npm:^2.2.0": version: 2.2.0 resolution: "xtend@npm:2.2.0"