From 40e767615511d55e0909804d9cbc69e8365cab32 Mon Sep 17 00:00:00 2001 From: Dylan Vanmali Date: Wed, 18 Mar 2026 09:55:12 -0700 Subject: [PATCH] fix(oauth-provider): improve allowed paths for oauth_query for client plugin (#8320) --- packages/oauth-provider/src/client.ts | 18 +--- packages/oauth-provider/src/oauth.test.ts | 100 +++++++++++++++++++--- 2 files changed, 90 insertions(+), 28 deletions(-) diff --git a/packages/oauth-provider/src/client.ts b/packages/oauth-provider/src/client.ts index b5278e5fca..ad95e59c2f 100644 --- a/packages/oauth-provider/src/client.ts +++ b/packages/oauth-provider/src/client.ts @@ -33,24 +33,14 @@ export const oauthProviderClient = () => { : safeJSONParse>(ctx.body ?? "{}") : ctx.body; if (body?.oauth_query) return; - const pathname = - typeof ctx.url === "string" - ? new URL(ctx.url).pathname - : ctx.url.pathname; - // Should only need to run for /sign-in/email, /sign-in/social, /sign-in/oauth2, /oauth2/consent, /oauth2/continue if ( - pathname.endsWith("/sign-in/email") || - pathname.endsWith("/sign-in/social") || - pathname.endsWith("/sign-in/oauth2") || - pathname.endsWith("/oauth2/consent") || - pathname.endsWith("/oauth2/continue") + typeof window !== "undefined" && + window?.location?.search && + !(ctx.method === "GET" || ctx.method === "DELETE") ) { ctx.body = JSON.stringify({ ...body, - oauth_query: - typeof window !== "undefined" - ? parseSignedQuery(window?.location?.search) - : undefined, + oauth_query: parseSignedQuery(window.location.search), }); } }, diff --git a/packages/oauth-provider/src/oauth.test.ts b/packages/oauth-provider/src/oauth.test.ts index 28b1614fa7..3df663c323 100644 --- a/packages/oauth-provider/src/oauth.test.ts +++ b/packages/oauth-provider/src/oauth.test.ts @@ -447,6 +447,7 @@ describe("oauth - prompt", async () => { const scopes = ["openid", "profile", "email", "offline_access", "read:posts"]; let enableSelectAccount = false; let enablePostLogin = false; + let selectedPostLogin = false; let isUserRegistered = true; const { auth: authorizationServer, @@ -481,10 +482,12 @@ describe("oauth - prompt", async () => { page: "/select-organization", shouldRedirect({ session }) { if (!enablePostLogin) return false; + if (selectedPostLogin) return false; return !session?.activeOrganizationId; }, consentReferenceId({ session }) { if (!enablePostLogin) return undefined; + if (selectedPostLogin) return undefined; const activeOrganizationId = (session?.activeOrganizationId ?? undefined) as string | undefined; if (!activeOrganizationId) @@ -1426,6 +1429,84 @@ describe("oauth - prompt", async () => { enableSelectAccount = false; }); + it("shall allow user to post login via continue", async ({ + onTestFinished, + }) => { + if (!oauthClient?.client_id || !oauthClient?.client_secret) { + throw Error("beforeAll not run properly"); + } + enablePostLogin = true; + const { customFetchImpl: customFetchImplRP, cookieSetter } = + await createTestInstance(); + const client = createAuthClient({ + plugins: [genericOAuthClient(), organization()], + baseURL: rpBaseUrl, + fetchOptions: { + customFetchImpl: customFetchImplRP, + }, + }); + + // Generate authorize url + const oauthHeaders = new Headers(); + const data = await client.signIn.oauth2( + { + providerId, + callbackURL: "/success", + }, + { + headers, + throw: true, + onSuccess: cookieSetter(oauthHeaders), + }, + ); + expect(data.url).toContain( + `${authServerBaseUrl}/api/auth/oauth2/authorize`, + ); + expect(data.url).toContain(`client_id=${oauthClient.client_id}`); + + // Check for redirection to /select-organization + let selectOrgRedirectUri = ""; + await serverClient.$fetch(data.url, { + method: "GET", + headers, + onError(context) { + selectOrgRedirectUri = context.response.headers.get("Location") || ""; + cookieSetter(headers)(context); + }, + }); + expect(selectOrgRedirectUri).toContain(`/select-organization`); + expect(selectOrgRedirectUri).toContain( + `client_id=${oauthClient.client_id}`, + ); + expect(selectOrgRedirectUri).toContain(`scope=`); + expect(selectOrgRedirectUri).toContain(`state=`); + vi.stubGlobal("window", { + location: { + search: new URL(selectOrgRedirectUri, authServerBaseUrl).search, + }, + }); + onTestFinished(() => { + vi.unstubAllGlobals(); + }); + + selectedPostLogin = true; + const continueRes = await serverClient.oauth2.continue( + { + postLogin: true, + }, + { + headers, + throw: true, + onResponse: cookieSetter(headers), + }, + ); + expect(continueRes.url).toContain(redirectUri); + expect(continueRes.url).toContain(`code=`); + + selectedPostLogin = false; + enablePostLogin = false; + }); + it("shall allow user to select an organization/team post login and consent", async ({ onTestFinished, }) => { @@ -1487,6 +1568,7 @@ describe("oauth - prompt", async () => { vi.unstubAllGlobals(); }); + let consentRedirectUri = ""; // Select Account and continue auth flow await serverClient.organization.setActive( { @@ -1495,22 +1577,12 @@ describe("oauth - prompt", async () => { }, { headers, - throw: true, - onResponse: cookieSetter(headers), + onResponse(context) { + consentRedirectUri = context.response.headers.get("Location") || ""; + cookieSetter(headers)(context); + }, }, ); - const selectedAccountRes = await serverClient.oauth2.continue( - { - postLogin: true, - }, - { - headers, - throw: true, - onResponse: cookieSetter(headers), - }, - ); - expect(selectedAccountRes.redirect).toBeTruthy(); - const consentRedirectUri = selectedAccountRes?.url; expect(consentRedirectUri).toContain(`/consent`); expect(consentRedirectUri).toContain(`client_id=${oauthClient.client_id}`); expect(consentRedirectUri).toContain(`scope=`);