From 1688b5e0d1a90c698192b37faf451b55dabd03d4 Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Wed, 2 Sep 2020 15:27:52 -0400 Subject: [PATCH] Backport NoSchemaIntrospectionCustomRule from graphql@15.2.0 This rule is adapted from https://github.com/graphql/graphql-js/pull/2600 and should be replaced once using graphql >=15.2.0. Co-authored by: dzucconi --- .env.example | 3 +- src/config.ts | 2 + src/index.ts | 14 ++++++- .../noSchemaIntrospectionCustomRule.ts | 41 +++++++++++++++++++ .../principalFieldDirectiveValidation.ts | 4 +- 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/validations/noSchemaIntrospectionCustomRule.ts diff --git a/.env.example b/.env.example index 0205310d64..f35dcb5050 100644 --- a/.env.example +++ b/.env.example @@ -14,7 +14,7 @@ DIFFUSION_API_BASE=https://diffusion-staging.artsy.net/api DIFFUSION_TOKEN=REPLACE EMBEDLY_ENDPOINT=https://i.embed.ly/1/display EMBEDLY_KEY=REPLACE -ENABLE_APOLLO=true +ENABLE_APOLLO=false ENABLE_CONSIGNMENTS_STITCHING=true ENABLE_QUERY_TRACING=false ENABLE_REQUEST_LOGGING=true @@ -37,6 +37,7 @@ GRAVITY_SECRET=REPLACE HMAC_SECRET=https://www.youtube.com/watch?v=F5bAa6gFvLs IMPULSE_API_BASE=https://impulse-staging.artsy.net/api IMPULSE_APPLICATION_ID=REPLACE +INTROSPECT_TOKEN=replaceme KAWS_API_BASE=https://kaws-staging.artsy.net MEMCACHED_URL=localhost:11211 NODE_ENV=development diff --git a/src/config.ts b/src/config.ts index 87a3ac88dd..d6b37f3c8b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -56,6 +56,7 @@ const { HMAC_SECRET, IMPULSE_API_BASE, IMPULSE_APPLICATION_ID, + INTROSPECT_TOKEN, IP_BLACKLIST, LOG_QUERY_DETAILS_THRESHOLD, MEMCACHED_MAX_POOL, @@ -186,6 +187,7 @@ export default { HMAC_SECRET: HMAC_SECRET as string, IMPULSE_API_BASE, IMPULSE_APPLICATION_ID, + INTROSPECT_TOKEN, IP_BLACKLIST: IP_BLACKLIST || "", LOG_QUERY_DETAILS_THRESHOLD, MEMCACHED_MAX_POOL: Number(MEMCACHED_MAX_POOL) || 10, diff --git a/src/index.ts b/src/index.ts index 4c4b462784..11d45fd5b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ import { ErrorExtension } from "./extensions/errorExtension" import { LoggingExtension } from "./extensions/loggingExtension" import { principalFieldDirectiveExtension } from "./extensions/principalFieldDirectiveExtension" import { principalFieldDirectiveValidation } from "validations/principalFieldDirectiveValidation" +import { NoSchemaIntrospectionCustomRule } from "validations/noSchemaIntrospectionCustomRule" import * as Sentry from "@sentry/node" const { @@ -44,6 +45,7 @@ const { RESOLVER_TIMEOUT_MS, SENTRY_PRIVATE_DSN, ENABLE_APOLLO, + INTROSPECT_TOKEN, } = config const enableSentry = !!SENTRY_PRIVATE_DSN @@ -220,7 +222,17 @@ function startApp(appSchema, path: string) { userAgent, } - const validationRules = [principalFieldDirectiveValidation] + const validationRules = [ + principalFieldDirectiveValidation, + + // require Authorization header for introspection (in production if configured) + ...(PRODUCTION_ENV && + INTROSPECT_TOKEN && + req.headers["authorization"] !== `Bearer ${INTROSPECT_TOKEN}` + ? [NoSchemaIntrospectionCustomRule] + : []), + ] + if (QUERY_DEPTH_LIMIT) validationRules.push(depthLimit(QUERY_DEPTH_LIMIT)) diff --git a/src/validations/noSchemaIntrospectionCustomRule.ts b/src/validations/noSchemaIntrospectionCustomRule.ts new file mode 100644 index 0000000000..6e0169a0a0 --- /dev/null +++ b/src/validations/noSchemaIntrospectionCustomRule.ts @@ -0,0 +1,41 @@ +import { + ASTVisitor, + GraphQLError, + FieldNode, + ValidationContext, + getNamedType, + isIntrospectionType, +} from "graphql" + +// Adapted from https://github.com/graphql/graphql-js/pull/2600. +// TODO: replace once using graphql >=15.2.0 + +/** + * Prohibit introspection queries + * + * A GraphQL document is only valid if all fields selected are not fields that + * return an introspection type. + * + * Note: This rule is optional and is not part of the Validation section of the + * GraphQL Specification. This rule effectively disables introspection, which + * does not reflect best practices and should only be done if absolutely necessary. + */ +export const NoSchemaIntrospectionCustomRule = ( + context: ValidationContext +): ASTVisitor => { + return { + Field(node: FieldNode) { + const contextType = context.getType() + if (!contextType) return + const type = getNamedType(contextType) + if (type && isIntrospectionType(type)) { + context.reportError( + new GraphQLError( + `GraphQL introspection has been disabled, but the requested query contained the field "${node.name.value}".`, + node + ) + ) + } + }, + } +} diff --git a/src/validations/principalFieldDirectiveValidation.ts b/src/validations/principalFieldDirectiveValidation.ts index 7b985a6602..ae3bddf915 100644 --- a/src/validations/principalFieldDirectiveValidation.ts +++ b/src/validations/principalFieldDirectiveValidation.ts @@ -1,6 +1,6 @@ -import { GraphQLError, BREAK } from "graphql" +import { GraphQLError, BREAK, ASTVisitor } from "graphql" -export const principalFieldDirectiveValidation = (context) => { +export const principalFieldDirectiveValidation = (context): ASTVisitor => { let directivesSeen = 0 return { Directive(node) {