fix(ui): clear auth context on session invalidation; drop dead showSSOBanner

Greptile flagged that the admin_ui_disabled path cleared the token cookie
but left AuthContext state populated until a hard reload. The context now
exposes clearAuth(), which useAuthorized calls instead of reaching for
clearTokenCookies() directly, and the decoded JWT fields are derived from
the token with useMemo instead of living in their own useState slots, so
nulling the token clears them for free. The userRole/userEmail reset runs
in the token-sync effect rather than clearAuth itself so a stale sync
queued in the same commit cannot resurrect them

showSSOBanner was computed and passed through but rendered nowhere; the
banner UI it fed was deleted long ago. Removed from the context, the hook,
and the typed test mocks instead of restoring the login_method guard
Greptile suggested, since polishing dead code is the wrong fix

The admin_ui_disabled test now asserts the decoded fields are cleared
after the redirect; it previously pinned the stale values, so it fails
without clearAuth
This commit is contained in:
ryan-crabbe-berri 2026-06-09 14:50:01 -07:00
parent 3ece94bb7a
commit 24ae260c74
32 changed files with 44 additions and 162 deletions

View file

@ -81,7 +81,6 @@ describe("useAgents", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -144,7 +143,6 @@ describe("useAgents", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAgents(), { wrapper });
@ -168,7 +166,6 @@ describe("useAgents", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAgents(), { wrapper });
@ -192,7 +189,6 @@ describe("useAgents", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAgents(), { wrapper });
@ -216,7 +212,6 @@ describe("useAgents", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAgents(), { wrapper });
@ -240,7 +235,6 @@ describe("useAgents", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAgents(), { wrapper });
@ -283,7 +277,6 @@ describe("useAgents", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAgents(), { wrapper });

View file

@ -66,7 +66,6 @@ describe("useCredentials", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -129,7 +128,6 @@ describe("useCredentials", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCredentials(), { wrapper });

View file

@ -83,7 +83,6 @@ describe("useCustomers", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -146,7 +145,6 @@ describe("useCustomers", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCustomers(), { wrapper });
@ -170,7 +168,6 @@ describe("useCustomers", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCustomers(), { wrapper });
@ -194,7 +191,6 @@ describe("useCustomers", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCustomers(), { wrapper });
@ -218,7 +214,6 @@ describe("useCustomers", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCustomers(), { wrapper });
@ -242,7 +237,6 @@ describe("useCustomers", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCustomers(), { wrapper });
@ -285,7 +279,6 @@ describe("useCustomers", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCustomers(), { wrapper });

View file

@ -51,7 +51,6 @@ describe("useGuardrails", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -114,7 +113,6 @@ describe("useGuardrails", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useGuardrails(), { wrapper });
@ -138,7 +136,6 @@ describe("useGuardrails", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useGuardrails(), { wrapper });
@ -162,7 +159,6 @@ describe("useGuardrails", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useGuardrails(), { wrapper });
@ -186,7 +182,6 @@ describe("useGuardrails", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useGuardrails(), { wrapper });

View file

@ -182,7 +182,6 @@ describe("useKeys", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
// Reset fetch mock
@ -273,7 +272,6 @@ describe("useKeys", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useKeys(1, 10), { wrapper });
@ -485,7 +483,6 @@ describe("useDeletedKeys", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
// Reset fetch mock
@ -596,7 +593,6 @@ describe("useDeletedKeys", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useDeletedKeys(1, 10), { wrapper });

View file

@ -67,7 +67,6 @@ describe("useModelsInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -164,7 +163,6 @@ describe("useModelsInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useModelsInfo(), { wrapper });
@ -184,7 +182,6 @@ describe("useModelsInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useModelsInfo(), { wrapper });
@ -204,7 +201,6 @@ describe("useModelsInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useModelsInfo(), { wrapper });
@ -224,7 +220,6 @@ describe("useModelsInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useModelsInfo(), { wrapper });
@ -258,7 +253,6 @@ describe("useModelHub", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -322,7 +316,6 @@ describe("useModelHub", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useModelHub(), { wrapper });
@ -356,7 +349,6 @@ describe("useAllProxyModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -428,7 +420,6 @@ describe("useAllProxyModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAllProxyModels(), { wrapper });
@ -448,7 +439,6 @@ describe("useAllProxyModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAllProxyModels(), { wrapper });
@ -468,7 +458,6 @@ describe("useAllProxyModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useAllProxyModels(), { wrapper });
@ -502,7 +491,6 @@ describe("useSelectedTeamModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -574,7 +562,6 @@ describe("useSelectedTeamModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useSelectedTeamModels("team-1"), { wrapper });
@ -594,7 +581,6 @@ describe("useSelectedTeamModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useSelectedTeamModels("team-1"), { wrapper });
@ -614,7 +600,6 @@ describe("useSelectedTeamModels", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useSelectedTeamModels("team-1"), { wrapper });
@ -673,7 +658,6 @@ describe("useInfiniteModelInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -802,7 +786,6 @@ describe("useInfiniteModelInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useInfiniteModelInfo(), { wrapper });
@ -822,7 +805,6 @@ describe("useInfiniteModelInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useInfiniteModelInfo(), { wrapper });
@ -842,7 +824,6 @@ describe("useInfiniteModelInfo", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useInfiniteModelInfo(), { wrapper });

View file

@ -82,7 +82,6 @@ describe("useOrganizations", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -145,7 +144,6 @@ describe("useOrganizations", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useOrganizations(), { wrapper });
@ -169,7 +167,6 @@ describe("useOrganizations", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useOrganizations(), { wrapper });
@ -193,7 +190,6 @@ describe("useOrganizations", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useOrganizations(), { wrapper });
@ -217,7 +213,6 @@ describe("useOrganizations", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useOrganizations(), { wrapper });

View file

@ -130,7 +130,6 @@ describe("useProxyConfig", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
fetchSpy = vi.fn();
@ -218,7 +217,6 @@ describe("useProxyConfig", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper });
@ -301,7 +299,6 @@ describe("useDeleteProxyConfigField", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
fetchSpy = vi.fn();
@ -388,7 +385,6 @@ describe("useDeleteProxyConfigField", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useDeleteProxyConfigField(), { wrapper });

View file

@ -77,7 +77,6 @@ describe("useRouterFields", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -159,7 +158,6 @@ describe("useRouterFields", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useRouterFields(), { wrapper });
@ -182,7 +180,6 @@ describe("useRouterFields", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useRouterFields(), { wrapper });
@ -205,7 +202,6 @@ describe("useRouterFields", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useRouterFields(), { wrapper });

View file

@ -44,7 +44,6 @@ describe("useEditSSOSettings", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -113,7 +112,6 @@ describe("useEditSSOSettings", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useEditSSOSettings(), { wrapper });

View file

@ -79,7 +79,6 @@ describe("useSSOSettings", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -143,7 +142,6 @@ describe("useSSOSettings", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useSSOSettings(), { wrapper });
@ -164,7 +162,6 @@ describe("useSSOSettings", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useSSOSettings(), { wrapper });
@ -185,7 +182,6 @@ describe("useSSOSettings", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useSSOSettings(), { wrapper });
@ -206,7 +202,6 @@ describe("useSSOSettings", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useSSOSettings(), { wrapper });

View file

@ -93,7 +93,6 @@ describe("useStoreModelInDB", () => {
userEmail: null,
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
} as any);
const { result } = renderHook(() => useStoreModelInDB(), { wrapper });

View file

@ -83,7 +83,6 @@ describe("useTags", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -146,7 +145,6 @@ describe("useTags", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTags(), { wrapper });
@ -170,7 +168,6 @@ describe("useTags", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTags(), { wrapper });
@ -194,7 +191,6 @@ describe("useTags", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTags(), { wrapper });
@ -218,7 +214,6 @@ describe("useTags", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTags(), { wrapper });

View file

@ -79,7 +79,6 @@ describe("useTeams", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -150,7 +149,6 @@ describe("useTeams", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTeams(), { wrapper });
@ -174,7 +172,6 @@ describe("useTeams", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTeams(), { wrapper });
@ -250,7 +247,6 @@ describe("useTeams", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTeams(), { wrapper });
@ -276,7 +272,6 @@ describe("useTeams", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTeams(), { wrapper });
@ -312,7 +307,6 @@ describe("useTeam", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -376,7 +370,6 @@ describe("useTeam", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useTeam("team-1"), { wrapper });
@ -433,7 +426,6 @@ describe("useTeam", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
// Import useQueryClient to get access to query client
@ -465,7 +457,6 @@ describe("useTeam", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const testQueryFnMissingTeamId = async () => {
@ -651,7 +642,6 @@ describe("useDeletedTeams", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
global.fetch = vi.fn();
@ -719,7 +709,6 @@ describe("useDeletedTeams", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useDeletedTeams(1, 10, {}), { wrapper });

View file

@ -126,7 +126,6 @@ describe("useAuthorized", () => {
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();
expect(clearTokenCookiesMock).not.toHaveBeenCalled();
});
@ -163,9 +162,15 @@ describe("useAuthorized", () => {
expect(clearTokenCookiesMock).toHaveBeenCalled();
expect(result.current.isAuthorized).toBe(false);
expect(result.current.token).toBeNull();
expect(result.current.accessToken).toBe("api-key-123");
expect(result.current.userId).toBe("user-1");
expect(result.current.userEmail).toBe("user@example.com");
// clearAuth must reset the context, not just the cookie, so no consumer
// can keep acting on the revoked session until a hard reload.
await waitFor(() => {
expect(result.current.accessToken).toBeNull();
});
expect(result.current.userId).toBeNull();
expect(result.current.userEmail).toBeNull();
expect(result.current.userRole).toBe("Undefined Role");
});
it("should redirect when token is missing", async () => {

View file

@ -1,7 +1,6 @@
"use client";
import { getProxyBaseUrl } from "@/components/networking";
import { clearTokenCookies } from "@/utils/cookieUtils";
import { buildLoginUrlWithReturn, storeReturnUrl } from "@/utils/returnUrlUtils";
import { useRouter } from "next/navigation";
import { useCallback, useEffect } from "react";
@ -30,7 +29,7 @@ const useAuthorized = () => {
accessToken,
premiumUser,
disabledPersonalKeyCreation,
showSSOBanner,
clearAuth,
} = useAuth();
const isLoading = authLoading || isUIConfigLoading;
@ -47,11 +46,11 @@ const useAuthorized = () => {
if (!isAuthorized) {
if (token) {
clearTokenCookies();
clearAuth();
}
redirectToLogin();
}
}, [isLoading, isAuthorized, token, redirectToLogin]);
}, [isLoading, isAuthorized, token, clearAuth, redirectToLogin]);
return {
isLoading,
@ -63,7 +62,6 @@ const useAuthorized = () => {
userRole,
premiumUser: premiumUser as LegacyDecodedField,
disabledPersonalKeyCreation: disabledPersonalKeyCreation as LegacyDecodedField,
showSSOBanner,
};
};

View file

@ -70,7 +70,6 @@ describe("useCurrentUser", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});
@ -134,7 +133,6 @@ describe("useCurrentUser", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCurrentUser(), { wrapper });
@ -158,7 +156,6 @@ describe("useCurrentUser", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCurrentUser(), { wrapper });
@ -182,7 +179,6 @@ describe("useCurrentUser", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
const { result } = renderHook(() => useCurrentUser(), { wrapper });

View file

@ -33,7 +33,6 @@ const DEFAULT_AUTH = {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
};
const buildUserListResponse = (page: number, totalPages: number, userCount = 2): UserListResponse => ({

View file

@ -119,7 +119,6 @@ describe("AllModelsTab", () => {
userRole: "Admin",
premiumUser: true,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
};
beforeEach(() => {

View file

@ -42,7 +42,6 @@ describe("TopKeyView", () => {
userRole: "admin",
premiumUser: true,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
};
const mockSetTopKeysLimit = vi.fn();

View file

@ -517,7 +517,6 @@ describe("UsagePage", () => {
userRole: "Admin",
premiumUser: true,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
mockUseCurrentUser.mockReturnValue({
data: {
@ -651,7 +650,6 @@ describe("UsagePage", () => {
userRole: "internal_user",
premiumUser: true,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
renderWithProviders(<UsagePage {...defaultProps} />);
@ -839,7 +837,6 @@ describe("UsagePage", () => {
userRole: "Internal User",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
renderWithProviders(<UsagePage {...defaultProps} />);
@ -865,7 +862,6 @@ describe("UsagePage", () => {
userRole: "Internal User",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
renderWithProviders(<UsagePage {...defaultProps} />);

View file

@ -113,7 +113,6 @@ const mockAuthorizedUser = (userRole: string, userId: string, premiumUser: boole
userRole,
premiumUser,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
const testTeam: Team = {

View file

@ -12,7 +12,6 @@ describe("CacheFieldGroup", () => {
userRole: "Admin",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
}),
}));

View file

@ -12,7 +12,6 @@ vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
userRole: "Admin",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
}),
}));

View file

@ -23,7 +23,6 @@ const { mockUseAuthorized, mockUseOrganizations } = vi.hoisted(() => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
}));
const mockUseOrganizations = vi.fn(() => ({
@ -148,7 +147,6 @@ describe("Sidebar (leftnav)", () => {
userEmail: "viewer@example.com",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
};
it("hides Playground from Admin Viewer (cost-incurring action)", () => {
@ -192,7 +190,6 @@ describe("Sidebar (leftnav)", () => {
userEmail: "orgadmin@example.com",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
mockUseOrganizations.mockReturnValueOnce({

View file

@ -107,7 +107,6 @@ describe("TeamMembersComponent", () => {
userEmail: "test@example.com",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
});
});

View file

@ -322,7 +322,6 @@ const renderView = (premiumUser: boolean) => {
token: "token_123",
userEmail: "test@example.com",
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
return render(
@ -353,7 +352,6 @@ describe("KeyInfoView handleKeyUpdate guardrails guard", () => {
token: "token_123",
userEmail: "test@example.com",
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
render(

View file

@ -102,7 +102,6 @@ const baseAuthorized = {
token: "test-token",
userEmail: null,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
};
describe("KeyInfoView overview budget display (LIT-2845)", () => {

View file

@ -143,7 +143,6 @@ describe("KeyInfoView", () => {
token: "test-token",
userEmail: null,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
};
it("should render tags", async () => {

View file

@ -1,19 +1,12 @@
"use client";
import React, { createContext, useContext, useEffect, useState } from "react";
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { jwtDecode } from "jwt-decode";
import { clearTokenCookies, getCookie } from "@/utils/cookieUtils";
import { isJwtExpired } from "@/utils/jwtUtils";
import { formatUserRole } from "@/utils/roles";
import { getUiConfig, setGlobalLitellmHeaderName } from "@/components/networking";
function deleteCookie(name: string, path = "/") {
document.cookie = `${name}=; Max-Age=0; Path=${path}`;
if (name === "token") {
clearTokenCookies();
}
}
type AuthContextValue = {
authLoading: boolean;
token: string | null;
@ -23,8 +16,8 @@ type AuthContextValue = {
accessToken: string | null;
premiumUser: boolean;
disabledPersonalKeyCreation: boolean;
showSSOBanner: boolean;
clearAuth: () => void;
setUserRole: React.Dispatch<React.SetStateAction<string>>;
setUserEmail: React.Dispatch<React.SetStateAction<string | null>>;
};
@ -34,13 +27,22 @@ const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [authLoading, setAuthLoading] = useState(true);
const [token, setToken] = useState<string | null>(null);
const [userID, setUserID] = useState<string | null>(null);
const [userRole, setUserRole] = useState(formatUserRole(""));
const [userEmail, setUserEmail] = useState<string | null>(null);
const [accessToken, setAccessToken] = useState<string | null>(null);
const [premiumUser, setPremiumUser] = useState(false);
const [disabledPersonalKeyCreation, setDisabledPersonalKeyCreation] = useState(false);
const [showSSOBanner, setShowSSOBanner] = useState(false);
const decoded = useMemo<{ [k: string]: any } | null>(() => {
if (!token || isJwtExpired(token)) return null;
try {
return jwtDecode(token);
} catch {
return null;
}
}, [token]);
const clearAuth = useCallback(() => {
clearTokenCookies();
setToken(null);
}, []);
// Load runtime UI config (populates proxyBaseUrl etc.) before clearing
// authLoading, so any consumer that builds proxy-rooted URLs from authLoading=false
@ -63,7 +65,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
// Clear expired/invalid token so downstream code doesn't keep trying to use it.
if (raw && !valid) {
deleteCookie("token", "/");
clearTokenCookies();
}
setToken(valid);
@ -75,60 +77,42 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
};
}, []);
// Decode JWT and populate derived auth state whenever the token changes.
// Side effects of a token change: drop tokens that fail to decode, sync the
// mutable user fields, and apply any custom auth header. The reset branch runs
// after any commit that nulls the token, so user fields cannot survive clearAuth
// even if a stale sync was queued in the same commit.
useEffect(() => {
if (!token) {
if (token && !decoded) {
clearAuth();
return;
}
if (isJwtExpired(token)) {
deleteCookie("token", "/");
setToken(null);
if (!decoded) {
setUserRole(formatUserRole(""));
setUserEmail(null);
return;
}
let decoded: { [k: string]: any } | null = null;
try {
decoded = jwtDecode(token);
} catch {
deleteCookie("token", "/");
setToken(null);
return;
}
if (!decoded) return;
setAccessToken(decoded.key);
setDisabledPersonalKeyCreation(decoded.disabled_non_admin_personal_key_creation);
setShowSSOBanner(decoded.login_method === "username_password");
if (decoded.user_role) {
setUserRole(formatUserRole(decoded.user_role));
}
if (decoded.user_email) {
setUserEmail(decoded.user_email);
}
if (decoded.premium_user) {
setPremiumUser(decoded.premium_user);
}
if (decoded.auth_header_name) {
setGlobalLitellmHeaderName(decoded.auth_header_name);
}
if (decoded.user_id) {
setUserID(decoded.user_id);
}
}, [token]);
}, [token, decoded, clearAuth]);
const value: AuthContextValue = {
authLoading,
token,
userID,
userID: decoded?.user_id ?? null,
userRole,
userEmail,
accessToken,
premiumUser,
disabledPersonalKeyCreation,
showSSOBanner,
accessToken: decoded?.key ?? null,
premiumUser: !!decoded?.premium_user,
disabledPersonalKeyCreation: !!decoded?.disabled_non_admin_personal_key_creation,
clearAuth,
setUserRole,
setUserEmail,
};

View file

@ -147,7 +147,6 @@ vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
userRole: "Admin",
premiumUser: false,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
}),
}));

View file

@ -72,7 +72,6 @@ describe("TopKeyView", () => {
userRole: mockProps.userRole,
premiumUser: mockProps.premiumUser,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
});
});