Skip to content

Commit 54091f4

Browse files
authored
feat(server): disable graphql introspection in production (#1170)
1 parent 1558d12 commit 54091f4

File tree

4 files changed

+72
-7
lines changed

4 files changed

+72
-7
lines changed

src/runtime/server/handler-graphql.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ function createHandler(...types: any) {
162162
}),
163163
() => {
164164
return {}
165+
},
166+
{
167+
introspection: true,
165168
}
166169
)
167170
}

src/runtime/server/handler-graphql.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
11
import { Either, isLeft, left, right, toError, tryCatch } from 'fp-ts/lib/Either'
2-
import { execute, getOperationAST, GraphQLSchema, parse, Source, validate } from 'graphql'
2+
import {
3+
execute,
4+
getOperationAST,
5+
GraphQLSchema,
6+
parse,
7+
Source,
8+
validate,
9+
ValidationContext,
10+
specifiedRules,
11+
FieldNode,
12+
GraphQLError,
13+
} from 'graphql'
314
import { IncomingMessage } from 'http'
415
import createError, { HttpError } from 'http-errors'
516
import url from 'url'
617
import { parseBody } from './parse-body'
718
import { ContextCreator, NexusRequestHandler } from './server'
819
import { sendError, sendErrorData, sendSuccess } from './utils'
920

10-
type CreateHandler = (schema: GraphQLSchema, createContext: ContextCreator) => NexusRequestHandler
21+
type Settings = {
22+
introspection: boolean
23+
}
24+
25+
type CreateHandler = (
26+
schema: GraphQLSchema,
27+
createContext: ContextCreator,
28+
settings: Settings
29+
) => NexusRequestHandler
1130

1231
type GraphQLParams = {
1332
query: null | string
@@ -16,10 +35,26 @@ type GraphQLParams = {
1635
raw: boolean
1736
}
1837

38+
const NoIntrospection = (context: ValidationContext) => ({
39+
Field(node: FieldNode) {
40+
if (node.name.value === '__schema' || node.name.value === '__type') {
41+
context.reportError(
42+
new GraphQLError(
43+
'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',
44+
[node]
45+
)
46+
)
47+
}
48+
},
49+
})
50+
1951
/**
2052
* Create a handler for graphql requests.
2153
*/
22-
export const createRequestHandlerGraphQL: CreateHandler = (schema, createContext) => async (req, res) => {
54+
export const createRequestHandlerGraphQL: CreateHandler = (schema, createContext, settings) => async (
55+
req,
56+
res
57+
) => {
2358
const errParams = await getGraphQLParams(req)
2459

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

4378
const documentAST = errDocumentAST.right
4479

45-
const validationFailures = validate(schema, documentAST)
80+
let rules = specifiedRules
81+
if (!settings.introspection) {
82+
rules = [...rules, NoIntrospection]
83+
}
84+
const validationFailures = validate(schema, documentAST, rules)
4685

4786
if (validationFailures.length > 0) {
4887
// todo lots of rich info for clients in here, expose it to them

src/runtime/server/server.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ export function create(appState: AppState) {
7474
return (
7575
assembledGuard(appState, 'app.server.handlers.graphql', () => {
7676
return wrapHandlerWithErrorHandling(
77-
createRequestHandlerGraphQL(appState.assembled!.schema, appState.assembled!.createContext)
77+
createRequestHandlerGraphQL(
78+
appState.assembled!.schema,
79+
appState.assembled!.createContext,
80+
settings.data.graphql
81+
)
7882
)
7983
}) ?? noop
8084
)
@@ -106,7 +110,7 @@ export function create(appState: AppState) {
106110
loadedRuntimePlugins
107111
)
108112

109-
const graphqlHandler = createRequestHandlerGraphQL(schema, createContext)
113+
const graphqlHandler = createRequestHandlerGraphQL(schema, createContext, settings.data.graphql)
110114

111115
express.post(settings.data.path, wrapHandlerWithErrorHandling(graphqlHandler))
112116
express.get(settings.data.path, wrapHandlerWithErrorHandling(graphqlHandler))

src/runtime/server/settings.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export type PlaygroundSettings = {
88
path?: string
99
}
1010

11+
export type GraphqlSettings = {
12+
introspection?: boolean
13+
}
14+
1115
export type SettingsInput = {
1216
/**
1317
* todo
@@ -50,11 +54,16 @@ export type SettingsInput = {
5054
path: string
5155
playgroundPath?: string
5256
}) => void
57+
/**
58+
* todo
59+
*/
60+
graphql?: GraphqlSettings
5361
}
5462

55-
export type SettingsData = Omit<Utils.DeepRequired<SettingsInput>, 'host' | 'playground'> & {
63+
export type SettingsData = Omit<Utils.DeepRequired<SettingsInput>, 'host' | 'playground' | 'graphql'> & {
5664
host: string | undefined
5765
playground: false | Required<PlaygroundSettings>
66+
graphql: Required<GraphqlSettings>
5867
}
5968

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

75+
export const defaultGraphqlSettings: () => Readonly<Required<GraphqlSettings>> = () => ({
76+
introspection: process.env.NODE_ENV === 'production' ? false : true,
77+
})
78+
6679
/**
6780
* The default server options. These are merged with whatever you provide. Your
6881
* settings take precedence over these.
@@ -86,6 +99,7 @@ export const defaultSettings: () => Readonly<SettingsData> = () => {
8699
},
87100
playground: process.env.NODE_ENV === 'production' ? false : defaultPlaygroundSettings(),
88101
path: '/graphql',
102+
graphql: defaultGraphqlSettings(),
89103
}
90104
}
91105

@@ -121,6 +135,10 @@ export function playgroundSettings(settings: SettingsInput['playground']): Setti
121135
}
122136
}
123137

138+
export function graphqlSettings(settings: SettingsInput['graphql']): SettingsData['graphql'] {
139+
return { ...defaultGraphqlSettings(), ...settings }
140+
}
141+
124142
function validateGraphQLPath(path: string): string {
125143
let outputPath = path
126144

@@ -147,6 +165,7 @@ export function changeSettings(state: SettingsData, newSettings: SettingsInput):
147165
state.path = validateGraphQLPath(updatedSettings.path)
148166
state.port = updatedSettings.port
149167
state.startMessage = updatedSettings.startMessage
168+
state.graphql = graphqlSettings(updatedSettings.graphql)
150169
}
151170

152171
export function createServerSettingsManager() {

0 commit comments

Comments
 (0)