fix(ui): resolve ERR_TOO_MANY_REDIRECTS on password reset with SERVER_ROOT_PATH

The getUiConfig() function hardcoded `/litellm/.well-known/litellm-ui-config`
which caused redirect loops when SERVER_ROOT_PATH was set (e.g., `/litellm`),
especially in read-only Docker containers where the file-rewriting fallback
silently fails. Now derives the config URL dynamically from the current page
location, making it work correctly regardless of the configured root path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Julio Quinteros Pro 2026-02-26 15:40:32 -03:00
parent 6600c86dbd
commit 05d1ee4eb9
2 changed files with 18 additions and 11 deletions

View file

@ -169,7 +169,7 @@ describe("UI config and public endpoints", () => {
};
const mockFetch = setupMockFetch([
{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/public/providers/fields", data: [] },
]);
@ -195,7 +195,7 @@ describe("UI config and public endpoints", () => {
};
const mockFetch = setupMockFetch([
{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/public/model_hub/info", data: {} },
]);
@ -218,7 +218,7 @@ describe("UI config and public endpoints", () => {
};
const mockFetch = setupMockFetch([
{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/public/model_hub", data: [] },
]);
@ -241,7 +241,7 @@ describe("UI config and public endpoints", () => {
};
const mockFetch = setupMockFetch([
{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/public/agent_hub", data: [] },
]);
@ -262,7 +262,7 @@ describe("UI config and public endpoints", () => {
};
const mockFetch = setupMockFetch([
{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/public/mcp_hub", data: [] },
]);
@ -283,7 +283,7 @@ describe("UI config and public endpoints", () => {
};
const mockFetch = setupMockFetch([
{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/.well-known/litellm-ui-config", data: uiConfig },
{ url: "/public/providers/fields", data: [] },
]);
@ -305,14 +305,14 @@ describe("UI config and public endpoints", () => {
proxy_base_url: "https://example.com",
};
const mockFetch = setupMockFetch([{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }]);
const mockFetch = setupMockFetch([{ url: "/.well-known/litellm-ui-config", data: uiConfig }]);
const result = await Networking.getUiConfig();
expect(mockFetch).toHaveBeenCalledOnce();
expect(result).toEqual(uiConfig);
const configCall = mockFetch.mock.calls.find((call) =>
(call[0] as string).includes("/litellm/.well-known/litellm-ui-config"),
(call[0] as string).includes("/.well-known/litellm-ui-config"),
);
expect(configCall).toBeDefined();
});

View file

@ -362,9 +362,16 @@ export const makeModelGroupPublic = async (accessToken: string, modelGroups: str
export const getUiConfig = async () => {
console.log("Getting UI config");
/**Special route to get the proxy base url and server root path */
const url = defaultProxyBaseUrl
? `${defaultProxyBaseUrl}/litellm/.well-known/litellm-ui-config`
: `/litellm/.well-known/litellm-ui-config`;
let url: string;
if (defaultProxyBaseUrl) {
// Local development — use the explicit dev proxy URL
url = `${defaultProxyBaseUrl}/.well-known/litellm-ui-config`;
} else {
// Production — derive the base path from the current page URL
// The UI is served at {serverRootPath}/ui/*, so we go up from /ui/ to the root
const basePath = window.location.pathname.replace(/\/ui\/.*$/, "");
url = `${basePath}/.well-known/litellm-ui-config`;
}
const response = await fetch(url);
const jsonData: LiteLLMWellKnownUiConfig = await response.json();
/**