From 2b3b4ca3ee575f88c3225fd8d2988b97fc968d60 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Tue, 16 Apr 2024 15:48:55 +0700 Subject: [PATCH 01/14] feat(server-auth): Update getAuthenticationContext to support cookies and tokens both --- packages/api/package.json | 1 + .../getAuthenticationContext.test.ts | 117 +++++++++++++++--- packages/api/src/auth/index.ts | 62 ++++++++-- yarn.lock | 1 + 4 files changed, 158 insertions(+), 23 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index a91e210ac7f5..93c2f5afacdb 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -50,6 +50,7 @@ "@types/memjs": "1", "@types/pascalcase": "1.0.3", "@types/split2": "4.2.3", + "cookie": "0.6.0", "memjs": "1.3.2", "redis": "4.6.7", "split2": "4.2.0", diff --git a/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts b/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts index cfbebafd8c73..d4ac85b63375 100644 --- a/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts +++ b/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts @@ -3,17 +3,12 @@ import { describe, it, expect } from 'vitest' import { getAuthenticationContext } from '../index' -export const createMockedEvent = ({ - authProvider, -}: { - authProvider: string -}): APIGatewayProxyEvent => { +export const createMockedEvent = ( + headers: Record, +): APIGatewayProxyEvent => { return { body: null, - headers: { - 'auth-provider': authProvider, - authorization: 'Bearer auth-test-token', - }, + headers, multiValueHeaders: {}, httpMethod: 'POST', isBase64Encoded: false, @@ -56,7 +51,7 @@ export const createMockedEvent = ({ } } -describe('getAuthenticationContext', () => { +describe('getAuthenticationContext with bearer tokens', () => { it('Can take a single auth decoder for the given provider', async () => { const authDecoderOne = async (_token: string, type: string) => { if (type !== 'one') { @@ -71,7 +66,10 @@ describe('getAuthenticationContext', () => { const result = await getAuthenticationContext({ authDecoder: authDecoderOne, - event: createMockedEvent({ authProvider: 'one' }), + event: createMockedEvent({ + 'auth-provider': 'one', + authorization: 'Bearer auth-test-token', + }), context: {} as Context, }) @@ -104,7 +102,10 @@ describe('getAuthenticationContext', () => { const result = await getAuthenticationContext({ authDecoder: authDecoderOne, - event: createMockedEvent({ authProvider: 'some-other' }), + event: createMockedEvent({ + 'auth-provider': 'some-other', + authorization: 'Bearer auth-test-token', + }), context: {} as Context, }) @@ -123,7 +124,10 @@ describe('getAuthenticationContext', () => { it('Can take an empty array of auth decoders', async () => { const result = await getAuthenticationContext({ authDecoder: [], - event: createMockedEvent({ authProvider: 'two' }), + event: createMockedEvent({ + 'auth-provider': 'two', + authorization: 'Bearer auth-test-token', + }), context: {} as Context, }) @@ -164,7 +168,10 @@ describe('getAuthenticationContext', () => { const result = await getAuthenticationContext({ authDecoder: [authDecoderOne, authDecoderTwo], - event: createMockedEvent({ authProvider: 'two' }), + event: createMockedEvent({ + 'auth-provider': 'two', + authorization: 'Bearer auth-test-token', + }), context: {} as Context, }) @@ -185,7 +192,10 @@ describe('getAuthenticationContext', () => { it('Works even without any auth decoders', async () => { const result = await getAuthenticationContext({ - event: createMockedEvent({ authProvider: 'two' }), + event: createMockedEvent({ + 'auth-provider': 'two', + authorization: 'Bearer auth-test-token', + }), context: {} as Context, }) @@ -201,3 +211,80 @@ describe('getAuthenticationContext', () => { expect(token).toEqual('auth-test-token') }) }) + +describe('getAuthenticationContext with cookies', () => { + const authDecoderOne = async (_token: string, type: string) => { + if (type !== 'one') { + return null + } + + return { + iss: 'one', + sub: 'user-id', + } + } + + it('Can take a single auth decoder for the given provider', async () => { + const fetchRequest = new Request('http://localhost:3000', { + method: 'POST', + body: '', + headers: { + cookie: 'auth-provider=one; session=xx/yy/zz', + }, + }) + + const result = await getAuthenticationContext({ + authDecoder: authDecoderOne, + event: fetchRequest, + context: {} as Context, + }) + + if (!result) { + fail('Result is undefined') + } + + const [decoded, { type, schema, token }] = result + + expect(decoded).toMatchObject({ + iss: 'one', + sub: 'user-id', + }) + expect(type).toEqual('one') + expect(schema).toEqual('cookie') + // @TODO we need to rename this. It's not actually the token, because + // some auth providers will have a cookie where we don't know the key + expect(token).toEqual('auth-provider=one; session=xx/yy/zz') + }) + + it('Cookie takes precendence over auth header, if both are present', async () => { + const fetchRequest = new Request('http://localhost:3000', { + method: 'POST', + body: '', + headers: { + cookie: 'auth-provider=one; session=xx/yy/zz', + 'auth-provider': 'two', + authorization: 'Bearer im-a-two-token', + }, + }) + + const result = await getAuthenticationContext({ + authDecoder: authDecoderOne, + event: fetchRequest, + context: {} as Context, + }) + + if (!result) { + fail('Result is undefined') + } + + const [decoded, { type, schema, token }] = result + + expect(decoded).toMatchObject({ + iss: 'one', + sub: 'user-id', + }) + expect(type).toEqual('one') + expect(schema).toEqual('cookie') + expect(token).toEqual('auth-provider=one; session=xx/yy/zz') + }) +}) diff --git a/packages/api/src/auth/index.ts b/packages/api/src/auth/index.ts index 56eebb4178c5..eee7052473e3 100644 --- a/packages/api/src/auth/index.ts +++ b/packages/api/src/auth/index.ts @@ -1,6 +1,7 @@ export * from './parseJWT' import type { APIGatewayProxyEvent, Context as LambdaContext } from 'aws-lambda' +import { parse as parseCookie } from 'cookie' import { getEventHeader } from '../event' @@ -27,6 +28,27 @@ export interface AuthorizationHeader { token: string } +export const parseAuthorizationCookie = ( + event: APIGatewayProxyEvent | Request, +) => { + const cookie = getEventHeader(event, 'cookie') + + // Unauthenticated request + if (!cookie) { + return null + } + + const parsedCookie = parseCookie(cookie) + + return { + parsedCookie, + rawCookie: cookie, + // When not unauthenticated, this will be null/undefined + // Remember that the cookie header could contain other (unrelated) values! + type: parsedCookie['auth-provider'], + } +} + /** * Split the `Authorization` header into a schema and token part. */ @@ -77,16 +99,39 @@ export const getAuthenticationContext = async ({ event: APIGatewayProxyEvent | Request context: LambdaContext }): Promise => { - const type = getAuthProviderHeader(event) + const cookieHeader = parseAuthorizationCookie(event) + const typeFromHeader = getAuthProviderHeader(event) - // No `auth-provider` header means that the user is logged out, - // and none of this auth malarky is required. - if (!type) { + // Short-circuit - if no auth-provider or cookie header, its + // an unauthenticated request + if (!typeFromHeader && !cookieHeader) { return undefined } - const { schema, token } = parseAuthorizationHeader(event) + let token: string | undefined + let type: string | undefined + let schema: string | undefined + + // The actual session parsing is done by the auth decoder + // Priority given to cookie + if (cookieHeader) { + token = cookieHeader.rawCookie + type = cookieHeader.type + schema = 'cookie' + // If type is set in the header, use Bearer token auth (priority 2) + } else if (typeFromHeader) { + const parsedAuthHeader = parseAuthorizationHeader(event as any) + token = parsedAuthHeader.token + type = typeFromHeader + schema = parsedAuthHeader.schema + } + + // Unauthenticated request + if (!token || !type || !schema) { + return undefined + } + // Run through decoders until one returns a decoded payload let authDecoders: Array = [] if (Array.isArray(authDecoder)) { @@ -101,12 +146,13 @@ export const getAuthenticationContext = async ({ while (!decoded && i < authDecoders.length) { decoded = await authDecoders[i](token, type, { // @TODO: We will need to make a breaking change to support `Request` objects. - // We can remove this typecast - event: event, + event, context, }) i++ } - return [decoded, { type, schema, token }, { event, context }] + // @TODO we need to rename token. It's not actually the token - its the cookie header -because + // some auth providers will have a cookie where we don't know the key + return [decoded, { type, schema, token }, { event: event, context }] } diff --git a/yarn.lock b/yarn.lock index 0abff40f8520..8655ec94292c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7504,6 +7504,7 @@ __metadata: "@types/pascalcase": "npm:1.0.3" "@types/split2": "npm:4.2.3" "@whatwg-node/fetch": "npm:0.9.17" + cookie: "npm:0.6.0" core-js: "npm:3.36.1" humanize-string: "npm:2.1.0" jsonwebtoken: "npm:9.0.2" From e699cf0873258feb14be931da6c76db38f60abfa Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Tue, 16 Apr 2024 15:57:38 +0700 Subject: [PATCH 02/14] Update comments --- packages/api/src/auth/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/src/auth/index.ts b/packages/api/src/auth/index.ts index eee7052473e3..b066f9f32649 100644 --- a/packages/api/src/auth/index.ts +++ b/packages/api/src/auth/index.ts @@ -145,14 +145,14 @@ export const getAuthenticationContext = async ({ let i = 0 while (!decoded && i < authDecoders.length) { decoded = await authDecoders[i](token, type, { - // @TODO: We will need to make a breaking change to support `Request` objects. + // @MARK: When called from middleware, the decoder will pass Request, not Lambda event event, context, }) i++ } - // @TODO we need to rename token. It's not actually the token - its the cookie header -because + // @TODO should we rename token? It's not actually the token - its the cookie header -because // some auth providers will have a cookie where we don't know the key - return [decoded, { type, schema, token }, { event: event, context }] + return [decoded, { type, schema, token }, { event, context }] } From 42f341a06930dd3418f8c9930d6bbd958be2fa32 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Tue, 16 Apr 2024 16:10:57 +0700 Subject: [PATCH 03/14] Add changeset --- .changesets/10465.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changesets/10465.md diff --git a/.changesets/10465.md b/.changesets/10465.md new file mode 100644 index 000000000000..82a72d010521 --- /dev/null +++ b/.changesets/10465.md @@ -0,0 +1,13 @@ +- feat(server-auth): Update getAuthenticationContext to support cookies and tokens both (#10465) by @dac09 + +**1. Updates `getAuthenticationContext` to parse the cookie header and pass it to authDecoder.** + +Note that the authentication context itself does not pull out the token from cookies, because with some providers (e.g. supabase) - we don't know the name of the cookie. This is left to the authDecoder implementation. + +The return type from this function is actually just a deserialized cookie header i.e. +`cookie: auth-provider=one; session=xx/yy/zz; somethingElse=bsbs` => `{ 'auth-provider': 'one', session: 'xx/yy/zz', somethingElse: 'bsbs'` + +**2. Retains support for header/token based auth** +See test on line 259 of `packages/api/src/auth/__tests__/getAuthenticationContext.test.ts`. If a the `authorization` and `auth-provider` headers are passed in the request (as we do for SPA based auth) - then this will take precedence. + +The end result is that graphql requests will now work with middleware-based auth providers! From 52d29b94e1f3f0ce890aa9e726ad608740696801 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Tue, 16 Apr 2024 20:19:10 +0700 Subject: [PATCH 04/14] Uppercase Cookie --- packages/api/src/auth/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/api/src/auth/index.ts b/packages/api/src/auth/index.ts index b066f9f32649..9fcea991f213 100644 --- a/packages/api/src/auth/index.ts +++ b/packages/api/src/auth/index.ts @@ -31,7 +31,7 @@ export interface AuthorizationHeader { export const parseAuthorizationCookie = ( event: APIGatewayProxyEvent | Request, ) => { - const cookie = getEventHeader(event, 'cookie') + const cookie = getEventHeader(event, 'Cookie') // Unauthenticated request if (!cookie) { @@ -113,7 +113,6 @@ export const getAuthenticationContext = async ({ let schema: string | undefined // The actual session parsing is done by the auth decoder - // Priority given to cookie if (cookieHeader) { token = cookieHeader.rawCookie type = cookieHeader.type From 7b5f5e468a4acb72dafd061263b41e5d7fe52b15 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Tue, 16 Apr 2024 20:41:18 +0700 Subject: [PATCH 05/14] Remove authProvier header from FetchConfigProvider --- .../web/src/components/FetchConfigProvider.tsx | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/web/src/components/FetchConfigProvider.tsx b/packages/web/src/components/FetchConfigProvider.tsx index b79e992c3d07..220f5119c4f0 100644 --- a/packages/web/src/components/FetchConfigProvider.tsx +++ b/packages/web/src/components/FetchConfigProvider.tsx @@ -31,25 +31,9 @@ export const FetchConfigProvider: React.FC = ({ useAuth = useNoAuth, ...rest }) => { - const { isAuthenticated, type } = useAuth() - - if (!isAuthenticated) { - return ( - - ) - } - return ( ) From ba16af8787293529c42f7f0bde4f7133e6a948e5 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Tue, 16 Apr 2024 20:41:29 +0700 Subject: [PATCH 06/14] Update incorrect statement in changeset --- .changesets/10465.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changesets/10465.md b/.changesets/10465.md index 82a72d010521..0c667c4d47dd 100644 --- a/.changesets/10465.md +++ b/.changesets/10465.md @@ -8,6 +8,6 @@ The return type from this function is actually just a deserialized cookie header `cookie: auth-provider=one; session=xx/yy/zz; somethingElse=bsbs` => `{ 'auth-provider': 'one', session: 'xx/yy/zz', somethingElse: 'bsbs'` **2. Retains support for header/token based auth** -See test on line 259 of `packages/api/src/auth/__tests__/getAuthenticationContext.test.ts`. If a the `authorization` and `auth-provider` headers are passed in the request (as we do for SPA based auth) - then this will take precedence. +See test on line 259 of `packages/api/src/auth/__tests__/getAuthenticationContext.test.ts`. If a the `authorization` and `auth-provider` headers are passed in the request (as we do for SPA based auth) - then cookies will take precedence. The end result is that graphql requests will now work with middleware-based auth providers! From ad0f3cc81505ebf419f2da2b5ef42e5b15e9dc60 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Tue, 16 Apr 2024 21:16:12 +0700 Subject: [PATCH 07/14] Lint fix --- packages/web/src/components/FetchConfigProvider.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/web/src/components/FetchConfigProvider.tsx b/packages/web/src/components/FetchConfigProvider.tsx index 220f5119c4f0..ffa4582a76d8 100644 --- a/packages/web/src/components/FetchConfigProvider.tsx +++ b/packages/web/src/components/FetchConfigProvider.tsx @@ -27,10 +27,7 @@ interface Props { * Note that the auth bearer token is now passed in packages/web/src/apollo/index.tsx * as the token is retrieved async */ -export const FetchConfigProvider: React.FC = ({ - useAuth = useNoAuth, - ...rest -}) => { +export const FetchConfigProvider: React.FC = ({ ...rest }) => { return ( Date: Wed, 17 Apr 2024 12:25:04 +0700 Subject: [PATCH 08/14] Lint --- packages/web/src/components/FetchConfigProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/web/src/components/FetchConfigProvider.tsx b/packages/web/src/components/FetchConfigProvider.tsx index ffa4582a76d8..934916ae13a8 100644 --- a/packages/web/src/components/FetchConfigProvider.tsx +++ b/packages/web/src/components/FetchConfigProvider.tsx @@ -1,7 +1,6 @@ import React from 'react' import type { UseAuth } from '@redwoodjs/auth' -import { useNoAuth } from '@redwoodjs/auth' export const getApiGraphQLUrl = () => { return globalThis.RWJS_API_GRAPHQL_URL From 4851a254292f78e8d1b780d56765475f041ec6b8 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Wed, 17 Apr 2024 13:00:00 +0700 Subject: [PATCH 09/14] Update FetchConfigProvider test, as we no longer pass headers --- .../__tests__/FetchConfigProvider.test.tsx | 60 ++++++------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx index adc551689f04..4af008758649 100644 --- a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx +++ b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { render, screen, waitFor } from '@testing-library/react' +import { render, screen } from '@testing-library/react' import { describe, test, expect } from 'vitest' import type { AuthContextInterface } from '@redwoodjs/auth' @@ -14,24 +14,30 @@ const FetchConfigToString: React.FunctionComponent = () => { return <>{JSON.stringify(c)} } +type UnkownAuthContext = AuthContextInterface< + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown +> + describe('FetchConfigProvider', () => { - test('Unauthenticated user does not receive headers', () => { + test('Uri gets passed via fetch config provider', () => { render( ({ loading: false, isAuthenticated: false, - }) as AuthContextInterface< - unknown, - unknown, - unknown, - unknown, - unknown, - unknown, - unknown, - unknown - > + }) as UnkownAuthContext } > @@ -42,34 +48,4 @@ describe('FetchConfigProvider', () => { screen.getByText('{"uri":"https://api.example.com/graphql"}'), ).toBeInTheDocument() }) - - test('Authenticated user does receive headers', async () => { - render( - - ({ - loading: false, - isAuthenticated: true, - type: 'custom', - }) as AuthContextInterface< - unknown, - unknown, - unknown, - unknown, - unknown, - unknown, - unknown, - unknown - > - } - > - - , - ) - await waitFor(() => - screen.getByText( - '{"uri":"https://api.example.com/graphql","headers":{"auth-provider":"custom"}}', - ), - ) - }) }) From 25ac17cdf29c442dc06b66958a551b5311945617 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Wed, 17 Apr 2024 13:00:06 +0700 Subject: [PATCH 10/14] Fix TS issues in test --- packages/web/testing-library.d.ts | 4 ++++ packages/web/tsconfig.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 packages/web/testing-library.d.ts diff --git a/packages/web/testing-library.d.ts b/packages/web/testing-library.d.ts new file mode 100644 index 000000000000..1c391d05708c --- /dev/null +++ b/packages/web/testing-library.d.ts @@ -0,0 +1,4 @@ +// Extend expect(*) from Vitest with TestingLibraryMatchers +// The import path is a bit strange, but look inside the package! + +import '@testing-library/jest-dom/vitest' diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index 5787b2d0b025..c1f06c37204c 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -4,6 +4,6 @@ "rootDir": "src", "outDir": "dist" }, - "include": ["./src/**/*", "ambient.d.ts"], + "include": ["./src/**/*", "ambient.d.ts", "testing-library.d.ts"], "references": [{ "path": "../auth" }] } From 1e26c455ce41b55b215f43043100cf93f5ac6afb Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Wed, 17 Apr 2024 13:00:41 +0700 Subject: [PATCH 11/14] Typo --- .../web/src/components/__tests__/FetchConfigProvider.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx index 4af008758649..3e47fa267df2 100644 --- a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx +++ b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx @@ -14,7 +14,7 @@ const FetchConfigToString: React.FunctionComponent = () => { return <>{JSON.stringify(c)} } -type UnkownAuthContext = AuthContextInterface< +type UnknownAuthContext = AuthContextInterface< unknown, unknown, unknown, @@ -37,7 +37,7 @@ describe('FetchConfigProvider', () => { ({ loading: false, isAuthenticated: false, - }) as UnkownAuthContext + }) as UnknownAuthContext } > From 14b2539ef35eb0d14170c977912d681e7121deec Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Wed, 17 Apr 2024 15:44:20 +0700 Subject: [PATCH 12/14] Undo FetchConfigProvider change --- .../src/components/FetchConfigProvider.tsx | 29 +++++++++++- .../__tests__/FetchConfigProvider.test.tsx | 47 ++++++++++++++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/packages/web/src/components/FetchConfigProvider.tsx b/packages/web/src/components/FetchConfigProvider.tsx index 934916ae13a8..8d2574c68022 100644 --- a/packages/web/src/components/FetchConfigProvider.tsx +++ b/packages/web/src/components/FetchConfigProvider.tsx @@ -1,6 +1,7 @@ import React from 'react' import type { UseAuth } from '@redwoodjs/auth' +import { useNoAuth } from '@redwoodjs/auth' export const getApiGraphQLUrl = () => { return globalThis.RWJS_API_GRAPHQL_URL @@ -26,10 +27,34 @@ interface Props { * Note that the auth bearer token is now passed in packages/web/src/apollo/index.tsx * as the token is retrieved async */ -export const FetchConfigProvider: React.FC = ({ ...rest }) => { +export const FetchConfigProvider: React.FC = ({ + useAuth = useNoAuth, + ...rest +}) => { + const { isAuthenticated, type } = useAuth() + + if (!isAuthenticated) { + return ( + + ) + } + + // @NOTE: See packages/web/src/apollo/links.tsx + // Where we remove the auth-provider header if token is null. + // Token === null means you're logged out OR are using cookie/middleware auth + const headers = { + 'auth-provider': type, + } + return ( ) diff --git a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx index 3e47fa267df2..5569f3c7f1ec 100644 --- a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx +++ b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { render, screen } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' import { describe, test, expect } from 'vitest' import type { AuthContextInterface } from '@redwoodjs/auth' @@ -30,7 +30,7 @@ type UnknownAuthContext = AuthContextInterface< > describe('FetchConfigProvider', () => { - test('Uri gets passed via fetch config provider', () => { + test('Unauthenticated user does not receive headers', () => { render( @@ -48,4 +48,47 @@ describe('FetchConfigProvider', () => { screen.getByText('{"uri":"https://api.example.com/graphql"}'), ).toBeInTheDocument() }) + + test('Authenticated user does receive headers', async () => { + render( + + ({ + loading: false, + isAuthenticated: true, + type: 'custom', + }) as UnknownAuthContext + } + > + + , + ) + await waitFor(() => + screen.getByText( + '{"uri":"https://api.example.com/graphql","headers":{"auth-provider":"custom"}}', + ), + ) + }) + + test('Headers are NOT set when middleware auth is being used', async () => { + render( + + ({ + loading: false, + isAuthenticated: true, + type: 'custom', + useMiddlewareAuth: true, + }) as UnknownAuthContext + } + > + + , + ) + await waitFor(() => + screen.getByText( + '{"uri":"https://api.example.com/graphql","headers":{}}', + ), + ) + }) }) From 4d5bc950e5f58727ae2a845bab0b985ea2a35954 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Wed, 17 Apr 2024 15:44:32 +0700 Subject: [PATCH 13/14] Remove auth-provider header if token is null --- packages/web/src/apollo/links.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/web/src/apollo/links.tsx b/packages/web/src/apollo/links.tsx index 8003518de480..3484e3c2f529 100644 --- a/packages/web/src/apollo/links.tsx +++ b/packages/web/src/apollo/links.tsx @@ -57,7 +57,7 @@ export function createUpdateDataLink() { } export function createAuthApolloLink( authProviderType: string, - headers: + headersFromFetchProvider: | { 'auth-provider'?: string | undefined authorization?: string | undefined @@ -74,10 +74,16 @@ export function createAuthApolloLink( } : {} + if (!token) { + // If there's no token i.e. it's using middleware auth + // remove the auth-provider header + delete headersFromFetchProvider?.['auth-provider'] + } + operation.setContext(() => ({ headers: { ...operation.getContext().headers, - ...headers, + ...headersFromFetchProvider, // Duped auth headers, because we may remove the `FetchConfigProvider` at a later date. ...authHeaders, }, From 3cc28c08750bc312efd6fb60755d0e59cba2ff76 Mon Sep 17 00:00:00 2001 From: Daniel Choudhury Date: Wed, 17 Apr 2024 15:46:24 +0700 Subject: [PATCH 14/14] Remove unneeded test --- .../__tests__/FetchConfigProvider.test.tsx | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx index 5569f3c7f1ec..bd1d8868a978 100644 --- a/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx +++ b/packages/web/src/components/__tests__/FetchConfigProvider.test.tsx @@ -69,26 +69,4 @@ describe('FetchConfigProvider', () => { ), ) }) - - test('Headers are NOT set when middleware auth is being used', async () => { - render( - - ({ - loading: false, - isAuthenticated: true, - type: 'custom', - useMiddlewareAuth: true, - }) as UnknownAuthContext - } - > - - , - ) - await waitFor(() => - screen.getByText( - '{"uri":"https://api.example.com/graphql","headers":{}}', - ), - ) - }) })