From 4024ba5a20d94520506dd7c7779d830434d3d1da Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Fri, 16 Dec 2022 20:40:42 +0100 Subject: [PATCH 1/5] feat: disable introspection plugin --- .../__tests__/disable-introspection.spec.ts | 108 ++++++++++++++++++ .../disable-introspection/package.json | 50 ++++++++ .../disable-introspection/src/index.ts | 28 +++++ pnpm-lock.yaml | 7 ++ 4 files changed, 193 insertions(+) create mode 100644 packages/plugins/disable-introspection/__tests__/disable-introspection.spec.ts create mode 100644 packages/plugins/disable-introspection/package.json create mode 100644 packages/plugins/disable-introspection/src/index.ts diff --git a/packages/plugins/disable-introspection/__tests__/disable-introspection.spec.ts b/packages/plugins/disable-introspection/__tests__/disable-introspection.spec.ts new file mode 100644 index 0000000000..f6b8cabd78 --- /dev/null +++ b/packages/plugins/disable-introspection/__tests__/disable-introspection.spec.ts @@ -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() + }) +}) diff --git a/packages/plugins/disable-introspection/package.json b/packages/plugins/disable-introspection/package.json new file mode 100644 index 0000000000..1cb133d7f2 --- /dev/null +++ b/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 ", + "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" +} diff --git a/packages/plugins/disable-introspection/src/index.ts b/packages/plugins/disable-introspection/src/index.ts new file mode 100644 index 0000000000..3da3441711 --- /dev/null +++ b/packages/plugins/disable-introspection/src/index.ts @@ -0,0 +1,28 @@ +import type { Plugin, PromiseOrValue } from 'graphql-yoga' +import { NoSchemaIntrospectionCustomRule } from 'graphql' + +type UseDisableIntrospectionArgs = { + isDisabled?: (request: Request) => PromiseOrValue +} + +const store = new WeakMap() + +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 }) { + console.log(store.get(context.request)) + const isDisabled = store.get(context.request) ?? true + if (isDisabled) { + addValidationRule(NoSchemaIntrospectionCustomRule) + } + }, + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd68bdad4a..de0a1eff60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -961,6 +961,13 @@ importers: '@whatwg-node/fetch': 0.5.3 publishDirectory: dist + packages/plugins/disable-introspection: + specifiers: + graphql-yoga: workspace:* + devDependencies: + graphql-yoga: link:../../graphql-yoga + publishDirectory: dist + packages/plugins/persisted-operations: specifiers: '@types/lru-cache': 7.10.9 From d86483a87d45bd94b27da6b288364bdd148062d5 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Fri, 16 Dec 2022 23:30:10 +0100 Subject: [PATCH 2/5] fix: use new alpha version with improved behavior --- package.json | 11 ++++++----- packages/plugins/disable-introspection/src/index.ts | 1 - pnpm-lock.yaml | 9 +++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 795153cf5a..47286c85db 100644 --- a/package.json +++ b/package.json @@ -99,17 +99,18 @@ "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", + "@envelop/validation-cache": "5.0.5-alpha-20221216222744-232e3b3d" } } } diff --git a/packages/plugins/disable-introspection/src/index.ts b/packages/plugins/disable-introspection/src/index.ts index 3da3441711..0a96d7ea8e 100644 --- a/packages/plugins/disable-introspection/src/index.ts +++ b/packages/plugins/disable-introspection/src/index.ts @@ -18,7 +18,6 @@ export const useDisableIntrospection = ( store.set(request, isDisabled) }, onValidate({ addValidationRule, context }) { - console.log(store.get(context.request)) const isDisabled = store.get(context.request) ?? true if (isDisabled) { addValidationRule(NoSchemaIntrospectionCustomRule) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de0a1eff60..68f291a5b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,7 @@ overrides: graphql: 16.6.0 '@envelop/core': 3.0.4 '@changesets/assemble-release-plan': 5.2.1 + '@envelop/validation-cache': 5.0.5-alpha-20221216222744-232e3b3d patchedDependencies: '@changesets/assemble-release-plan@5.2.1': @@ -870,7 +871,7 @@ importers: '@envelop/disable-introspection': 4.0.4 '@envelop/live-query': 5.0.4 '@envelop/parser-cache': 5.0.4 - '@envelop/validation-cache': 5.0.4 + '@envelop/validation-cache': 5.0.5-alpha-20221216222744-232e3b3d '@graphql-tools/executor': 0.0.9 '@graphql-tools/schema': ^9.0.0 '@graphql-tools/utils': ^9.0.1 @@ -892,7 +893,7 @@ importers: dependencies: '@envelop/core': 3.0.4 '@envelop/parser-cache': 5.0.4_a6sekiasy2tqr6d5gj7n2wtjli - '@envelop/validation-cache': 5.0.4_a6sekiasy2tqr6d5gj7n2wtjli + '@envelop/validation-cache': 5.0.5-alpha-20221216222744-232e3b3d_a6sekiasy2tqr6d5gj7n2wtjli '@graphql-tools/executor': 0.0.9_graphql@16.6.0 '@graphql-tools/schema': 9.0.4_graphql@16.6.0 '@graphql-tools/utils': 9.0.1_graphql@16.6.0 @@ -4216,8 +4217,8 @@ packages: dependencies: tslib: 2.4.1 - /@envelop/validation-cache/5.0.4_a6sekiasy2tqr6d5gj7n2wtjli: - resolution: {integrity: sha512-7b4BWtNMxSdXspwzFN2qmkEaaHfmuDz60uMlVFaMN4nA1Vc5duAV7GQWfAKl56VoePU6UwQ0i49Dm/plJfwxIQ==} + /@envelop/validation-cache/5.0.5-alpha-20221216222744-232e3b3d_a6sekiasy2tqr6d5gj7n2wtjli: + resolution: {integrity: sha512-U3ekSc0TANnFlsMH96ZcY6vL5N65o44n6x1s8+hQLoe74VxjYWrG14emMylxOG519b+mXVf3mTadkYm4bgvzeQ==} peerDependencies: '@envelop/core': ^3.0.4 graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 From 6671339f0595b206b627ef2dd5633bfd1441f3e3 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Fri, 16 Dec 2022 23:51:47 +0100 Subject: [PATCH 3/5] docs: disable introspection --- .../src/pages/docs/features/introspection.mdx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/website/src/pages/docs/features/introspection.mdx b/website/src/pages/docs/features/introspection.mdx index 5466a72079..931009b431 100644 --- a/website/src/pages/docs/features/introspection.mdx +++ b/website/src/pages/docs/features/introspection.mdx @@ -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({ @@ -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. From 6a3aaf6d1e8ab18466b84a15e58291224805180d Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Thu, 22 Dec 2022 11:57:48 +0100 Subject: [PATCH 4/5] chore: bump validation cache version --- package.json | 3 +-- packages/graphql-yoga/package.json | 4 ++-- pnpm-lock.yaml | 11 +++++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 47286c85db..1b6aef4424 100644 --- a/package.json +++ b/package.json @@ -109,8 +109,7 @@ "overrides": { "graphql": "16.6.0", "@envelop/core": "3.0.4", - "@changesets/assemble-release-plan": "5.2.1", - "@envelop/validation-cache": "5.0.5-alpha-20221216222744-232e3b3d" + "@changesets/assemble-release-plan": "5.2.1" } } } diff --git a/packages/graphql-yoga/package.json b/packages/graphql-yoga/package.json index 2a16c88fff..52612c4ea8 100644 --- a/packages/graphql-yoga/package.json +++ b/packages/graphql-yoga/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68f291a5b6..edae890158 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,7 +4,6 @@ overrides: graphql: 16.6.0 '@envelop/core': 3.0.4 '@changesets/assemble-release-plan': 5.2.1 - '@envelop/validation-cache': 5.0.5-alpha-20221216222744-232e3b3d patchedDependencies: '@changesets/assemble-release-plan@5.2.1': @@ -870,8 +869,8 @@ importers: '@envelop/core': 3.0.4 '@envelop/disable-introspection': 4.0.4 '@envelop/live-query': 5.0.4 - '@envelop/parser-cache': 5.0.4 - '@envelop/validation-cache': 5.0.5-alpha-20221216222744-232e3b3d + '@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 @@ -893,7 +892,7 @@ importers: dependencies: '@envelop/core': 3.0.4 '@envelop/parser-cache': 5.0.4_a6sekiasy2tqr6d5gj7n2wtjli - '@envelop/validation-cache': 5.0.5-alpha-20221216222744-232e3b3d_a6sekiasy2tqr6d5gj7n2wtjli + '@envelop/validation-cache': 5.0.5_a6sekiasy2tqr6d5gj7n2wtjli '@graphql-tools/executor': 0.0.9_graphql@16.6.0 '@graphql-tools/schema': 9.0.4_graphql@16.6.0 '@graphql-tools/utils': 9.0.1_graphql@16.6.0 @@ -4217,8 +4216,8 @@ packages: dependencies: tslib: 2.4.1 - /@envelop/validation-cache/5.0.5-alpha-20221216222744-232e3b3d_a6sekiasy2tqr6d5gj7n2wtjli: - resolution: {integrity: sha512-U3ekSc0TANnFlsMH96ZcY6vL5N65o44n6x1s8+hQLoe74VxjYWrG14emMylxOG519b+mXVf3mTadkYm4bgvzeQ==} + /@envelop/validation-cache/5.0.5_a6sekiasy2tqr6d5gj7n2wtjli: + resolution: {integrity: sha512-69sq5H7hvxE+7VV60i0bgnOiV1PX9GEJHKrBrVvyEZAXqYojKO3DP9jnLGryiPgVaBjN5yw12ge0l0s2gXbolQ==} peerDependencies: '@envelop/core': ^3.0.4 graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 From bdbc1f54c60fefd48a300af57c8677eb47431f22 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Dec 2022 11:17:50 +0000 Subject: [PATCH 5/5] chore(dependencies): updated changesets for modified dependencies --- .changeset/graphql-yoga-2231-dependencies.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/graphql-yoga-2231-dependencies.md diff --git a/.changeset/graphql-yoga-2231-dependencies.md b/.changeset/graphql-yoga-2231-dependencies.md new file mode 100644 index 0000000000..c58041305f --- /dev/null +++ b/.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`)