Merge pull request #17553 from BerriAI/litellm_ui_use_auth_new_login

[Fix] Change useAuthorized Hook to redirect to new Login Page
This commit is contained in:
yuneng-jiang 2025-12-05 21:06:07 -08:00 • committed by GitHub
commit 2dd2f84b86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 5 deletions

View file

@ -39,9 +39,8 @@ test("view internal user page", async ({ page }) => {
const rowCount = await page.locator("tbody tr").count();
expect(rowCount).toBeGreaterThan(0);
const userIdHeader = page.locator("th", { hasText: "User ID" });
page.screenshot({ path: "test-results/user_id_header.png" });
await expect(userIdHeader).toBeVisible();
const userIdHeader = await page.locator("th", { hasText: "User ID" });
await expect(userIdHeader).toBeVisible({ timeout: 10000 });
// test pagination
// Wait for pagination controls to be visible

View file

@ -0,0 +1,82 @@
/* @vitest-environment jsdom */
import { renderHook } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import useAuthorized from "./useAuthorized";
const { replaceMock, clearTokenCookiesMock, getProxyBaseUrlMock } = vi.hoisted(() => ({
replaceMock: vi.fn(),
clearTokenCookiesMock: vi.fn(),
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<typeof import("@/utils/cookieUtils")>();
return {
...actual,
clearTokenCookies: clearTokenCookiesMock,
};
});
const createJwt = (payload: Record<string, unknown>) => {
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("Undefined Role");
});
});

View file

@ -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]);