fix(ui): send onboarding claim with configured litellm_key_header_name

This commit is contained in:
Devin AI 2026-07-25 23:06:37 +00:00
parent 4edf8f1551
commit 71982f63dc
2 changed files with 76 additions and 6 deletions

View file

@ -5,17 +5,14 @@ import { OnboardingForm } from "./OnboardingForm";
const mockUseOnboardingCredentials = vi.fn();
const mockClaimToken = vi.fn();
const mockJwtDecode = vi.fn();
vi.mock("next/navigation", () => ({
useSearchParams: () => new URLSearchParams("invitation_id=inv-123"),
}));
vi.mock("jwt-decode", () => ({
jwtDecode: vi.fn(() => ({
user_email: "alice@example.com",
user_id: "user-1",
key: "access-tok",
})),
jwtDecode: (...args: unknown[]) => mockJwtDecode(...args),
}));
vi.mock("@/app/(dashboard)/hooks/onboarding/useOnboarding", () => ({
@ -23,8 +20,14 @@ vi.mock("@/app/(dashboard)/hooks/onboarding/useOnboarding", () => ({
useClaimOnboardingToken: () => ({ mutate: mockClaimToken, isPending: false }),
}));
let currentAuthHeaderName = "Authorization";
vi.mock("@/components/networking", () => ({
getProxyBaseUrl: vi.fn(() => ""),
setGlobalLitellmHeaderName: (headerName: string) => {
currentAuthHeaderName = headerName;
},
getGlobalLitellmHeaderName: () => currentAuthHeaderName,
}));
vi.mock("./OnboardingLoadingView", () => ({
@ -60,6 +63,12 @@ vi.mock("./OnboardingFormBody", () => ({
describe("OnboardingForm", () => {
beforeEach(() => {
vi.clearAllMocks();
currentAuthHeaderName = "Authorization";
mockJwtDecode.mockReturnValue({
user_email: "alice@example.com",
user_id: "user-1",
key: "access-tok",
});
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/ui";
sessionStorage.clear();
@ -166,6 +175,62 @@ describe("OnboardingForm", () => {
cookieSpy.mockRestore();
});
it("should send the claim request with the proxy's custom auth header name", async () => {
mockJwtDecode.mockReturnValue({
user_email: "alice@example.com",
user_id: "user-1",
key: "access-tok",
auth_header_name: "x-litellm-api-key",
});
mockUseOnboardingCredentials.mockReturnValue({
data: { token: "fake-jwt-token" },
isLoading: false,
isError: false,
});
// The onboarding page has no session yet, so the only source for a custom
// `litellm_key_header_name` is the onboarding token's own claims; without
// propagating it the claim lands in `Authorization` and the proxy answers
// "Missing onboarding session for invitation link."
const { getGlobalLitellmHeaderName } = await import("@/components/networking");
const headerNameAtClaimTime: string[] = [];
mockClaimToken.mockImplementation((_params, options) => {
headerNameAtClaimTime.push(getGlobalLitellmHeaderName());
options.onSuccess({ token: "NEW_USER_TOKEN" });
});
render(<OnboardingForm variant="signup" />);
await act(async () => {
fireEvent.click(screen.getByRole("button", { name: "Submit" }));
});
expect(headerNameAtClaimTime).toEqual(["x-litellm-api-key"]);
});
it("should keep the default auth header name when the onboarding token has no custom header claim", async () => {
mockUseOnboardingCredentials.mockReturnValue({
data: { token: "fake-jwt-token" },
isLoading: false,
isError: false,
});
const { getGlobalLitellmHeaderName } = await import("@/components/networking");
const headerNameAtClaimTime: string[] = [];
mockClaimToken.mockImplementation((_params, options) => {
headerNameAtClaimTime.push(getGlobalLitellmHeaderName());
options.onSuccess({ token: "NEW_USER_TOKEN" });
});
render(<OnboardingForm variant="signup" />);
await act(async () => {
fireEvent.click(screen.getByRole("button", { name: "Submit" }));
});
expect(headerNameAtClaimTime).toEqual(["Authorization"]);
});
it("should show claim error when claim response is missing final token", async () => {
mockUseOnboardingCredentials.mockReturnValue({
data: { token: "fake-jwt-token" },

View file

@ -3,7 +3,7 @@ import React from "react";
import { useSearchParams } from "next/navigation";
import { jwtDecode } from "jwt-decode";
import { useOnboardingCredentials, useClaimOnboardingToken } from "@/app/(dashboard)/hooks/onboarding/useOnboarding";
import { getProxyBaseUrl } from "@/components/networking";
import { getProxyBaseUrl, setGlobalLitellmHeaderName } from "@/components/networking";
import { clearTokenCookies, storeLoginToken } from "@/utils/cookieUtils";
import { OnboardingLoadingView } from "./OnboardingLoadingView";
import { OnboardingErrorView } from "./OnboardingErrorView";
@ -30,12 +30,17 @@ export function OnboardingForm({ variant }: OnboardingFormProps) {
const userEmail: string = decoded?.user_email ?? "";
const userId: string | null = decoded?.user_id ?? null;
const accessToken: string | null = decoded?.key ?? null;
const authHeaderName: string | null = decoded?.auth_header_name ?? null;
const handleSubmit = (formValues: { password: string }) => {
if (!accessToken || !userId || !inviteId) return;
setClaimError(null);
if (authHeaderName) {
setGlobalLitellmHeaderName(authHeaderName);
}
claimToken(
{ accessToken, inviteId, userId, password: formValues.password },
{