From 3a0803aaa1c4329e3687389ae2dc0b2b53eeffbc Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Sat, 10 Oct 2026 13:33:03 -0700 Subject: [PATCH] fix(ui): store SSO exchange token via storeLoginToken to keep Secure flag (#45840) Co-authored-by: Nabil Ameena Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/components/networking.test.ts | 34 +++++++++++++++++++ .../src/components/networking.tsx | 2 +- .../src/utils/cookieUtils.test.ts | 23 +++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/networking.test.ts b/ui/litellm-dashboard/src/components/networking.test.ts index e60c0356751..1aa003750d7 100644 --- a/ui/litellm-dashboard/src/components/networking.test.ts +++ b/ui/litellm-dashboard/src/components/networking.test.ts @@ -118,6 +118,40 @@ describe("loginCall - storeLoginToken integration", () => { }); }); +describe("exchangeLoginCode - storeLoginToken integration", () => { + const originalFetch = global.fetch; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + it("calls storeLoginToken when exchange response includes token", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ token: "sso-jwt" }), + }) as unknown as typeof global.fetch; + const { storeLoginToken } = await import("@/utils/cookieUtils"); + const token = await Networking.exchangeLoginCode("some-login-code"); + expect(token).toBe("sso-jwt"); + expect(storeLoginToken).toHaveBeenCalledWith("sso-jwt"); + }); + + it("does not call storeLoginToken when exchange response has no token", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({}), + }) as unknown as typeof global.fetch; + const { storeLoginToken } = await import("@/utils/cookieUtils"); + const token = await Networking.exchangeLoginCode("some-login-code"); + expect(token).toBeUndefined(); + expect(storeLoginToken).not.toHaveBeenCalled(); + }); +}); + describe("modelInfoCall", () => { let currentFetch: typeof global.fetch; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 2b38da75411..54b2066c9c5 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -7102,7 +7102,7 @@ export const exchangeLoginCode = async (code: string, workerBaseUrl?: string | n const data = await response.json(); if (data.token) { - document.cookie = `token=${data.token}; path=/; SameSite=Lax`; + storeLoginToken(data.token); } return data.token; }; diff --git a/ui/litellm-dashboard/src/utils/cookieUtils.test.ts b/ui/litellm-dashboard/src/utils/cookieUtils.test.ts index a38b96e74c2..a1bf827560b 100644 --- a/ui/litellm-dashboard/src/utils/cookieUtils.test.ts +++ b/ui/litellm-dashboard/src/utils/cookieUtils.test.ts @@ -174,6 +174,29 @@ describe("cookieUtils", () => { expect(cookieSpy).toHaveBeenCalledWith(expect.stringContaining("path=/ui")); vi.restoreAllMocks(); }); + + it("should write the cookie with Secure on an https page", () => { + vi.stubGlobal("location", { ...window.location, protocol: "https:" }); + const cookieSpy = vi.spyOn(document, "cookie", "set"); + + storeLoginToken("https-jwt"); + + expect(cookieSpy).toHaveBeenCalledWith(expect.stringMatching(/^token=https-jwt; .*; Secure$/)); + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it("should write the cookie without Secure on an http page", () => { + vi.stubGlobal("location", { ...window.location, protocol: "http:" }); + const cookieSpy = vi.spyOn(document, "cookie", "set"); + + storeLoginToken("http-jwt"); + + expect(cookieSpy).toHaveBeenCalledWith(expect.stringMatching(/^token=http-jwt; /)); + expect(cookieSpy).not.toHaveBeenCalledWith(expect.stringContaining("Secure")); + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); }); describe("getCookie", () => {