mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-18 04:28:19 +00:00
[Feature] UI - Settings: add forward_llm_provider_auth_headers toggle
This commit is contained in:
@@ -100,6 +100,15 @@ class UISettings(BaseModel):
|
||||
description="If enabled, forwards client headers (e.g. Authorization) to the LLM API. Required for Claude Code with Max subscription.",
|
||||
)
|
||||
|
||||
forward_llm_provider_auth_headers: bool = Field(
|
||||
default=False,
|
||||
description=(
|
||||
"If enabled, forwards LLM provider auth headers (x-api-key, "
|
||||
"x-goog-api-key, api-key, ocp-apim-subscription-key) to the upstream "
|
||||
"provider. Required for Claude Code BYOK (bring-your-own-key) flows."
|
||||
),
|
||||
)
|
||||
|
||||
enable_projects_ui: bool = Field(
|
||||
default=False,
|
||||
description="If enabled, shows the Projects feature in the UI sidebar and the project field in key management.",
|
||||
@@ -149,6 +158,7 @@ ALLOWED_UI_SETTINGS_FIELDS = {
|
||||
"enabled_ui_pages_internal_users",
|
||||
"require_auth_for_public_ai_hub",
|
||||
"forward_client_headers_to_llm_api",
|
||||
"forward_llm_provider_auth_headers",
|
||||
"enable_projects_ui",
|
||||
"disable_agents_for_internal_users",
|
||||
"allow_agents_for_team_admins",
|
||||
@@ -162,6 +172,7 @@ ALLOWED_UI_SETTINGS_FIELDS = {
|
||||
# general_settings at runtime (on both read and write).
|
||||
_RUNTIME_GENERAL_SETTINGS_FLAGS = [
|
||||
"forward_client_headers_to_llm_api",
|
||||
"forward_llm_provider_auth_headers",
|
||||
"disable_agents_for_internal_users",
|
||||
"allow_agents_for_team_admins",
|
||||
"disable_vector_stores_for_internal_users",
|
||||
|
||||
@@ -1016,6 +1016,83 @@ class TestProxySettingEndpoints:
|
||||
assert "unsupported_flag" not in stored_settings
|
||||
assert stored_settings["disable_model_add_for_internal_users"] is False
|
||||
|
||||
def test_update_ui_settings_persists_forward_llm_provider_auth_headers(
|
||||
self, mock_auth, monkeypatch
|
||||
):
|
||||
"""BYOK flag must be allowlisted and persisted to litellm_uisettings."""
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
|
||||
mock_user_auth = UserAPIKeyAuth(
|
||||
user_id="test-user-123",
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN,
|
||||
)
|
||||
app.dependency_overrides[user_api_key_auth] = lambda: mock_user_auth
|
||||
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
|
||||
mock_prisma = MagicMock()
|
||||
mock_prisma.db.litellm_uisettings.upsert = AsyncMock()
|
||||
mock_prisma.db.litellm_uisettings.find_unique = AsyncMock(return_value=None)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
|
||||
payload = {"forward_llm_provider_auth_headers": True}
|
||||
|
||||
try:
|
||||
response = client.patch("/update/ui_settings", json=payload)
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "success"
|
||||
assert data["settings"]["forward_llm_provider_auth_headers"] is True
|
||||
|
||||
assert mock_prisma.db.litellm_uisettings.upsert.called
|
||||
call_args = mock_prisma.db.litellm_uisettings.upsert.call_args
|
||||
create_data = call_args.kwargs["data"]["create"]
|
||||
stored_settings = json.loads(create_data["ui_settings"])
|
||||
assert stored_settings["forward_llm_provider_auth_headers"] is True
|
||||
|
||||
def test_update_ui_settings_syncs_forward_llm_provider_auth_headers_to_general_settings(
|
||||
self, mock_auth, monkeypatch
|
||||
):
|
||||
"""BYOK flag must be synced into general_settings dict so the request path sees it."""
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
|
||||
mock_user_auth = UserAPIKeyAuth(
|
||||
user_id="test-user-123",
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN,
|
||||
)
|
||||
app.dependency_overrides[user_api_key_auth] = lambda: mock_user_auth
|
||||
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
|
||||
|
||||
# Reset general_settings so the test is hermetic
|
||||
general_settings: dict = {}
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.proxy_server.general_settings", general_settings
|
||||
)
|
||||
|
||||
mock_prisma = MagicMock()
|
||||
mock_prisma.db.litellm_uisettings.upsert = AsyncMock()
|
||||
mock_prisma.db.litellm_uisettings.find_unique = AsyncMock(return_value=None)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
|
||||
payload = {"forward_llm_provider_auth_headers": True}
|
||||
|
||||
try:
|
||||
response = client.patch("/update/ui_settings", json=payload)
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert general_settings.get("forward_llm_provider_auth_headers") is True
|
||||
|
||||
def test_get_sso_settings_from_database(self, mock_proxy_config, mock_auth, monkeypatch):
|
||||
"""Test getting SSO settings from the dedicated database table"""
|
||||
import json
|
||||
|
||||
Reference in New Issue
Block a user