diff --git a/litellm/proxy/auth/login_utils.py b/litellm/proxy/auth/login_utils.py index 94f2f698e0..6ef983b221 100644 --- a/litellm/proxy/auth/login_utils.py +++ b/litellm/proxy/auth/login_utils.py @@ -34,6 +34,33 @@ from litellm.secret_managers.main import get_secret_bool from litellm.types.proxy.ui_sso import ReturnedUITokenObject +def get_ui_credentials(master_key: Optional[str]) -> tuple[str, str]: + """ + Get UI username and password from environment variables or master key. + + Args: + master_key: Master key for the proxy (used as fallback for password) + + Returns: + tuple[str, str]: A tuple containing (ui_username, ui_password) + + Raises: + ProxyException: If neither UI_PASSWORD nor master_key is available + """ + ui_username = os.getenv("UI_USERNAME", "admin") + ui_password = os.getenv("UI_PASSWORD", None) + if ui_password is None: + ui_password = str(master_key) if master_key is not None else None + if ui_password is None: + raise ProxyException( + message="set Proxy master key to use UI. https://docs.litellm.ai/docs/proxy/virtual_keys. If set, use `--detailed_debug` to debug issue.", + type=ProxyErrorTypes.auth_error, + param="UI_PASSWORD", + code=500, + ) + return ui_username, ui_password + + class LoginResult: """Result object containing authentication data from login.""" @@ -85,17 +112,7 @@ async def authenticate_user( code=500, ) - ui_username = os.getenv("UI_USERNAME", "admin") - ui_password = os.getenv("UI_PASSWORD", None) - if ui_password is None: - ui_password = str(master_key) if master_key is not None else None - if ui_password is None: - raise ProxyException( - message="set Proxy master key to use UI. https://docs.litellm.ai/docs/proxy/virtual_keys. If set, use `--detailed_debug` to debug issue.", - type=ProxyErrorTypes.auth_error, - param="UI_PASSWORD", - code=500, - ) + ui_username, ui_password = get_ui_credentials(master_key) # Check if we can find the `username` in the db. On the UI, users can enter username=their email _user_row: Optional[LiteLLM_UserTable] = None @@ -116,10 +133,6 @@ async def authenticate_user( ), ) - disabled_non_admin_personal_key_creation = ( - get_disabled_non_admin_personal_key_creation() - ) - """ To login to Admin UI, we support the following - Login with UI_USERNAME and UI_PASSWORD diff --git a/tests/test_litellm/proxy/auth/test_login_utils.py b/tests/test_litellm/proxy/auth/test_login_utils.py index abb7b58c29..201461dc8b 100644 --- a/tests/test_litellm/proxy/auth/test_login_utils.py +++ b/tests/test_litellm/proxy/auth/test_login_utils.py @@ -18,7 +18,43 @@ from litellm.proxy._types import ( ProxyException, hash_token, ) -from litellm.proxy.auth.login_utils import LoginResult, authenticate_user +from litellm.proxy.auth.login_utils import ( + LoginResult, + authenticate_user, + get_ui_credentials, +) + + +def test_get_ui_credentials_prefers_explicit_password(): + """The configured UI password should be returned when available.""" + with patch.dict( + os.environ, + {"UI_USERNAME": "test-admin", "UI_PASSWORD": "secure-pass"}, + clear=True, + ): + username, password = get_ui_credentials(master_key="sk-123") + + assert username == "test-admin" + assert password == "secure-pass" + + +def test_get_ui_credentials_can_use_master_key(): + """Master key should be used as password when UI_PASSWORD is missing.""" + with patch.dict(os.environ, {"UI_USERNAME": "fallback-admin"}, clear=True): + username, password = get_ui_credentials(master_key="fallback-key") + + assert username == "fallback-admin" + assert password == "fallback-key" + + +def test_get_ui_credentials_requires_password(): + """Missing UI password and master key results in error.""" + with patch.dict(os.environ, {}, clear=True): + with pytest.raises(ProxyException) as exc_info: + get_ui_credentials(master_key=None) + + assert exc_info.value.type == ProxyErrorTypes.auth_error + assert exc_info.value.code == "500" @pytest.mark.asyncio