From 3fea19f2a01c85b7d837163d763fae107e8f5a53 Mon Sep 17 00:00:00 2001 From: antonio-iodice Date: Thu, 30 Nov 2023 12:14:38 +0100 Subject: [PATCH] fix: do not return 404s when trailing slashes or query params are in the url (#3120) Co-authored-by: Antonio Iodice --- .changeset/healthy-ghosts-look.md | 5 +++ .../graphql-yoga/__tests__/requests.spec.ts | 36 +++++++++++++++++++ .../src/plugins/use-graphiql.spec.ts | 28 +++++++++++++++ .../graphql-yoga/src/plugins/use-graphiql.ts | 4 ++- .../src/plugins/use-unhandled-route.ts | 4 ++- 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 .changeset/healthy-ghosts-look.md diff --git a/.changeset/healthy-ghosts-look.md b/.changeset/healthy-ghosts-look.md new file mode 100644 index 0000000000..70c565bf87 --- /dev/null +++ b/.changeset/healthy-ghosts-look.md @@ -0,0 +1,5 @@ +--- +'graphql-yoga': patch +--- + +Do not return 404 when using query params or trailing slashes diff --git a/packages/graphql-yoga/__tests__/requests.spec.ts b/packages/graphql-yoga/__tests__/requests.spec.ts index fde0b29091..a4d7be03f9 100644 --- a/packages/graphql-yoga/__tests__/requests.spec.ts +++ b/packages/graphql-yoga/__tests__/requests.spec.ts @@ -55,6 +55,42 @@ describe('requests', () => { expect(body.data.requestUrl).toBe('http://yoga/v1/mypath'); }); + it('supports trailing slash in url', async () => { + const yoga = createYoga({ + schema, + logging: false, + graphqlEndpoint: '/graphql', + }); + const response = await yoga.fetch('http://yoga/graphql/', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ query: '{ requestUrl }' }), + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body.errors).toBeUndefined(); + expect(body.data.requestUrl).toBe('http://yoga/graphql/'); + }); + + it('supports query params in url', async () => { + const yoga = createYoga({ + schema, + logging: false, + graphqlEndpoint: '/graphql', + }); + const response = await yoga.fetch('http://yoga/graphql?query=something+awesome', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ query: '{ requestUrl }' }), + }); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body.errors).toBeUndefined(); + expect(body.data.requestUrl).toBe('http://yoga/graphql?query=something+awesome'); + }); + it('allows you to bypass endpoint check with wildcard', async () => { const yoga = createYoga({ schema, diff --git a/packages/graphql-yoga/src/plugins/use-graphiql.spec.ts b/packages/graphql-yoga/src/plugins/use-graphiql.spec.ts index 71b7bb7f25..4ed45ca0f2 100644 --- a/packages/graphql-yoga/src/plugins/use-graphiql.spec.ts +++ b/packages/graphql-yoga/src/plugins/use-graphiql.spec.ts @@ -30,5 +30,33 @@ describe('GraphiQL', () => { expect(response.headers.get('content-type')).not.toEqual('text/html'); expect(response.status).toEqual(406); }); + + it('returns graphiql when passing query params and trailing slash', async () => { + const yoga = createYoga({ + graphiql: () => Promise.resolve({ title: 'Test GraphiQL' }), + }); + const responseWithQueryParams = await yoga.fetch( + 'http://localhost:3000/graphql?query=something+awesome', + { + method: 'GET', + headers: { + Accept: 'text/html', + }, + }, + ); + expect(responseWithQueryParams.headers.get('content-type')).toEqual('text/html'); + const resultWithQueryParams = await responseWithQueryParams.text(); + expect(resultWithQueryParams).toMatch(/Test GraphiQL<\/title>/); + + const responseWithTrailingSlash = await yoga.fetch('http://localhost:3000/graphql/', { + method: 'GET', + headers: { + Accept: 'text/html', + }, + }); + expect(responseWithTrailingSlash.headers.get('content-type')).toEqual('text/html'); + const resultWithTrailingSlash = await responseWithTrailingSlash.text(); + expect(resultWithTrailingSlash).toMatch(/<title>Test GraphiQL<\/title>/); + }); }); }); diff --git a/packages/graphql-yoga/src/plugins/use-graphiql.ts b/packages/graphql-yoga/src/plugins/use-graphiql.ts index 08dd5790b9..3e4eba59bf 100644 --- a/packages/graphql-yoga/src/plugins/use-graphiql.ts +++ b/packages/graphql-yoga/src/plugins/use-graphiql.ts @@ -98,9 +98,11 @@ export function useGraphiQL<TServerContext extends Record<string, any>>( }; return { async onRequest({ request, serverContext, fetchAPI, endResponse, url }) { + const requestedUrl = request.url.split('?')[0]; if ( shouldRenderGraphiQL(request) && - (request.url.endsWith(config.graphqlEndpoint) || + (requestedUrl.endsWith(config.graphqlEndpoint) || + requestedUrl.endsWith(`${config.graphqlEndpoint}/`) || url.pathname === config.graphqlEndpoint || getUrlPattern(fetchAPI).test(url)) ) { diff --git a/packages/graphql-yoga/src/plugins/use-unhandled-route.ts b/packages/graphql-yoga/src/plugins/use-unhandled-route.ts index aa499aea22..89a074f0f4 100644 --- a/packages/graphql-yoga/src/plugins/use-unhandled-route.ts +++ b/packages/graphql-yoga/src/plugins/use-unhandled-route.ts @@ -15,8 +15,10 @@ export function useUnhandledRoute(args: { } return { onRequest({ request, fetchAPI, endResponse, url }) { + const requestedUrl = request.url.split('?')[0]; if ( - !request.url.endsWith(args.graphqlEndpoint) && + !requestedUrl.endsWith(args.graphqlEndpoint) && + !requestedUrl.endsWith(`${args.graphqlEndpoint}/`) && url.pathname !== args.graphqlEndpoint && !getUrlPattern(fetchAPI).test(url) ) {