Skip to content

Commit

Permalink
feat(server): disable graphql introspection in production (#1170)
Browse files Browse the repository at this point in the history
  • Loading branch information
lvauvillier authored Jul 3, 2020
1 parent 1558d12 commit 54091f4
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/runtime/server/handler-graphql.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ function createHandler(...types: any) {
}),
() => {
return {}
},
{
introspection: true,
}
)
}
Expand Down
47 changes: 43 additions & 4 deletions src/runtime/server/handler-graphql.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
import { Either, isLeft, left, right, toError, tryCatch } from 'fp-ts/lib/Either'
import { execute, getOperationAST, GraphQLSchema, parse, Source, validate } from 'graphql'
import {
execute,
getOperationAST,
GraphQLSchema,
parse,
Source,
validate,
ValidationContext,
specifiedRules,
FieldNode,
GraphQLError,
} from 'graphql'
import { IncomingMessage } from 'http'
import createError, { HttpError } from 'http-errors'
import url from 'url'
import { parseBody } from './parse-body'
import { ContextCreator, NexusRequestHandler } from './server'
import { sendError, sendErrorData, sendSuccess } from './utils'

type CreateHandler = (schema: GraphQLSchema, createContext: ContextCreator) => NexusRequestHandler
type Settings = {
introspection: boolean
}

type CreateHandler = (
schema: GraphQLSchema,
createContext: ContextCreator,
settings: Settings
) => NexusRequestHandler

type GraphQLParams = {
query: null | string
Expand All @@ -16,10 +35,26 @@ type GraphQLParams = {
raw: boolean
}

const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldNode) {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQLError(
'GraphQL introspection is not allowed by Nexus, but the query contained __schema or __type. To enable introspection, pass introspection: true to Nexus graphql settings in production',
[node]
)
)
}
},
})

/**
* Create a handler for graphql requests.
*/
export const createRequestHandlerGraphQL: CreateHandler = (schema, createContext) => async (req, res) => {
export const createRequestHandlerGraphQL: CreateHandler = (schema, createContext, settings) => async (
req,
res
) => {
const errParams = await getGraphQLParams(req)

if (isLeft(errParams)) {
Expand All @@ -42,7 +77,11 @@ export const createRequestHandlerGraphQL: CreateHandler = (schema, createContext

const documentAST = errDocumentAST.right

const validationFailures = validate(schema, documentAST)
let rules = specifiedRules
if (!settings.introspection) {
rules = [...rules, NoIntrospection]
}
const validationFailures = validate(schema, documentAST, rules)

if (validationFailures.length > 0) {
// todo lots of rich info for clients in here, expose it to them
Expand Down
8 changes: 6 additions & 2 deletions src/runtime/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ export function create(appState: AppState) {
return (
assembledGuard(appState, 'app.server.handlers.graphql', () => {
return wrapHandlerWithErrorHandling(
createRequestHandlerGraphQL(appState.assembled!.schema, appState.assembled!.createContext)
createRequestHandlerGraphQL(
appState.assembled!.schema,
appState.assembled!.createContext,
settings.data.graphql
)
)
}) ?? noop
)
Expand Down Expand Up @@ -106,7 +110,7 @@ export function create(appState: AppState) {
loadedRuntimePlugins
)

const graphqlHandler = createRequestHandlerGraphQL(schema, createContext)
const graphqlHandler = createRequestHandlerGraphQL(schema, createContext, settings.data.graphql)

express.post(settings.data.path, wrapHandlerWithErrorHandling(graphqlHandler))
express.get(settings.data.path, wrapHandlerWithErrorHandling(graphqlHandler))
Expand Down
21 changes: 20 additions & 1 deletion src/runtime/server/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export type PlaygroundSettings = {
path?: string
}

export type GraphqlSettings = {
introspection?: boolean
}

export type SettingsInput = {
/**
* todo
Expand Down Expand Up @@ -50,11 +54,16 @@ export type SettingsInput = {
path: string
playgroundPath?: string
}) => void
/**
* todo
*/
graphql?: GraphqlSettings
}

export type SettingsData = Omit<Utils.DeepRequired<SettingsInput>, 'host' | 'playground'> & {
export type SettingsData = Omit<Utils.DeepRequired<SettingsInput>, 'host' | 'playground' | 'graphql'> & {
host: string | undefined
playground: false | Required<PlaygroundSettings>
graphql: Required<GraphqlSettings>
}

export const defaultPlaygroundPath = '/'
Expand All @@ -63,6 +72,10 @@ export const defaultPlaygroundSettings: () => Readonly<Required<PlaygroundSettin
path: defaultPlaygroundPath,
})

export const defaultGraphqlSettings: () => Readonly<Required<GraphqlSettings>> = () => ({
introspection: process.env.NODE_ENV === 'production' ? false : true,
})

/**
* The default server options. These are merged with whatever you provide. Your
* settings take precedence over these.
Expand All @@ -86,6 +99,7 @@ export const defaultSettings: () => Readonly<SettingsData> = () => {
},
playground: process.env.NODE_ENV === 'production' ? false : defaultPlaygroundSettings(),
path: '/graphql',
graphql: defaultGraphqlSettings(),
}
}

Expand Down Expand Up @@ -121,6 +135,10 @@ export function playgroundSettings(settings: SettingsInput['playground']): Setti
}
}

export function graphqlSettings(settings: SettingsInput['graphql']): SettingsData['graphql'] {
return { ...defaultGraphqlSettings(), ...settings }
}

function validateGraphQLPath(path: string): string {
let outputPath = path

Expand All @@ -147,6 +165,7 @@ export function changeSettings(state: SettingsData, newSettings: SettingsInput):
state.path = validateGraphQLPath(updatedSettings.path)
state.port = updatedSettings.port
state.startMessage = updatedSettings.startMessage
state.graphql = graphqlSettings(updatedSettings.graphql)
}

export function createServerSettingsManager() {
Expand Down

0 comments on commit 54091f4

Please sign in to comment.