From 44dd08ca95fe6a8755e0eee5513f9740feabb48d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 15:52:30 +0000 Subject: [PATCH 1/3] fix: repair sanitized redirect_uri test and prettier formatting The new test was missing storage setup (state + codeVerifier) and a fetchMock response, causing exchangeAuthCode to bail out early before reaching fetch. Also fixes prettier formatting in types.ts and the test file. https://claude.ai/code/session_01MfwF59xe6Rs5kyxnQuPRAR --- lib/sessionManager/types.ts | 6 +++--- lib/utils/exchangeAuthCode.test.ts | 28 ++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/sessionManager/types.ts b/lib/sessionManager/types.ts index 37429e7d..09331b27 100644 --- a/lib/sessionManager/types.ts +++ b/lib/sessionManager/types.ts @@ -48,9 +48,9 @@ export type StorageSettingsType = { onRefreshHandler?: (refreshType: RefreshType) => Promise; }; -export abstract class SessionBase - implements SessionManager -{ +export abstract class SessionBase< + V extends string = StorageKeys, +> implements SessionManager { abstract asyncStore: boolean; private listeners: Set = new Set(); private notificationScheduled = false; diff --git a/lib/utils/exchangeAuthCode.test.ts b/lib/utils/exchangeAuthCode.test.ts index dd9176c8..401730ef 100644 --- a/lib/utils/exchangeAuthCode.test.ts +++ b/lib/utils/exchangeAuthCode.test.ts @@ -620,22 +620,34 @@ describe("exchangeAuthCode", () => { }); it("sends a sanitized redirect_uri so /token matches the /authorize value", async () => { - // Setup: stash valid state + codeVerifier, mock fetch, etc. - // (use the same setup helpers as the surrounding tests) + const store = new MemoryStorage(); + setActiveStorage(store); + + await store.setItems({ + [StorageKeys.state]: "abc", + [StorageKeys.codeVerifier]: "verifier", + }); const urlParams = new URLSearchParams({ state: "abc", code: "xyz" }); - + + fetchMock.mockResponseOnce( + JSON.stringify({ + access_token: "access_token", + refresh_token: "refresh_token", + id_token: "id_token", + }), + ); + await exchangeAuthCode({ urlParams, domain: "https://example.kinde.com", clientId: "test-client", - // Trailing slash here is the key — must be stripped before POST. redirectURL: "https://app.example.com/", }); - - const [, requestInit] = (global.fetch as jest.Mock).mock.calls.at(-1); - const body = new URLSearchParams(requestInit.body as string); - + + const [, requestInit] = fetchMock.mock.calls.at(-1)!; + const body = requestInit?.body as URLSearchParams; + expect(body.get("redirect_uri")).toBe("https://app.example.com"); }); }); From ca5b6949b527eba7eeb100710344556b15e1e1d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 15:53:31 +0000 Subject: [PATCH 2/3] chore: ignore package-lock.json This file was never tracked in the project and is generated locally by npm install. https://claude.ai/code/session_01MfwF59xe6Rs5kyxnQuPRAR --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3030104d..750e1364 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +package-lock.json # Editor directories and files .vscode/* From 15dd62f3de478b3b3e1cac19b15728afc6415105 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 16:03:33 +0000 Subject: [PATCH 3/3] fix: honour disableUrlSanitization in exchangeAuthCode redirect_uri When callers pass disableUrlSanitization: true to generateAuthUrl the authorize request sends the raw redirectURL. The token exchange must use the same value; unconditionally calling sanitizeUrl() would produce a mismatched redirect_uri and cause the provider to reject the exchange. Adds the disableUrlSanitization option (default false) to ExchangeAuthCodeParams and mirrors the same conditional used in mapLoginMethodParamsForUrl. Also adds a test covering the raw-URI path. https://claude.ai/code/session_01MfwF59xe6Rs5kyxnQuPRAR --- lib/utils/exchangeAuthCode.test.ts | 33 ++++++++++++++++++++++++++++++ lib/utils/exchangeAuthCode.ts | 6 +++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/utils/exchangeAuthCode.test.ts b/lib/utils/exchangeAuthCode.test.ts index 401730ef..7fe20174 100644 --- a/lib/utils/exchangeAuthCode.test.ts +++ b/lib/utils/exchangeAuthCode.test.ts @@ -650,4 +650,37 @@ describe("exchangeAuthCode", () => { expect(body.get("redirect_uri")).toBe("https://app.example.com"); }); + + it("preserves raw redirect_uri when disableUrlSanitization is true", async () => { + const store = new MemoryStorage(); + setActiveStorage(store); + + await store.setItems({ + [StorageKeys.state]: "abc", + [StorageKeys.codeVerifier]: "verifier", + }); + + const urlParams = new URLSearchParams({ state: "abc", code: "xyz" }); + + fetchMock.mockResponseOnce( + JSON.stringify({ + access_token: "access_token", + refresh_token: "refresh_token", + id_token: "id_token", + }), + ); + + await exchangeAuthCode({ + urlParams, + domain: "https://example.kinde.com", + clientId: "test-client", + redirectURL: "https://app.example.com/", + disableUrlSanitization: true, + }); + + const [, requestInit] = fetchMock.mock.calls.at(-1)!; + const body = requestInit?.body as URLSearchParams; + + expect(body.get("redirect_uri")).toBe("https://app.example.com/"); + }); }); diff --git a/lib/utils/exchangeAuthCode.ts b/lib/utils/exchangeAuthCode.ts index 8357daeb..2e7818ef 100644 --- a/lib/utils/exchangeAuthCode.ts +++ b/lib/utils/exchangeAuthCode.ts @@ -29,6 +29,7 @@ interface ExchangeAuthCodeParams { redirectURL: string; autoRefresh?: boolean; onRefresh?: (data: RefreshTokenResult) => void; + disableUrlSanitization?: boolean; } type ExchangeAuthCodeResultSuccess = { @@ -73,6 +74,7 @@ export const exchangeAuthCode = async ({ redirectURL, autoRefresh = false, onRefresh, + disableUrlSanitization = false, }: ExchangeAuthCodeParams): Promise => { const state = urlParams.get("state"); const code = urlParams.get("code"); @@ -144,7 +146,9 @@ export const exchangeAuthCode = async ({ code, code_verifier: codeVerifier, grant_type: "authorization_code", - redirect_uri: sanitizeUrl(redirectURL), + redirect_uri: disableUrlSanitization + ? redirectURL + : sanitizeUrl(redirectURL), }); if (clientSecret) {