From 94d83af968b8bfd37d71a09f8cd459cf72919cb3 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 1 Nov 2024 15:29:46 -0500 Subject: [PATCH 01/17] feat(backend): send BAPI version through headers --- packages/backend/src/api/request.ts | 5 ++++- packages/backend/src/constants.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/api/request.ts b/packages/backend/src/api/request.ts index ecf019ab5c1..77a78bc4552 100644 --- a/packages/backend/src/api/request.ts +++ b/packages/backend/src/api/request.ts @@ -2,7 +2,7 @@ import { ClerkAPIResponseError, parseError } from '@clerk/shared/error'; import type { ClerkAPIError, ClerkAPIErrorJSON } from '@clerk/types'; import snakecaseKeys from 'snakecase-keys'; -import { API_URL, API_VERSION, constants, USER_AGENT } from '../constants'; +import { API_URL, API_VERSION, constants, SUPPORTED_BAPI_VERSION, USER_AGENT } from '../constants'; // DO NOT CHANGE: Runtime needs to be imported as a default export so that we can stub its dependencies with Sinon.js // For more information refer to https://sinonjs.org/how-to/stub-dependency/ import runtime from '../runtime'; @@ -10,6 +10,8 @@ import { assertValidSecretKey } from '../util/optionsAssertions'; import { joinPaths } from '../util/path'; import { deserialize } from './resources/Deserializer'; +console.log('API_URL', SUPPORTED_BAPI_VERSION); + export type ClerkBackendApiRequestOptions = { method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT'; queryParams?: Record; @@ -81,6 +83,7 @@ export function buildRequest(options: BuildRequestOptions) { // Build headers const headers: Record = { Authorization: `Bearer ${secretKey}`, + 'Clerk-API-Version': SUPPORTED_BAPI_VERSION, 'User-Agent': userAgent, ...headerParams, }; diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 9b7d92e83e1..15509152780 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -4,6 +4,7 @@ export const API_VERSION = 'v1'; export const USER_AGENT = `${PACKAGE_NAME}@${PACKAGE_VERSION}`; export const MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60; export const JWKS_CACHE_TTL_MS = 1000 * 60 * 60; +export const SUPPORTED_BAPI_VERSION = '2024-10-01'; const Attributes = { AuthToken: '__clerkAuthToken', From 875cd183a863394df66a030c03f48e4aab899035 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 1 Nov 2024 15:33:24 -0500 Subject: [PATCH 02/17] changeset --- .changeset/angry-cycles-cheer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/angry-cycles-cheer.md diff --git a/.changeset/angry-cycles-cheer.md b/.changeset/angry-cycles-cheer.md new file mode 100644 index 00000000000..475e7d8d118 --- /dev/null +++ b/.changeset/angry-cycles-cheer.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': minor +--- + +Seding BAPI version through request headers From df945d6635641bf09228acb90a4598b9a088e87b Mon Sep 17 00:00:00 2001 From: Jacek Date: Sat, 2 Nov 2024 14:41:16 -0500 Subject: [PATCH 03/17] Added withFAPIHeaders resolver --- .../backend/src/api/__tests__/factory.test.ts | 142 +++++++++++------- packages/backend/src/constants.ts | 1 + packages/backend/src/mock-server.ts | 21 +++ 3 files changed, 107 insertions(+), 57 deletions(-) diff --git a/packages/backend/src/api/__tests__/factory.test.ts b/packages/backend/src/api/__tests__/factory.test.ts index cb5735e1142..449101bf48f 100644 --- a/packages/backend/src/api/__tests__/factory.test.ts +++ b/packages/backend/src/api/__tests__/factory.test.ts @@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw'; import { describe, expect, it } from 'vitest'; import userJson from '../../fixtures/user.json'; -import { server } from '../../mock-server'; +import { server, withFAPIHeaders } from '../../mock-server'; import { createBackendApiClient } from '../factory'; describe('api.client', () => { @@ -13,9 +13,12 @@ describe('api.client', () => { it('executes a successful backend API request for a single resource and parses the response', async () => { server.use( - http.get(`https://api.clerk.test/v1/users/user_deadbeef`, () => { - return HttpResponse.json(userJson); - }), + http.get( + `https://api.clerk.test/v1/users/user_deadbeef`, + withFAPIHeaders(() => { + return HttpResponse.json(userJson); + }), + ), ); const response = await apiClient.users.getUser('user_deadbeef'); @@ -30,12 +33,18 @@ describe('api.client', () => { it('executes 2 backend API request for users.getUserList()', async () => { server.use( - http.get(`https://api.clerk.test/v1/users`, () => { - return HttpResponse.json([userJson]); - }), - http.get(`https://api.clerk.test/v1/users/count`, () => { - return HttpResponse.json({ object: 'total_count', total_count: 2 }); - }), + http.get( + `https://api.clerk.test/v1/users`, + withFAPIHeaders(() => { + return HttpResponse.json([userJson]); + }), + ), + http.get( + `https://api.clerk.test/v1/users/count`, + withFAPIHeaders(() => { + return HttpResponse.json({ object: 'total_count', total_count: 2 }); + }), + ), ); const { data, totalCount } = await apiClient.users.getUserList({ offset: 2, @@ -51,12 +60,15 @@ describe('api.client', () => { it('executes a successful backend API request for a paginated response', async () => { server.use( - http.get(`https://api.clerk.test/v1/users/user_123/organization_memberships`, () => { - return HttpResponse.json({ - data: [{ id: '1' }], - total_count: 3, - }); - }), + http.get( + `https://api.clerk.test/v1/users/user_123/organization_memberships`, + withFAPIHeaders(() => { + return HttpResponse.json({ + data: [{ id: '1' }], + total_count: 3, + }); + }), + ), ); const { data: response, totalCount } = await apiClient.users.getOrganizationMembershipList({ @@ -72,9 +84,12 @@ describe('api.client', () => { it('executes a successful backend API request to create a new resource', async () => { server.use( - http.post(`https://api.clerk.test/v1/users`, () => { - return HttpResponse.json(userJson); - }), + http.post( + `https://api.clerk.test/v1/users`, + withFAPIHeaders(() => { + return HttpResponse.json(userJson); + }), + ), ); const response = await apiClient.users.createUser({ @@ -100,12 +115,15 @@ describe('api.client', () => { const traceId = 'trace_id_123'; server.use( - http.get(`https://api.clerk.test/v1/users/user_deadbeef`, () => { - return HttpResponse.json( - { errors: [mockErrorPayload], clerk_trace_id: traceId }, - { status: 422, headers: { 'cf-ray': traceId } }, - ); - }), + http.get( + `https://api.clerk.test/v1/users/user_deadbeef`, + withFAPIHeaders(() => { + return HttpResponse.json( + { errors: [mockErrorPayload], clerk_trace_id: traceId }, + { status: 422, headers: { 'cf-ray': traceId } }, + ); + }), + ), ); const errResponse = await apiClient.users.getUser('user_deadbeef').catch(err => err); @@ -120,9 +138,12 @@ describe('api.client', () => { it('executes a failed backend API request and include cf ray id when trace not present', async () => { server.use( - http.get(`https://api.clerk.test/v1/users/user_deadbeef`, () => { - return HttpResponse.json({ errors: [] }, { status: 500, headers: { 'cf-ray': 'mock_cf_ray' } }); - }), + http.get( + `https://api.clerk.test/v1/users/user_deadbeef`, + withFAPIHeaders(() => { + return HttpResponse.json({ errors: [] }, { status: 500, headers: { 'cf-ray': 'mock_cf_ray' } }); + }), + ), ); const errResponse = await apiClient.users.getUser('user_deadbeef').catch(err => err); @@ -134,13 +155,16 @@ describe('api.client', () => { it('executes a successful backend API request to delete a domain', async () => { const DOMAIN_ID = 'dmn_123'; server.use( - http.delete(`https://api.clerk.test/v1/domains/${DOMAIN_ID}`, () => { - return HttpResponse.json({ - object: 'domain', - id: DOMAIN_ID, - deleted: true, - }); - }), + http.delete( + `https://api.clerk.test/v1/domains/${DOMAIN_ID}`, + withFAPIHeaders(() => { + return HttpResponse.json({ + object: 'domain', + id: DOMAIN_ID, + deleted: true, + }); + }), + ), ); await apiClient.domains.deleteDomain(DOMAIN_ID); @@ -148,28 +172,32 @@ describe('api.client', () => { it('successfully retrieves user access tokens from backend API for a specific provider', async () => { server.use( - http.get('https://api.clerk.test/v1/users/user_deadbeef/oauth_access_tokens/oauth_google', ({ request }) => { - const paginated = new URL(request.url).searchParams.get('paginated'); - - if (!paginated) { - return new HttpResponse(null, { status: 404 }); - } - - return HttpResponse.json({ - data: [ - { - external_account_id: 'eac_2dYS7stz9bgxQsSRvNqEAHhuxvW', - object: 'oauth_access_token', - token: '', - provider: 'oauth_google', - public_metadata: {}, - label: null, - scopes: ['email', 'profile'], - }, - ], - total_count: 1, - }); - }), + http.get( + 'https://api.clerk.test/v1/users/user_deadbeef/oauth_access_tokens/oauth_google', + // @ts-expect-error FIXME: type this correctly + withFAPIHeaders(({ request }) => { + const paginated = new URL(request.url).searchParams.get('paginated'); + + if (!paginated) { + return new HttpResponse(null, { status: 404 }); + } + + return HttpResponse.json({ + data: [ + { + external_account_id: 'eac_2dYS7stz9bgxQsSRvNqEAHhuxvW', + object: 'oauth_access_token', + token: '', + provider: 'oauth_google', + public_metadata: {}, + label: null, + scopes: ['email', 'profile'], + }, + ], + total_count: 1, + }); + }), + ), ); const { data } = await apiClient.users.getUserOauthAccessToken('user_deadbeef', 'oauth_google'); diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 15509152780..469e35bc3cc 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -43,6 +43,7 @@ const Headers = { AuthMessage: 'x-clerk-auth-message', ClerkUrl: 'x-clerk-clerk-url', EnableDebug: 'x-clerk-debug', + ClerkApiVersion: 'x-clerk-api-version', ClerkRequestData: 'x-clerk-request-data', ClerkRedirectTo: 'x-clerk-redirect-to', CloudFrontForwardedProto: 'cloudfront-forwarded-proto', diff --git a/packages/backend/src/mock-server.ts b/packages/backend/src/mock-server.ts index 7fe0f5539da..1be8879a96d 100644 --- a/packages/backend/src/mock-server.ts +++ b/packages/backend/src/mock-server.ts @@ -1,5 +1,26 @@ +import type { DefaultBodyType, HttpResponseResolver, PathParams } from 'msw'; +import { HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; const globalHandlers: any[] = []; export const server = setupServer(...globalHandlers); + +// A higher-order response resolver that validates the request headers before proceeding +export function withFAPIHeaders(resolver: HttpResponseResolver): HttpResponseResolver { + return input => { + const { request } = input; + + if (!request.headers.get('Authorization')) { + return HttpResponse.json(null, { status: 401 }); + } + if (!request.headers.get('Clerk-API-Version')) { + return HttpResponse.json(null, { status: 400 }); + } + if (!request.headers.get('User-Agent') || request.headers.get('User-Agent') !== '@clerk/backend@0.0.0-test') { + return HttpResponse.json(null, { status: 400 }); + } + + return resolver(input); + }; +} From 910325c389535ccd4a07e89be39fcd3bcb856056 Mon Sep 17 00:00:00 2001 From: Jacek Date: Sat, 2 Nov 2024 14:49:19 -0500 Subject: [PATCH 04/17] Assert correct headers are being sent --- packages/backend/src/mock-server.ts | 6 +- .../src/tokens/__tests__/authStatus.test.ts | 2 + .../backend/src/tokens/__tests__/keys.test.ts | 56 ++++++++++++------- .../src/tokens/__tests__/verify.test.ts | 20 ++++--- packages/backend/src/tokens/keys.ts | 3 +- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/packages/backend/src/mock-server.ts b/packages/backend/src/mock-server.ts index 1be8879a96d..c72b91b389d 100644 --- a/packages/backend/src/mock-server.ts +++ b/packages/backend/src/mock-server.ts @@ -17,9 +17,9 @@ export function withFAPIHeaders(resolver: HttpResponseResolver): HttpResponseRes if (!request.headers.get('Clerk-API-Version')) { return HttpResponse.json(null, { status: 400 }); } - if (!request.headers.get('User-Agent') || request.headers.get('User-Agent') !== '@clerk/backend@0.0.0-test') { - return HttpResponse.json(null, { status: 400 }); - } + // if (!request.headers.get('User-Agent') || request.headers.get('User-Agent') !== '@clerk/backend@0.0.0-test') { + // return HttpResponse.json(null, { status: 400 }); + // } return resolver(input); }; diff --git a/packages/backend/src/tokens/__tests__/authStatus.test.ts b/packages/backend/src/tokens/__tests__/authStatus.test.ts index 397c55d9fe3..87b50c567b1 100644 --- a/packages/backend/src/tokens/__tests__/authStatus.test.ts +++ b/packages/backend/src/tokens/__tests__/authStatus.test.ts @@ -5,6 +5,7 @@ import { handshake, signedIn, signedOut } from '../authStatus'; describe('signed-in', () => { it('does not include debug headers', () => { const authObject = signedIn({} as any, {} as any, undefined, 'token'); + expect(authObject.headers.get('x-clerk-auth-status')).toBeNull(); expect(authObject.headers.get('x-clerk-auth-reason')).toBeNull(); expect(authObject.headers.get('x-clerk-auth-message')).toBeNull(); @@ -13,6 +14,7 @@ describe('signed-in', () => { it('authObject returned by toAuth() returns the token passed', async () => { const signedInAuthObject = signedIn({} as any, { sid: 'sid' } as any, undefined, 'token').toAuth(); const token = await signedInAuthObject.getToken(); + expect(token).toBe('token'); }); }); diff --git a/packages/backend/src/tokens/__tests__/keys.test.ts b/packages/backend/src/tokens/__tests__/keys.test.ts index 22974d252c0..a58eb6cd650 100644 --- a/packages/backend/src/tokens/__tests__/keys.test.ts +++ b/packages/backend/src/tokens/__tests__/keys.test.ts @@ -11,7 +11,7 @@ import { mockRsaJwk, mockRsaJwkKid, } from '../../fixtures'; -import { server } from '../../mock-server'; +import { server, withFAPIHeaders } from '../../mock-server'; import { loadClerkJWKFromLocal, loadClerkJWKFromRemote } from '../keys'; describe('tokens.loadClerkJWKFromLocal(localKey)', () => { @@ -48,9 +48,12 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { it('loads JWKS from Backend API when secretKey is provided', async () => { server.use( - http.get('https://api.clerk.com/v1/jwks', () => { - return HttpResponse.json(mockJwks); - }), + http.get( + 'https://api.clerk.com/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json(mockJwks); + }), + ), ); const jwk = await loadClerkJWKFromRemote({ secretKey: 'sk_test_deadbeef', @@ -63,9 +66,12 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { it('loads JWKS from Backend API using the provided apiUrl', async () => { server.use( - http.get('https://api.clerk.test/v1/jwks', () => { - return HttpResponse.json(mockJwks); - }), + http.get( + 'https://api.clerk.test/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json(mockJwks); + }), + ), ); const jwk = await loadClerkJWKFromRemote({ @@ -80,9 +86,12 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { it('caches JWK by kid', async () => { server.use( - http.get('https://api.clerk.com/v1/jwks', () => { - return HttpResponse.json(mockJwks); - }), + http.get( + 'https://api.clerk.com/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json(mockJwks); + }), + ), ); let jwk = await loadClerkJWKFromRemote({ @@ -100,9 +109,12 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { it('retries five times with exponential back-off policy to fetch JWKS before it fails', async () => { server.use( - http.get('https://api.clerk.com/v1/jwks', () => { - return HttpResponse.json({}, { status: 503 }); - }), + http.get( + 'https://api.clerk.com/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json({}, { status: 503 }); + }), + ), ); await expect(async () => { @@ -127,9 +139,12 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { it('throws an error when no JWK matches the provided kid', async () => { server.use( - http.get('https://api.clerk.com/v1/jwks', () => { - return HttpResponse.json(mockJwks); - }), + http.get( + 'https://api.clerk.com/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json(mockJwks); + }), + ), ); const kid = 'ins_whatever'; @@ -146,9 +161,12 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { it('cache TTLs do not conflict', async () => { server.use( - http.get('https://api.clerk.com/v1/jwks', () => { - return HttpResponse.json(mockJwks); - }), + http.get( + 'https://api.clerk.com/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json(mockJwks); + }), + ), ); let jwk = await loadClerkJWKFromRemote({ diff --git a/packages/backend/src/tokens/__tests__/verify.test.ts b/packages/backend/src/tokens/__tests__/verify.test.ts index 85e966f65b6..e093127be5e 100644 --- a/packages/backend/src/tokens/__tests__/verify.test.ts +++ b/packages/backend/src/tokens/__tests__/verify.test.ts @@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { mockJwks, mockJwt, mockJwtPayload } from '../../fixtures'; -import { server } from '../../mock-server'; +import { server, withFAPIHeaders } from '../../mock-server'; import { verifyToken } from '../verify'; describe('tokens.verify(token, options)', () => { @@ -16,9 +16,12 @@ describe('tokens.verify(token, options)', () => { it('verifies the provided session JWT', async () => { server.use( - http.get('https://api.clerk.test/v1/jwks', () => { - return HttpResponse.json(mockJwks); - }), + http.get( + 'https://api.clerk.test/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json(mockJwks); + }), + ), ); const { data } = await verifyToken(mockJwt, { @@ -33,9 +36,12 @@ describe('tokens.verify(token, options)', () => { it('verifies the token by fetching the JWKs from Backend API when secretKey is provided', async () => { server.use( - http.get('https://api.clerk.com/v1/jwks', () => { - return HttpResponse.json(mockJwks); - }), + http.get( + 'https://api.clerk.com/v1/jwks', + withFAPIHeaders(() => { + return HttpResponse.json(mockJwks); + }), + ), ); const { data } = await verifyToken(mockJwt, { diff --git a/packages/backend/src/tokens/keys.ts b/packages/backend/src/tokens/keys.ts index 1512ab48018..edd82bf2d2f 100644 --- a/packages/backend/src/tokens/keys.ts +++ b/packages/backend/src/tokens/keys.ts @@ -1,4 +1,4 @@ -import { API_URL, API_VERSION, MAX_CACHE_LAST_UPDATED_AT_SECONDS } from '../constants'; +import { API_URL, API_VERSION, MAX_CACHE_LAST_UPDATED_AT_SECONDS, SUPPORTED_BAPI_VERSION } from '../constants'; import { TokenVerificationError, TokenVerificationErrorAction, @@ -166,6 +166,7 @@ async function fetchJWKSFromBAPI(apiUrl: string, key: string, apiVersion: string const response = await (process.env.NODE_ENV === 'test' ? fetch : runtime.fetch)(url.href, { headers: { Authorization: `Bearer ${key}`, + 'Clerk-API-Version': SUPPORTED_BAPI_VERSION, 'Content-Type': 'application/json', }, }); From d3f176e32bd92a0410ba5be9464f7c652abae50e Mon Sep 17 00:00:00 2001 From: Jacek Date: Sat, 2 Nov 2024 14:55:50 -0500 Subject: [PATCH 05/17] WIP --- packages/expo-passkeys/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/expo-passkeys/package.json b/packages/expo-passkeys/package.json index 7710bad8d43..a4d12fbe290 100644 --- a/packages/expo-passkeys/package.json +++ b/packages/expo-passkeys/package.json @@ -21,10 +21,10 @@ "types": "build/index.d.ts", "scripts": { "_build": "EXPO_NONINTERACTIVE=1 expo-module build", + "_lint": "expo-module lint", "build:watch": "expo-module build", "clean": "expo-module clean", "expo-module": "expo-module", - "_lint": "expo-module lint", "open:android": "open -a \"Android Studio\" example/android", "open:ios": "xed example/ios", "prepare": "expo-module prepare", From 72aa53ffe574d034b0d5ae80defeec50f52fb648 Mon Sep 17 00:00:00 2001 From: Jacek Date: Sat, 2 Nov 2024 19:55:06 -0500 Subject: [PATCH 06/17] WIP --- packages/backend/src/api/request.ts | 2 -- packages/backend/src/constants.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/backend/src/api/request.ts b/packages/backend/src/api/request.ts index 394535ae6ee..8fa1bb47db9 100644 --- a/packages/backend/src/api/request.ts +++ b/packages/backend/src/api/request.ts @@ -8,8 +8,6 @@ import { assertValidSecretKey } from '../util/optionsAssertions'; import { joinPaths } from '../util/path'; import { deserialize } from './resources/Deserializer'; -console.log('API_URL', SUPPORTED_BAPI_VERSION); - export type ClerkBackendApiRequestOptions = { method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT'; queryParams?: Record; diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 469e35bc3cc..15509152780 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -43,7 +43,6 @@ const Headers = { AuthMessage: 'x-clerk-auth-message', ClerkUrl: 'x-clerk-clerk-url', EnableDebug: 'x-clerk-debug', - ClerkApiVersion: 'x-clerk-api-version', ClerkRequestData: 'x-clerk-request-data', ClerkRedirectTo: 'x-clerk-redirect-to', CloudFrontForwardedProto: 'cloudfront-forwarded-proto', From a3e393a99ca18ea0152c6a424d80acea60bcd8bd Mon Sep 17 00:00:00 2001 From: Jacek Date: Sun, 3 Nov 2024 15:51:13 -0600 Subject: [PATCH 07/17] rename function --- .../backend/src/api/__tests__/factory.test.ts | 20 +++++++++---------- packages/backend/src/mock-server.ts | 2 +- .../backend/src/tokens/__tests__/keys.test.ts | 14 ++++++------- .../src/tokens/__tests__/verify.test.ts | 6 +++--- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/backend/src/api/__tests__/factory.test.ts b/packages/backend/src/api/__tests__/factory.test.ts index 449101bf48f..5820f90e55c 100644 --- a/packages/backend/src/api/__tests__/factory.test.ts +++ b/packages/backend/src/api/__tests__/factory.test.ts @@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw'; import { describe, expect, it } from 'vitest'; import userJson from '../../fixtures/user.json'; -import { server, withFAPIHeaders } from '../../mock-server'; +import { server, validateHeaders } from '../../mock-server'; import { createBackendApiClient } from '../factory'; describe('api.client', () => { @@ -15,7 +15,7 @@ describe('api.client', () => { server.use( http.get( `https://api.clerk.test/v1/users/user_deadbeef`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(userJson); }), ), @@ -35,13 +35,13 @@ describe('api.client', () => { server.use( http.get( `https://api.clerk.test/v1/users`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json([userJson]); }), ), http.get( `https://api.clerk.test/v1/users/count`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json({ object: 'total_count', total_count: 2 }); }), ), @@ -62,7 +62,7 @@ describe('api.client', () => { server.use( http.get( `https://api.clerk.test/v1/users/user_123/organization_memberships`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json({ data: [{ id: '1' }], total_count: 3, @@ -86,7 +86,7 @@ describe('api.client', () => { server.use( http.post( `https://api.clerk.test/v1/users`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(userJson); }), ), @@ -117,7 +117,7 @@ describe('api.client', () => { server.use( http.get( `https://api.clerk.test/v1/users/user_deadbeef`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json( { errors: [mockErrorPayload], clerk_trace_id: traceId }, { status: 422, headers: { 'cf-ray': traceId } }, @@ -140,7 +140,7 @@ describe('api.client', () => { server.use( http.get( `https://api.clerk.test/v1/users/user_deadbeef`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json({ errors: [] }, { status: 500, headers: { 'cf-ray': 'mock_cf_ray' } }); }), ), @@ -157,7 +157,7 @@ describe('api.client', () => { server.use( http.delete( `https://api.clerk.test/v1/domains/${DOMAIN_ID}`, - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json({ object: 'domain', id: DOMAIN_ID, @@ -175,7 +175,7 @@ describe('api.client', () => { http.get( 'https://api.clerk.test/v1/users/user_deadbeef/oauth_access_tokens/oauth_google', // @ts-expect-error FIXME: type this correctly - withFAPIHeaders(({ request }) => { + validateHeaders(({ request }) => { const paginated = new URL(request.url).searchParams.get('paginated'); if (!paginated) { diff --git a/packages/backend/src/mock-server.ts b/packages/backend/src/mock-server.ts index c72b91b389d..f7f4f757540 100644 --- a/packages/backend/src/mock-server.ts +++ b/packages/backend/src/mock-server.ts @@ -7,7 +7,7 @@ const globalHandlers: any[] = []; export const server = setupServer(...globalHandlers); // A higher-order response resolver that validates the request headers before proceeding -export function withFAPIHeaders(resolver: HttpResponseResolver): HttpResponseResolver { +export function validateHeaders(resolver: HttpResponseResolver): HttpResponseResolver { return input => { const { request } = input; diff --git a/packages/backend/src/tokens/__tests__/keys.test.ts b/packages/backend/src/tokens/__tests__/keys.test.ts index a58eb6cd650..79bb10b05fc 100644 --- a/packages/backend/src/tokens/__tests__/keys.test.ts +++ b/packages/backend/src/tokens/__tests__/keys.test.ts @@ -11,7 +11,7 @@ import { mockRsaJwk, mockRsaJwkKid, } from '../../fixtures'; -import { server, withFAPIHeaders } from '../../mock-server'; +import { server, validateHeaders } from '../../mock-server'; import { loadClerkJWKFromLocal, loadClerkJWKFromRemote } from '../keys'; describe('tokens.loadClerkJWKFromLocal(localKey)', () => { @@ -50,7 +50,7 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { server.use( http.get( 'https://api.clerk.com/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(mockJwks); }), ), @@ -68,7 +68,7 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { server.use( http.get( 'https://api.clerk.test/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(mockJwks); }), ), @@ -88,7 +88,7 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { server.use( http.get( 'https://api.clerk.com/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(mockJwks); }), ), @@ -111,7 +111,7 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { server.use( http.get( 'https://api.clerk.com/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json({}, { status: 503 }); }), ), @@ -141,7 +141,7 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { server.use( http.get( 'https://api.clerk.com/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(mockJwks); }), ), @@ -163,7 +163,7 @@ describe('tokens.loadClerkJWKFromRemote(options)', () => { server.use( http.get( 'https://api.clerk.com/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(mockJwks); }), ), diff --git a/packages/backend/src/tokens/__tests__/verify.test.ts b/packages/backend/src/tokens/__tests__/verify.test.ts index e093127be5e..895f948b064 100644 --- a/packages/backend/src/tokens/__tests__/verify.test.ts +++ b/packages/backend/src/tokens/__tests__/verify.test.ts @@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { mockJwks, mockJwt, mockJwtPayload } from '../../fixtures'; -import { server, withFAPIHeaders } from '../../mock-server'; +import { server, validateHeaders } from '../../mock-server'; import { verifyToken } from '../verify'; describe('tokens.verify(token, options)', () => { @@ -18,7 +18,7 @@ describe('tokens.verify(token, options)', () => { server.use( http.get( 'https://api.clerk.test/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(mockJwks); }), ), @@ -38,7 +38,7 @@ describe('tokens.verify(token, options)', () => { server.use( http.get( 'https://api.clerk.com/v1/jwks', - withFAPIHeaders(() => { + validateHeaders(() => { return HttpResponse.json(mockJwks); }), ), From 2017ebc0a4b805dc5b4149da200d4df45d4a3f88 Mon Sep 17 00:00:00 2001 From: Jacek Date: Sun, 3 Nov 2024 19:40:51 -0600 Subject: [PATCH 08/17] add vitest coverage reporter --- package-lock.json | 145 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e4b8a95571..501628941e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@types/jest": "^29.3.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@vitest/coverage-v8": "^2.1.4", "citty": "^0.1.4", "conventional-changelog-conventionalcommits": "^4.6.3", "cpy-cli": "^5.0.0", @@ -17001,6 +17002,141 @@ "vite": "^4.3.9" } }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", + "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.7.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.4", + "vitest": "2.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/coverage-v8/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@vitest/expect": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", @@ -29859,7 +29995,9 @@ "license": "MIT" }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.1", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "license": "BSD-3-Clause", "engines": { "node": ">=8" @@ -29929,7 +30067,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "devOptional": true, "license": "BSD-3-Clause", "dependencies": { @@ -33610,7 +33750,6 @@ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", diff --git a/package.json b/package.json index 691360aea4c..80e075a9384 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@types/jest": "^29.3.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@vitest/coverage-v8": "^2.1.4", "citty": "^0.1.4", "conventional-changelog-conventionalcommits": "^4.6.3", "cpy-cli": "^5.0.0", From 7d3bbf414173c913e2034d70752ac2241196e980 Mon Sep 17 00:00:00 2001 From: Jacek Radko Date: Wed, 6 Nov 2024 10:05:11 -0600 Subject: [PATCH 09/17] Update packages/backend/src/constants.ts --- packages/backend/src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 15509152780..766e2f62fba 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -4,7 +4,7 @@ export const API_VERSION = 'v1'; export const USER_AGENT = `${PACKAGE_NAME}@${PACKAGE_VERSION}`; export const MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60; export const JWKS_CACHE_TTL_MS = 1000 * 60 * 60; -export const SUPPORTED_BAPI_VERSION = '2024-10-01'; +export const SUPPORTED_BAPI_VERSION = '2021-02-05'; const Attributes = { AuthToken: '__clerkAuthToken', From 02516f84fee6c78ec30829277b22e05b291fb2dd Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 7 Nov 2024 08:47:08 -0600 Subject: [PATCH 10/17] PR feedback: added error details to json errors --- packages/backend/src/mock-server.ts | 28 +++++++++++++++++++++++----- packages/backend/src/tokens/keys.ts | 9 ++++++++- packages/backend/vitest.config.mts | 2 +- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/mock-server.ts b/packages/backend/src/mock-server.ts index f7f4f757540..40a28b3296f 100644 --- a/packages/backend/src/mock-server.ts +++ b/packages/backend/src/mock-server.ts @@ -12,14 +12,32 @@ export function validateHeaders(resolver: HttpResponseResolver): HttpResponseRes const { request } = input; if (!request.headers.get('Authorization')) { - return HttpResponse.json(null, { status: 401 }); + return HttpResponse.json( + { + error: 'Unauthorized', + message: 'Missing Authorization header', + }, + { status: 401 }, + ); } if (!request.headers.get('Clerk-API-Version')) { - return HttpResponse.json(null, { status: 400 }); + return HttpResponse.json( + { + error: 'Bad request', + message: 'Missing Clerk-API-Version header', + }, + { status: 400 }, + ); + } + if (!request.headers.get('User-Agent') || request.headers.get('User-Agent') !== '@clerk/backend@0.0.0-test') { + return HttpResponse.json( + { + error: 'Bad request', + message: 'Missing or invalid User-Agent header', + }, + { status: 400 }, + ); } - // if (!request.headers.get('User-Agent') || request.headers.get('User-Agent') !== '@clerk/backend@0.0.0-test') { - // return HttpResponse.json(null, { status: 400 }); - // } return resolver(input); }; diff --git a/packages/backend/src/tokens/keys.ts b/packages/backend/src/tokens/keys.ts index edd82bf2d2f..456d6b30597 100644 --- a/packages/backend/src/tokens/keys.ts +++ b/packages/backend/src/tokens/keys.ts @@ -1,4 +1,10 @@ -import { API_URL, API_VERSION, MAX_CACHE_LAST_UPDATED_AT_SECONDS, SUPPORTED_BAPI_VERSION } from '../constants'; +import { + API_URL, + API_VERSION, + MAX_CACHE_LAST_UPDATED_AT_SECONDS, + SUPPORTED_BAPI_VERSION, + USER_AGENT, +} from '../constants'; import { TokenVerificationError, TokenVerificationErrorAction, @@ -168,6 +174,7 @@ async function fetchJWKSFromBAPI(apiUrl: string, key: string, apiVersion: string Authorization: `Bearer ${key}`, 'Clerk-API-Version': SUPPORTED_BAPI_VERSION, 'Content-Type': 'application/json', + 'User-Agent': USER_AGENT, }, }); diff --git a/packages/backend/vitest.config.mts b/packages/backend/vitest.config.mts index e1a3d868176..d6a14419236 100644 --- a/packages/backend/vitest.config.mts +++ b/packages/backend/vitest.config.mts @@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [], test: { - setupFiles: './vitest.setup.mts', includeSource: ['**/*.{js,ts,jsx,tsx}'], + setupFiles: './vitest.setup.mts', }, }); From 56b39fc95f343f1acbbea1cc0c43e2c9b7f04a3c Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 7 Nov 2024 08:58:25 -0600 Subject: [PATCH 11/17] WIP --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e12da30d759..64489ab6f65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@types/jest": "^29.3.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@vitest/coverage-v8": "^2.1.4", + "@vitest/coverage-v8": "2.1.4", "citty": "^0.1.4", "conventional-changelog-conventionalcommits": "^4.6.3", "cpy-cli": "^5.0.0", diff --git a/package.json b/package.json index 6f6fa5e60d8..ac883152323 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@types/jest": "^29.3.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@vitest/coverage-v8": "^2.1.4", + "@vitest/coverage-v8": "2.1.4", "citty": "^0.1.4", "conventional-changelog-conventionalcommits": "^4.6.3", "cpy-cli": "^5.0.0", From f150976e02e09510e0d43671b204198cf506cc1a Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 7 Nov 2024 09:15:38 -0600 Subject: [PATCH 12/17] Fix fetch patching for tests --- packages/backend/src/api/request.ts | 6 ++---- packages/backend/src/runtime.ts | 5 +++-- packages/backend/src/tokens/keys.ts | 3 +-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/api/request.ts b/packages/backend/src/api/request.ts index 8fa1bb47db9..df1d48b5870 100644 --- a/packages/backend/src/api/request.ts +++ b/packages/backend/src/api/request.ts @@ -87,8 +87,7 @@ export function buildRequest(options: BuildRequestOptions) { let res: Response | undefined; try { if (formData) { - // FIXME: We need to use the global fetch in tests because the runtime.fetch() is not intercepted by MSW - res = await (process.env.NODE_ENV === 'test' ? fetch : runtime.fetch)(finalUrl.href, { + res = await runtime.fetch(finalUrl.href, { method, headers, body: formData, @@ -100,8 +99,7 @@ export function buildRequest(options: BuildRequestOptions) { const hasBody = method !== 'GET' && bodyParams && Object.keys(bodyParams).length > 0; const body = hasBody ? { body: JSON.stringify(snakecaseKeys(bodyParams, { deep: false })) } : null; - // FIXME: We need to use the global fetch in tests because the runtime.fetch() is not intercepted by MSW - res = await (process.env.NODE_ENV === 'test' ? fetch : runtime.fetch)(finalUrl.href, { + res = await runtime.fetch(finalUrl.href, { method, headers, ...body, diff --git a/packages/backend/src/runtime.ts b/packages/backend/src/runtime.ts index 71650154a51..7b24a754c92 100644 --- a/packages/backend/src/runtime.ts +++ b/packages/backend/src/runtime.ts @@ -32,10 +32,11 @@ type Runtime = { // The globalThis object is supported for Node >= 12.0. // // https://github.com/supabase/supabase/issues/4417 -const globalFetch = fetch.bind(globalThis); export const runtime: Runtime = { crypto, - fetch: globalFetch, + get fetch() { + return process.env.NODE_ENV === 'test' ? fetch : fetch.bind(globalThis); + }, AbortController: globalThis.AbortController, Blob: globalThis.Blob, FormData: globalThis.FormData, diff --git a/packages/backend/src/tokens/keys.ts b/packages/backend/src/tokens/keys.ts index 456d6b30597..a9b7d97cad1 100644 --- a/packages/backend/src/tokens/keys.ts +++ b/packages/backend/src/tokens/keys.ts @@ -168,8 +168,7 @@ async function fetchJWKSFromBAPI(apiUrl: string, key: string, apiVersion: string const url = new URL(apiUrl); url.pathname = joinPaths(url.pathname, apiVersion, '/jwks'); - // FIXME: We need to use the global fetch in tests because the runtime.fetch() is not intercepted by MSW - const response = await (process.env.NODE_ENV === 'test' ? fetch : runtime.fetch)(url.href, { + const response = await runtime.fetch(url.href, { headers: { Authorization: `Bearer ${key}`, 'Clerk-API-Version': SUPPORTED_BAPI_VERSION, From 0462ae177c8e79a3399223f3baf987ec85e49e93 Mon Sep 17 00:00:00 2001 From: Jacek Radko Date: Thu, 7 Nov 2024 09:15:55 -0600 Subject: [PATCH 13/17] Update .changeset/angry-cycles-cheer.md Co-authored-by: Bryce Kalow --- .changeset/angry-cycles-cheer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/angry-cycles-cheer.md b/.changeset/angry-cycles-cheer.md index 475e7d8d118..5c831d151bd 100644 --- a/.changeset/angry-cycles-cheer.md +++ b/.changeset/angry-cycles-cheer.md @@ -2,4 +2,4 @@ '@clerk/backend': minor --- -Seding BAPI version through request headers +Send API version through request headers. From f27b44a70fd33c9239092e78fe0e2905d838cb96 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 7 Nov 2024 09:20:00 -0600 Subject: [PATCH 14/17] WIP --- packages/backend/vitest.config.mts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/backend/vitest.config.mts b/packages/backend/vitest.config.mts index d6a14419236..dc4f277fb5e 100644 --- a/packages/backend/vitest.config.mts +++ b/packages/backend/vitest.config.mts @@ -3,6 +3,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [], test: { + coverage: { + provider: 'v8', + }, includeSource: ['**/*.{js,ts,jsx,tsx}'], setupFiles: './vitest.setup.mts', }, From 82e4d00affc35b8109be6c78bc81d8d9ebacfb76 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 7 Nov 2024 11:03:37 -0600 Subject: [PATCH 15/17] WIP --- .../backend/src/api/__tests__/factory.test.ts | 3 +-- packages/backend/src/mock-server.ts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/api/__tests__/factory.test.ts b/packages/backend/src/api/__tests__/factory.test.ts index 5820f90e55c..103b078ea09 100644 --- a/packages/backend/src/api/__tests__/factory.test.ts +++ b/packages/backend/src/api/__tests__/factory.test.ts @@ -174,8 +174,7 @@ describe('api.client', () => { server.use( http.get( 'https://api.clerk.test/v1/users/user_deadbeef/oauth_access_tokens/oauth_google', - // @ts-expect-error FIXME: type this correctly - validateHeaders(({ request }) => { + validateHeaders(({ request }): any => { const paginated = new URL(request.url).searchParams.get('paginated'); if (!paginated) { diff --git a/packages/backend/src/mock-server.ts b/packages/backend/src/mock-server.ts index 40a28b3296f..bce4dc5afd6 100644 --- a/packages/backend/src/mock-server.ts +++ b/packages/backend/src/mock-server.ts @@ -1,5 +1,4 @@ -import type { DefaultBodyType, HttpResponseResolver, PathParams } from 'msw'; -import { HttpResponse } from 'msw'; +import { type DefaultBodyType, HttpResponse, type HttpResponseResolver, type PathParams } from 'msw'; import { setupServer } from 'msw/node'; const globalHandlers: any[] = []; @@ -7,10 +6,14 @@ const globalHandlers: any[] = []; export const server = setupServer(...globalHandlers); // A higher-order response resolver that validates the request headers before proceeding -export function validateHeaders(resolver: HttpResponseResolver): HttpResponseResolver { - return input => { - const { request } = input; - +export function validateHeaders< + Params extends PathParams, + RequestBodyType extends DefaultBodyType, + ResponseBodyType extends DefaultBodyType, +>( + resolver: HttpResponseResolver, +): HttpResponseResolver { + return async ({ request, requestId, params, cookies }) => { if (!request.headers.get('Authorization')) { return HttpResponse.json( { @@ -39,6 +42,6 @@ export function validateHeaders(resolver: HttpResponseResolver): HttpResponseRes ); } - return resolver(input); + return resolver({ request, requestId, params, cookies }); }; } From f7d6a064a2c565a99a6deef7f402e28c76db7e69 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 7 Nov 2024 11:06:17 -0600 Subject: [PATCH 16/17] WIP --- packages/backend/src/mock-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/mock-server.ts b/packages/backend/src/mock-server.ts index bce4dc5afd6..10bee5a939d 100644 --- a/packages/backend/src/mock-server.ts +++ b/packages/backend/src/mock-server.ts @@ -12,7 +12,7 @@ export function validateHeaders< ResponseBodyType extends DefaultBodyType, >( resolver: HttpResponseResolver, -): HttpResponseResolver { +): HttpResponseResolver> { return async ({ request, requestId, params, cookies }) => { if (!request.headers.get('Authorization')) { return HttpResponse.json( From f53e51b280c079a5b9d8b64979cc2f5c35129c1e Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 7 Nov 2024 11:28:38 -0600 Subject: [PATCH 17/17] WIP --- packages/backend/src/runtime.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/runtime.ts b/packages/backend/src/runtime.ts index 7b24a754c92..5f5895ca994 100644 --- a/packages/backend/src/runtime.ts +++ b/packages/backend/src/runtime.ts @@ -32,10 +32,13 @@ type Runtime = { // The globalThis object is supported for Node >= 12.0. // // https://github.com/supabase/supabase/issues/4417 +const globalFetch = fetch.bind(globalThis); + export const runtime: Runtime = { crypto, get fetch() { - return process.env.NODE_ENV === 'test' ? fetch : fetch.bind(globalThis); + // We need to use the globalFetch for Cloudflare Workers but the fetch for testing + return process.env.NODE_ENV === 'test' ? fetch : globalFetch; }, AbortController: globalThis.AbortController, Blob: globalThis.Blob,