From 2433efe2465f95147a71df113d8df48e73ba1615 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 22:08:50 +0000 Subject: [PATCH] fix(ui): use full-page navigation for auth-guard login redirect --- .../(dashboard)/hooks/useAuthorized.test.ts | 26 +++++++++---------- .../app/(dashboard)/hooks/useAuthorized.ts | 6 ++--- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts index 94f9d9173f0..bde4574f06a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts @@ -9,7 +9,6 @@ import useAuthorized from "./useAuthorized"; vi.unmock("@/app/(dashboard)/hooks/useAuthorized"); const { - replaceMock, clearTokenCookiesMock, getProxyBaseUrlMock, getUiConfigMock, @@ -17,20 +16,19 @@ const { checkTokenValidityMock, buildLoginUrlWithReturnMock, } = vi.hoisted(() => ({ - replaceMock: vi.fn(), clearTokenCookiesMock: vi.fn(), - getProxyBaseUrlMock: vi.fn(() => "http://proxy.example"), + getProxyBaseUrlMock: vi.fn(() => "https://proxy.example"), getUiConfigMock: vi.fn(), decodeTokenMock: vi.fn(), checkTokenValidityMock: vi.fn(), buildLoginUrlWithReturnMock: vi.fn((baseUrl: string) => baseUrl), })); -vi.mock("next/navigation", () => ({ - useRouter: () => ({ - replace: replaceMock, - }), -})); +const locationReplaceMock = vi.fn(); +Object.defineProperty(window, "location", { + configurable: true, + value: { ...window.location, replace: locationReplaceMock }, +}); vi.mock("@/components/networking", async (importOriginal) => { const actual = await importOriginal(); @@ -92,7 +90,7 @@ const clearCookie = () => { describe("useAuthorized", () => { afterEach(() => { - replaceMock.mockReset(); + locationReplaceMock.mockReset(); clearTokenCookiesMock.mockReset(); getProxyBaseUrlMock.mockClear(); getUiConfigMock.mockReset(); @@ -140,7 +138,7 @@ describe("useAuthorized", () => { expect(result.current.premiumUser).toBe(true); expect(result.current.disabledPersonalKeyCreation).toBe(false); expect(result.current.showSSOBanner).toBe(true); - expect(replaceMock).not.toHaveBeenCalled(); + expect(locationReplaceMock).not.toHaveBeenCalled(); expect(clearTokenCookiesMock).not.toHaveBeenCalled(); }); @@ -164,7 +162,7 @@ describe("useAuthorized", () => { expect(clearTokenCookiesMock).toHaveBeenCalled(); }); - expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); + expect(locationReplaceMock).toHaveBeenCalledWith("https://proxy.example/ui/login"); expect(result.current.accessToken).toBeNull(); expect(result.current.userRole).toBe("Undefined Role"); }); @@ -197,7 +195,7 @@ describe("useAuthorized", () => { const { result } = renderHook(() => useAuthorized(), { wrapper }); await waitFor(() => { - expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); + expect(locationReplaceMock).toHaveBeenCalledWith("https://proxy.example/ui/login"); }); expect(result.current.accessToken).toBe("api-key-123"); @@ -221,7 +219,7 @@ describe("useAuthorized", () => { const { result } = renderHook(() => useAuthorized(), { wrapper }); await waitFor(() => { - expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); + expect(locationReplaceMock).toHaveBeenCalledWith("https://proxy.example/ui/login"); }); expect(clearTokenCookiesMock).not.toHaveBeenCalled(); @@ -256,7 +254,7 @@ describe("useAuthorized", () => { expect(clearTokenCookiesMock).toHaveBeenCalled(); }); - expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); + expect(locationReplaceMock).toHaveBeenCalledWith("https://proxy.example/ui/login"); expect(checkTokenValidityMock).toHaveBeenCalledWith(token); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts index 8f8c403a4e9..08577cdabab 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts @@ -4,13 +4,11 @@ import { getProxyBaseUrl } from "@/components/networking"; import { clearTokenCookies, getCookie } from "@/utils/cookieUtils"; import { checkTokenValidity, decodeToken } from "@/utils/jwtUtils"; import { buildLoginUrlWithReturn, storeReturnUrl } from "@/utils/returnUrlUtils"; -import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo } from "react"; import { formatUserRole } from "@/utils/roles"; import { useUIConfig } from "./uiConfig/useUIConfig"; const useAuthorized = () => { - const router = useRouter(); const { data: uiConfig, isLoading: isUIConfigLoading } = useUIConfig(); const token = typeof document !== "undefined" ? getCookie("token") : null; @@ -25,8 +23,8 @@ const useAuthorized = () => { storeReturnUrl(); const baseLoginUrl = `${getProxyBaseUrl()}/ui/login`; const loginUrlWithReturn = buildLoginUrlWithReturn(baseLoginUrl); - router.replace(loginUrlWithReturn); - }, [router]); + window.location.replace(loginUrlWithReturn); + }, []); // Single useEffect for all redirect logic useEffect(() => {