Remove subscriptions, comment out the sanitize directive

This commit is contained in:
Jackson Harper 2022-02-15 13:05:19 -08:00
parent df2b36b33a
commit fb224d60c4
4 changed files with 32 additions and 191 deletions

View file

@ -15,12 +15,12 @@ import { tracer } from './tracing'
import { env } from './env'
import { promisify } from 'util'
import { buildLogger } from './utils/logger'
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
import { ApolloServer } from 'apollo-server-express'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { applyMiddleware } from 'graphql-middleware'
import * as cookie from 'cookie'
import typeDefs from './schema'
import { gqlTracingPlugin, traceResolvers } from './graphql_tracing'
import { SanitizeDirective } from './directives'
// import { SanitizeDirective } from './directives'
import { functionResolvers } from './resolvers/function_resolvers'
import ScalarResolvers from './scalars'
import * as Sentry from '@sentry/node'
@ -38,10 +38,6 @@ const resolvers = {
...ScalarResolvers,
}
const schemaDirectives = {
sanitize: SanitizeDirective,
}
const contextFunc: ContextFunction<ExpressContext, ResolverContext> = async ({
req,
res,
@ -112,53 +108,16 @@ const contextFunc: ContextFunction<ExpressContext, ResolverContext> = async ({
}
export function makeApolloServer(app: Express): ApolloServer {
traceResolvers(functionResolvers)
let schema = makeExecutableSchema({ typeDefs, resolvers })
const apollo = new ApolloServer({
schema: applyMiddleware(
makeExecutableSchema({ typeDefs, resolvers, schemaDirectives })
),
schema: schema,
context: contextFunc,
plugins: [
gqlTracingPlugin,
{
requestDidStart: () => ({
didEncounterErrors: (ctx) => {
const error = ctx.errors[0]
const trxId = ctx.request.http?.headers.get('X-Transaction-ID')
const userId = ctx.context?.claims?.uid
const consoleMessage = `Transaction ID: ${trxId}. user: ${userId}.\n`
console.error(consoleMessage, error)
},
}),
},
],
formatError: (err) => {
Sentry.captureException(err)
// hide error messages from frontend on prod
return new Error('Unexpected server error')
},
subscriptions: {
path: '/api/graphql',
keepAlive: 4000,
onConnect: (connectionParams, webSocket, context) => {
const extraContext: { [key: string]: Record<string, unknown> } = {}
if (
context.request &&
context.request.headers &&
context.request.headers.cookie
) {
extraContext.cookies = cookie.parse(context.request.headers.cookie)
}
return {
...extraContext,
...context,
}
},
},
})
apollo.applyMiddleware({ app, path: '/api/graphql', cors: corsConfig })
return apollo
}

View file

@ -1,25 +1,25 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { SchemaDirectiveVisitor } from 'apollo-server-express'
import { GraphQLInputField, GraphQLScalarType } from 'graphql'
import { SanitizedString } from './scalars'
import { GraphQLNonNull } from 'graphql/type/definition'
// import { SchemaDirectiveVisitor } from 'apollo-server-express'
// import { GraphQLInputField, GraphQLScalarType } from 'graphql'
// import { SanitizedString } from './scalars'
// import { GraphQLNonNull } from 'graphql/type/definition'
export class SanitizeDirective extends SchemaDirectiveVisitor {
visitInputFieldDefinition(
field: GraphQLInputField
): GraphQLInputField | void | null {
const { allowedTags, maxLength } = this.args
if (
field.type instanceof GraphQLNonNull &&
field.type.ofType instanceof GraphQLScalarType
) {
field.type = new GraphQLNonNull(
new SanitizedString(field.type.ofType, allowedTags, maxLength)
)
} else if (field.type instanceof GraphQLScalarType) {
field.type = new SanitizedString(field.type, allowedTags, maxLength)
} else {
throw new Error(`Not a scalar type: ${field.type}`)
}
}
}
// export class SanitizeDirective extends SchemaDirectiveVisitor {
// visitInputFieldDefinition(
// field: GraphQLInputField
// ): GraphQLInputField | void | null {
// const { allowedTags, maxLength } = this.args
// if (
// field.type instanceof GraphQLNonNull &&
// field.type.ofType instanceof GraphQLScalarType
// ) {
// field.type = new GraphQLNonNull(
// new SanitizedString(field.type.ofType, allowedTags, maxLength)
// )
// } else if (field.type instanceof GraphQLScalarType) {
// field.type = new SanitizedString(field.type, allowedTags, maxLength)
// } else {
// throw new Error(`Not a scalar type: ${field.type}`)
// }
// }
// }

View file

@ -1,122 +0,0 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
ApolloServerPlugin,
GraphQLRequestListener,
ValueOrPromise,
WithRequired,
} from 'apollo-server-plugin-base'
import { GraphQLRequestContext } from 'apollo-server-core'
import { GraphQLResolveInfo } from 'graphql'
import { ResolverContext } from './resolvers/types'
import { ResolverFn, SubscriptionResolverObject } from './generated/graphql'
import { traceAs } from './tracing'
/**
* Replaces resolver functions with tracing-wrapped functions.
*/
export function traceResolvers(resolvers: {
[rootKey: string]: {
[fieldKey: string]:
| ResolverFn<any, any, any, any>
| SubscriptionResolverObject<any, any, any, any>
}
}): void {
for (const typeKey in resolvers) {
const rootType = resolvers[typeKey]
for (const fieldKey in rootType) {
const resolver = rootType[fieldKey]
const resolveFn =
typeof resolver === 'function' ? resolver : resolver.resolve
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const wrappedResolveFn = async (
...resolverArgs: [any, any, ResolverContext, GraphQLResolveInfo]
) => {
let info, ctx, args
if (resolverArgs.length === 4) {
;[, args, ctx, info] = resolverArgs
}
if (!info?.path) {
return resolveFn(...resolverArgs)
}
const spanName = `${typeKey}.${info.path.key}`
return traceAs(
{
spanName,
attributes: {
'resolver.args': JSON.stringify(args),
},
},
async () => {
return resolveFn(...resolverArgs)
}
)
}
if (typeof resolver === 'function') {
rootType[fieldKey] = wrappedResolveFn
} else {
;(rootType[fieldKey] as any).resolve = wrappedResolveFn
}
}
}
}
class GQLTracingPlugin implements GraphQLRequestListener<ResolverContext> {
[key: string]: import('apollo-server-types').AnyFunction | undefined
willSendResponse({
context,
}: WithRequired<
GraphQLRequestContext<ResolverContext>,
'metrics' | 'response'
>): ValueOrPromise<void> {
context.tracingSpan.end()
}
didEncounterErrors(
ctx: WithRequired<
GraphQLRequestContext<ResolverContext>,
'metrics' | 'source' | 'errors'
>
): ValueOrPromise<void> {
const error = ctx.errors[0]
const trxId = ctx.request.http?.headers.get('X-Transaction-ID')
const userId = ctx.context?.claims?.uid
ctx.context.tracingSpan.setAttributes({
'graphql.error': true,
'graphql.error.message': error.message,
'request.transaction_id': trxId || '',
})
}
}
export const gqlTracingPlugin: ApolloServerPlugin<ResolverContext> = {
requestDidStart({
context: { tracingSpan, claims },
request,
}: GraphQLRequestContext<ResolverContext>): GraphQLRequestListener<ResolverContext> | void {
if (request.query) {
tracingSpan.setAttribute('graphql.query', request.query)
}
if (claims?.uid) {
tracingSpan.setAttribute('user.id', claims.uid)
}
if (request.operationName) {
tracingSpan.setAttribute('graphql.operationName', request.operationName)
}
if (request.variables) {
tracingSpan.setAttribute(
'graphql.variables',
JSON.stringify(request.variables)
)
}
return new GQLTracingPlugin()
},
}

View file

@ -38,6 +38,7 @@ import ReminderModel from './datalayer/reminders'
import { remindersServiceRouter } from './routers/svc/reminders'
import { ApolloServer } from 'apollo-server-express'
import { pdfAttachmentsRouter } from './routers/svc/pdf_attachments'
import { corsConfig } from './utils/corsConfig'
const PORT = process.env.PORT || 4000
@ -125,6 +126,9 @@ const main = async (): Promise<void> => {
const { app, apollo, httpServer } = createApp()
await apollo.start()
apollo.applyMiddleware({ app, path: '/api/graphql', cors: corsConfig })
if (!env.dev.isLocal) {
const mwLogger = loggers.get('express', { levels: config.syslog.levels })
const transport = buildLoggerTransport('express')