Skip to content

Commit

Permalink
feat: disable introspection plugin (#2231)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
n1ru4l and github-actions[bot] committed Dec 22, 2022
1 parent a0576a5 commit c5b1cc4
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/graphql-yoga-2231-dependencies.md
@@ -0,0 +1,6 @@
---
'graphql-yoga': patch
---
dependencies updates:
- Updated dependency [`@envelop/parser-cache@^5.0.4` ↗︎](https://www.npmjs.com/package/@envelop/parser-cache/v/5.0.4) (from `5.0.4`, in `dependencies`)
- Updated dependency [`@envelop/validation-cache@^5.0.5` ↗︎](https://www.npmjs.com/package/@envelop/validation-cache/v/5.0.5) (from `5.0.4`, in `dependencies`)
10 changes: 5 additions & 5 deletions package.json
Expand Up @@ -99,17 +99,17 @@
"weak-napi": "2.0.2",
"wrangler": "2.6.1"
},
"resolutions": {
"graphql": "16.6.0",
"@envelop/core": "3.0.4",
"@changesets/assemble-release-plan": "5.2.1"
},
"pnpm": {
"patchedDependencies": {
"@changesets/assemble-release-plan@5.2.1": "patches/@changesets__assemble-release-plan@5.2.1.patch",
"@graphiql/react@0.13.3": "patches/@graphiql__react@0.13.3.patch",
"formdata-node@4.4.1": "patches/formdata-node@4.4.1.patch",
"nextra-theme-docs@2.0.0-beta.43": "patches/nextra-theme-docs@2.0.0-beta.43.patch"
},
"overrides": {
"graphql": "16.6.0",
"@envelop/core": "3.0.4",
"@changesets/assemble-release-plan": "5.2.1"
}
}
}
4 changes: 2 additions & 2 deletions packages/graphql-yoga/package.json
Expand Up @@ -50,8 +50,8 @@
},
"dependencies": {
"@envelop/core": "3.0.4",
"@envelop/parser-cache": "5.0.4",
"@envelop/validation-cache": "5.0.4",
"@envelop/parser-cache": "^5.0.4",
"@envelop/validation-cache": "^5.0.5",
"@graphql-tools/executor": "0.0.9",
"@graphql-tools/schema": "^9.0.0",
"@graphql-tools/utils": "^9.0.1",
Expand Down
@@ -0,0 +1,108 @@
import { useDisableIntrospection } from '@graphql-yoga/plugin-disable-introspection'
import { createYoga, createSchema } from 'graphql-yoga'

describe('disable introspection', () => {
test('can disable introspection', async () => {
const schema = createSchema({
typeDefs: /* GraphQL */ `
type Query {
_: Boolean
}
`,
})

const yoga = createYoga({ schema, plugins: [useDisableIntrospection()] })

const response = await yoga.fetch('http://yoga/graphql', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ query: `{ __schema { types { name } } }` }),
})

expect(response.status).toEqual(200)
const result = await response.json()
expect(result.data).toEqual(undefined)
expect(result.errors).toHaveLength(2)
})

test('can disable introspection conditionally', async () => {
const schema = createSchema({
typeDefs: /* GraphQL */ `
type Query {
_: Boolean
}
`,
})

const yoga = createYoga({
schema,
plugins: [
useDisableIntrospection({
isDisabled: () => true,
}),
],
})

const response = await yoga.fetch('http://yoga/graphql', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ query: `{ __schema { types { name } } }` }),
})

expect(response.status).toEqual(200)
const result = await response.json()
expect(result.data).toEqual(undefined)
expect(result.errors).toHaveLength(2)
})

test('can disable introspection based on headers', async () => {
const schema = createSchema({
typeDefs: /* GraphQL */ `
type Query {
_: Boolean
}
`,
})

const yoga = createYoga({
schema,
plugins: [
useDisableIntrospection({
isDisabled: (request) =>
request.headers.get('x-disable-introspection') === '1',
}),
],
// uncomment this and the tests will pass
// validationCache: false,
})

// First request uses the header to disable introspection
let response = await yoga.fetch('http://yoga/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-disable-introspection': '1',
},
body: JSON.stringify({ query: `{ __schema { types { name } } }` }),
})

