diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 61c618eeb18..ffca4d533be 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -807,6 +807,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 api_key=None, user_role=LitellmUserRoles.PROXY_ADMIN, user_id=user_id, + key_alias=user_id, team_id=team_id, team_alias=( team_object.team_alias @@ -826,6 +827,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 valid_token = UserAPIKeyAuth( api_key=None, + key_alias=user_id, team_id=team_id, team_alias=( team_object.team_alias if team_object is not None else None diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index 5303da6fbcf..bd9fb517cdf 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -2029,3 +2029,184 @@ async def test_find_and_validate_specific_team_id_no_hint_for_valid_field(): error_msg = str(exc_info.value) assert "Hint" not in error_msg + + +@pytest.mark.asyncio +async def test_jwt_auth_sets_key_alias_to_user_id_admin(): + """ + Verify that JWT standard auth populates key_alias with user_id + on the admin path so Prometheus api_key_alias label is non-empty. + """ + import json + + from starlette.datastructures import URL + + import litellm + import litellm.proxy.proxy_server + from litellm.proxy.auth.user_api_key_auth import _user_api_key_auth_builder + from litellm.proxy.utils import ProxyLogging + from litellm.caching.dual_cache import DualCache + + proxy_logging_obj = ProxyLogging(user_api_key_cache=DualCache()) + + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth() + + # Wire proxy server globals + setattr(litellm.proxy.proxy_server, "premium_user", True) + setattr(litellm.proxy.proxy_server, "general_settings", {"enable_jwt_auth": True}) + setattr(litellm.proxy.proxy_server, "jwt_handler", jwt_handler) + setattr(litellm.proxy.proxy_server, "prisma_client", None) + setattr(litellm.proxy.proxy_server, "master_key", None) + setattr(litellm.proxy.proxy_server, "llm_router", None) + setattr(litellm.proxy.proxy_server, "llm_model_list", None) + setattr(litellm.proxy.proxy_server, "proxy_logging_obj", proxy_logging_obj) + setattr(litellm.proxy.proxy_server, "open_telemetry_logger", None) + setattr(litellm.proxy.proxy_server, "model_max_budget_limiter", None) + setattr(litellm.proxy.proxy_server, "litellm_proxy_admin_name", "admin") + + auth_builder_result = { + "is_proxy_admin": True, + "team_id": "team_123", + "team_object": LiteLLM_TeamTable(team_id="team_123"), + "user_id": "test_user_1", + "user_object": LiteLLM_UserTable( + user_id="test_user_1", user_role=LitellmUserRoles.PROXY_ADMIN + ), + "end_user_id": None, + "end_user_object": None, + "org_id": None, + "token": "fake_jwt_token", + "team_membership": None, + "jwt_claims": {"sub": "test_user_1"}, + } + + from fastapi import Request + + request = Request(scope={"type": "http", "headers": []}) + request._url = URL(url="/chat/completions") + + async def return_body(): + return json.dumps({"model": "gpt-4"}).encode("utf-8") + + request.body = return_body + + with patch.object( + jwt_handler, "is_jwt", return_value=True + ), patch.object( + JWTAuthManager, + "auth_builder", + new_callable=AsyncMock, + return_value=auth_builder_result, + ), patch( + "litellm.proxy.auth.user_api_key_auth.get_global_proxy_spend", + new_callable=AsyncMock, + return_value=0.0, + ): + result = await _user_api_key_auth_builder( + request=request, + api_key="Bearer fake_jwt_token", + azure_api_key_header="", + anthropic_api_key_header=None, + google_ai_studio_api_key_header=None, + azure_apim_header=None, + request_data={"model": "gpt-4"}, + ) + + assert result.key_alias == "test_user_1" + assert result.user_id == "test_user_1" + assert result.user_role == LitellmUserRoles.PROXY_ADMIN + + +@pytest.mark.asyncio +async def test_jwt_auth_sets_key_alias_to_user_id_non_admin(): + """ + Verify that JWT standard auth populates key_alias with user_id + on the non-admin path so Prometheus api_key_alias label is non-empty. + """ + import json + + from starlette.datastructures import URL + + import litellm + import litellm.proxy.proxy_server + from litellm.proxy.auth.user_api_key_auth import _user_api_key_auth_builder + from litellm.proxy.utils import ProxyLogging + from litellm.caching.dual_cache import DualCache + + proxy_logging_obj = ProxyLogging(user_api_key_cache=DualCache()) + + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth() + + # Wire proxy server globals + setattr(litellm.proxy.proxy_server, "premium_user", True) + setattr(litellm.proxy.proxy_server, "general_settings", {"enable_jwt_auth": True}) + setattr(litellm.proxy.proxy_server, "jwt_handler", jwt_handler) + setattr(litellm.proxy.proxy_server, "prisma_client", None) + setattr(litellm.proxy.proxy_server, "master_key", None) + setattr(litellm.proxy.proxy_server, "llm_router", None) + setattr(litellm.proxy.proxy_server, "llm_model_list", None) + setattr(litellm.proxy.proxy_server, "proxy_logging_obj", proxy_logging_obj) + setattr(litellm.proxy.proxy_server, "open_telemetry_logger", None) + setattr(litellm.proxy.proxy_server, "model_max_budget_limiter", None) + setattr(litellm.proxy.proxy_server, "litellm_proxy_admin_name", "admin") + + team_object = LiteLLM_TeamTable(team_id="team_123") + user_object = LiteLLM_UserTable( + user_id="test_user_1", user_role=LitellmUserRoles.INTERNAL_USER + ) + + auth_builder_result = { + "is_proxy_admin": False, + "team_id": "team_123", + "team_object": team_object, + "user_id": "test_user_1", + "user_object": user_object, + "end_user_id": None, + "end_user_object": None, + "org_id": None, + "token": "fake_jwt_token", + "team_membership": None, + "jwt_claims": {"sub": "test_user_1"}, + } + + from fastapi import Request + + request = Request(scope={"type": "http", "headers": []}) + request._url = URL(url="/chat/completions") + + async def return_body(): + return json.dumps({"model": "gpt-4"}).encode("utf-8") + + request.body = return_body + + with patch.object( + jwt_handler, "is_jwt", return_value=True + ), patch.object( + JWTAuthManager, + "auth_builder", + new_callable=AsyncMock, + return_value=auth_builder_result, + ), patch( + "litellm.proxy.auth.user_api_key_auth.get_global_proxy_spend", + new_callable=AsyncMock, + return_value=0.0, + ), patch( + "litellm.proxy.auth.user_api_key_auth.common_checks", + new_callable=AsyncMock, + return_value=True, + ): + result = await _user_api_key_auth_builder( + request=request, + api_key="Bearer fake_jwt_token", + azure_api_key_header="", + anthropic_api_key_header=None, + google_ai_studio_api_key_header=None, + azure_apim_header=None, + request_data={"model": "gpt-4"}, + ) + + assert result.key_alias == "test_user_1" + assert result.user_id == "test_user_1" + assert result.user_role == LitellmUserRoles.INTERNAL_USER