Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions litellm/proxy/auth/user_api_key_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -1191,13 +1191,15 @@ async def _user_api_key_auth_builder(
return await handle_oauth2_proxy_request(request=request)

if general_settings.get("enable_jwt_auth", False) is True:
from litellm.proxy.proxy_server import premium_user

if premium_user is not True:
raise ValueError(f"JWT Auth is an enterprise only feature. {CommonProxyErrors.not_premium_user.value}")
is_jwt = jwt_handler.is_jwt(token=api_key)
verbose_proxy_logger.debug("is_jwt: %s", is_jwt)
if is_jwt:
from litellm.proxy.proxy_server import premium_user

if premium_user is not True:
raise ValueError(
f"JWT Auth is an enterprise only feature. {CommonProxyErrors.not_premium_user.value}"
)
# Try JWT-to-Virtual-Key mapping first to avoid
# unnecessary DB queries in auth_builder
do_standard_jwt_auth = True
Expand Down
74 changes: 74 additions & 0 deletions tests/test_litellm/proxy/auth/test_user_api_key_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3992,6 +3992,80 @@ async def test_non_admin_cli_session_token_reaches_production_auth_path(monkeypa
assert result.is_session_token is True


@pytest.mark.asyncio
async def test_cli_session_token_authenticates_when_jwt_auth_enabled_without_license(monkeypatch):
"""A lite login token is an encrypted (non-JWT) session blob. With
enable_jwt_auth on and no enterprise license (premium_user False), the JWT
premium gate used to fire for every request before the token was decoded, so
the CLI token 401'd with 'JWT Auth is an enterprise only feature' and was
never decrypted. The gate must apply only to actual JWTs; a non-JWT session
token has to keep authenticating on its own path regardless of license."""
monkeypatch.delenv("EXPERIMENTAL_UI_LOGIN", raising=False)
cli_token = _mint_cli_session_token(monkeypatch)

jwt_handler = MagicMock()
jwt_handler.is_jwt = JWTHandler.is_jwt
jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth()

mock_request = MagicMock()
mock_request.url.path = "/v1/messages"
mock_request.method = "POST"
mock_request.headers = {"authorization": f"Bearer {cli_token}"}
mock_request.query_params = {}

with (
patch("litellm.proxy.proxy_server.general_settings", {"enable_jwt_auth": True}),
patch("litellm.proxy.proxy_server.premium_user", False),
patch("litellm.proxy.proxy_server.jwt_handler", jwt_handler),
patch("litellm.proxy.proxy_server.master_key", "sk-master"),
patch("litellm.proxy.proxy_server.prisma_client", None),
):
result = await user_api_key_auth(
request=mock_request,
api_key=f"Bearer {cli_token}",
)

assert result.user_id == "cli-admin"
assert result.team_id == "cli-team"
assert result.token is not None and result.token.startswith("cli-session-")


@pytest.mark.asyncio
async def test_real_jwt_still_requires_license_when_jwt_auth_enabled(monkeypatch):
"""Guard for the reorder above: the enterprise gate must still reject an
actual JWT when there is no license. Moving the premium check inside the
is_jwt branch must not open JWT auth to non-premium deployments."""
monkeypatch.delenv("EXPERIMENTAL_UI_LOGIN", raising=False)
monkeypatch.setenv("LITELLM_SALT_KEY", "sk-salt-cli-test")

jwt_token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSJ9.sig"
jwt_handler = MagicMock()
jwt_handler.is_jwt = JWTHandler.is_jwt
jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth()

mock_request = MagicMock()
mock_request.url.path = "/v1/messages"
mock_request.method = "POST"
mock_request.headers = {"authorization": f"Bearer {jwt_token}"}
mock_request.query_params = {}

with (
patch("litellm.proxy.proxy_server.general_settings", {"enable_jwt_auth": True}),
patch("litellm.proxy.proxy_server.premium_user", False),
patch("litellm.proxy.proxy_server.jwt_handler", jwt_handler),
patch("litellm.proxy.proxy_server.master_key", "sk-master"),
patch("litellm.proxy.proxy_server.prisma_client", None),
):
with pytest.raises(Exception) as exc_info:
await user_api_key_auth(
request=mock_request,
api_key=f"Bearer {jwt_token}",
)

message = str(getattr(exc_info.value, "message", exc_info.value))
assert "enterprise only feature" in message


@pytest.mark.asyncio
async def test_auth_path_caches_team_object_under_canonical_team_id_key():
"""Regression for LIT-4000: the auth builder must cache the team object under
Expand Down
Loading