expect(response.status).toEqual(200)
let result = await response.json()
expect(result.data).toEqual(undefined)
expect(result.errors).toHaveLength(2)

// Seconds request does not disable introspection
response = await yoga.fetch('http://yoga/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({ query: `{ __schema { types { name } } }` }),
})

expect(response.status).toEqual(200)
result = await response.json()
expect(result.data).toBeDefined()
expect(result.errors).toBeUndefined()
})
})
50 changes: 50 additions & 0 deletions packages/plugins/disable-introspection/package.json
@@ -0,0 +1,50 @@
{
"name": "@graphql-yoga/plugin-disable-introspection",
"version": "0.0.0",
"description": "Disable Introspection plugin for GraphQL Yoga.",
"repository": {
"type": "git",
"url": "https://github.com/dotansimha/graphql-yoga.git",
"directory": "packages/plugins/disable-introspection"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"scripts": {
"check": "tsc --pretty --noEmit"
},
"author": "Laurin Quast <laurinquast@googlemail.com>",
"license": "MIT",
"exports": {
".": {
"require": {
"types": "./dist/typings/index.d.cts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
},
"default": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"typescript": {
"definition": "dist/typings/index.d.ts"
},
"publishConfig": {
"directory": "dist",
"access": "public"
},
"peerDependencies": {
"graphql-yoga": "^3.1.1",
"graphql": "^15.2.0 || ^16.0.0"
},
"devDependencies": {
"graphql-yoga": "workspace:*"
},
"type": "module"
}
27 changes: 27 additions & 0 deletions packages/plugins/disable-introspection/src/index.ts
@@ -0,0 +1,27 @@
import type { Plugin, PromiseOrValue } from 'graphql-yoga'
import { NoSchemaIntrospectionCustomRule } from 'graphql'

type UseDisableIntrospectionArgs = {
isDisabled?: (request: Request) => PromiseOrValue<boolean>
}

const store = new WeakMap<Request, boolean>()

export const useDisableIntrospection = (
props?: UseDisableIntrospectionArgs,
): Plugin => {
return {
async onRequest({ request }) {
const isDisabled = props?.isDisabled
? await props.isDisabled(request)
: true
store.set(request, isDisabled)
},
onValidate({ addValidationRule, context }) {
const isDisabled = store.get(context.request) ?? true
if (isDisabled) {
addValidationRule(NoSchemaIntrospectionCustomRule)
}
},
}
}
17 changes: 12 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 28 additions & 1 deletion website/src/pages/docs/features/introspection.mdx
Expand Up @@ -20,7 +20,7 @@ GraphQL schema introspection is also a feature that allows clients to ask a Grap

```ts "Disabling GraphQL schema introspection with a plugin" {7}
import { createYoga } from 'graphql-yoga'
import { useDisableIntrospection } from '@envelop/disable-introspection'
import { useDisableIntrospection } from '@graphql-yoga/plugin-disable-introspection'

// Provide your schema
const yoga = createYoga({
Expand All @@ -34,6 +34,33 @@ server.listen(4000, () => {
})
```

## Disable Introspection based on the GraphQL Request

Somnetimes you want to allow introspectition for certain users.
You can access the `Request` object and determine based on that whether introspection should be enabled or not.
E.g. you can check the headers.

```ts "Disabling GraphQL schema introspection conditionally" {7}
import { createYoga } from 'graphql-yoga'
import { useDisableIntrospection } from '@graphql-yoga/plugin-disable-introspection'

// Provide your schema
const yoga = createYoga({
graphiql: false,
plugins: [
useDisableIntrospection({
isDisabled: (request) =>
request.headers.get('x-allow-introspection') !== 'secret-access-key'
})
]
})

const server = createServer(yoga)
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
})
```

## Disabling Field Suggestions

When executing invalid GraphQL operation the GraphQL engine will try to construct smart suggestions that hint typos in the executed GraphQL document.
Expand Down

0 comments on commit c5b1cc4

Please sign in to comment.