From 6b74e8223bf74daada1412882d107dccc774b777 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 5 Dec 2025 12:23:25 -0800 Subject: [PATCH] change useAuthorized Hook to redirect to new login page --- .../(dashboard)/hooks/useAuthorized.test.ts | 80 +++++++++++++++++++ .../app/(dashboard)/hooks/useAuthorized.ts | 5 +- 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts new file mode 100644 index 00000000000..5059d5d69d1 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts @@ -0,0 +1,80 @@ +/* @vitest-environment jsdom */ +import { renderHook } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import useAuthorized from "./useAuthorized"; + +const replaceMock = vi.fn(); +const clearTokenCookiesMock = vi.fn(); +const getProxyBaseUrlMock = vi.fn(() => "http://proxy.example"); + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + replace: replaceMock, + }), +})); + +vi.mock("@/components/networking", () => ({ + getProxyBaseUrl: getProxyBaseUrlMock, +})); + +vi.mock("@/utils/cookieUtils", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + clearTokenCookies: clearTokenCookiesMock, + }; +}); + +const createJwt = (payload: Record) => { + const base64Url = btoa(JSON.stringify(payload)).replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_"); + return `eyJhbGciOiJub25lIn0.${base64Url}.signature`; +}; + +const clearCookie = () => { + document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; +}; + +describe("useAuthorized", () => { + afterEach(() => { + replaceMock.mockReset(); + clearTokenCookiesMock.mockReset(); + getProxyBaseUrlMock.mockClear(); + clearCookie(); + }); + + it("should decode the token and expose user details", () => { + const token = createJwt({ + key: "api-key-123", + user_id: "user-1", + user_email: "user@example.com", + user_role: "app_admin", + premium_user: true, + disabled_non_admin_personal_key_creation: false, + login_method: "username_password", + }); + document.cookie = `token=${token}; path=/;`; + + const { result } = renderHook(() => useAuthorized()); + + expect(result.current.token).toBe(token); + expect(result.current.accessToken).toBe("api-key-123"); + expect(result.current.userId).toBe("user-1"); + expect(result.current.userEmail).toBe("user@example.com"); + expect(result.current.userRole).toBe("Admin"); + expect(result.current.premiumUser).toBe(true); + expect(result.current.disabledPersonalKeyCreation).toBe(false); + expect(result.current.showSSOBanner).toBe(true); + expect(replaceMock).not.toHaveBeenCalled(); + }); + + it("should clear cookies and redirect on an invalid token", () => { + document.cookie = "token=invalid-token; path=/;"; + + const { result } = renderHook(() => useAuthorized()); + + expect(clearTokenCookiesMock).toHaveBeenCalled(); + expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); + expect(result.current.accessToken).toBeNull(); + expect(result.current.userRole).toBe("Unknown Role"); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts index cba7c1a3dc8..7610c6346be 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts @@ -4,6 +4,7 @@ import { useEffect, useMemo } from "react"; import { useRouter } from "next/navigation"; import { jwtDecode } from "jwt-decode"; import { clearTokenCookies, getCookie } from "@/utils/cookieUtils"; +import { getProxyBaseUrl } from "@/components/networking"; function formatUserRole(userRole: string) { if (!userRole) { @@ -42,7 +43,7 @@ const useAuthorized = () => { // Redirect after mount if missing/invalid token useEffect(() => { if (!token) { - router.replace("/sso/key/generate"); + router.replace(`${getProxyBaseUrl()}/ui/login`); } }, [token, router]); @@ -54,7 +55,7 @@ const useAuthorized = () => { } catch { // Bad token in cookie — clear and bounce clearTokenCookies(); - router.replace("/sso/key/generate"); + router.replace(`${getProxyBaseUrl()}/ui/login`); return null; } }, [token, router]);