mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix(cli): address review feedback on EXPERIMENTAL_UI_LOGIN gate and e2e test
Restore EXPERIMENTAL_UI_LOGIN=false as an explicit opt-out: operators who set it to false keep the old boundary; unset (new default) and true both attempt NaCl decryption, which fails closed for non-blob tokens. In the e2e test: replace the silent Redis fallback with pytest.skip so a missing Redis instance is explicit rather than silently degrading to a directly-minted token. Write the seeded flow back as JSON (proxy reads it via json.loads on cache fetch) instead of Python repr, and build the updated flow immutably.
This commit is contained in:
parent
36c2b029ab
commit
ef8482dcd4
2 changed files with 26 additions and 53 deletions
|
|
@ -84,6 +84,7 @@ from litellm.proxy.utils import (
|
|||
normalize_route_for_root_path,
|
||||
)
|
||||
from litellm.repositories.table_repositories import TeamMembershipRepository
|
||||
from litellm.secret_managers.main import get_secret_bool
|
||||
from litellm.types.services import ServiceTypes
|
||||
|
||||
try:
|
||||
|
|
@ -1513,10 +1514,15 @@ async def _user_api_key_auth_builder(
|
|||
valid_token = None
|
||||
|
||||
## Check UI/CLI Hash Key
|
||||
# Embedded session tokens (lite login, UI dashboard) are encrypted
|
||||
# blobs, never sk- keys. Attempt decryption only for non-sk- keys;
|
||||
# decryption fails closed for anything that isn't a genuine blob.
|
||||
if valid_token is None and not api_key.startswith("sk-"):
|
||||
# Attempt decryption for non-sk- tokens unless the operator has
|
||||
# explicitly set EXPERIMENTAL_UI_LOGIN=false to disable it.
|
||||
# Unset (None) keeps the new default of always attempting decryption;
|
||||
# decryption fails closed for anything that is not a genuine blob.
|
||||
if (
|
||||
valid_token is None
|
||||
and not api_key.startswith("sk-")
|
||||
and get_secret_bool("EXPERIMENTAL_UI_LOGIN") is not False
|
||||
):
|
||||
valid_token = ExperimentalUIJWTToken.get_key_object_from_ui_hash_key(
|
||||
api_key
|
||||
)
|
||||
|
|
|
|||
|
|
@ -357,37 +357,6 @@ async def add_team_member(
|
|||
return await response.json()
|
||||
|
||||
|
||||
def _mint_cli_sso_token(
|
||||
*,
|
||||
user_id: str,
|
||||
user_email: str,
|
||||
team_id: str,
|
||||
team_alias: str,
|
||||
models: list[str],
|
||||
) -> str:
|
||||
"""
|
||||
Mint the encrypted CLI session JWT returned by /sso/cli/poll after SSO login.
|
||||
Uses the same code path as ExperimentalUIJWTToken.get_cli_jwt_auth_token.
|
||||
"""
|
||||
from litellm.proxy import proxy_server
|
||||
from litellm.proxy._types import LiteLLM_UserTable, LitellmUserRoles
|
||||
from litellm.proxy.auth.auth_checks import ExperimentalUIJWTToken
|
||||
|
||||
proxy_server.master_key = "sk-1234"
|
||||
user_info = LiteLLM_UserTable(
|
||||
user_id=user_id,
|
||||
user_email=user_email,
|
||||
user_role=LitellmUserRoles.INTERNAL_USER.value,
|
||||
teams=[team_id],
|
||||
models=models,
|
||||
)
|
||||
return ExperimentalUIJWTToken.get_cli_jwt_auth_token(
|
||||
user_info=user_info,
|
||||
team_id=team_id,
|
||||
team_alias=team_alias,
|
||||
)
|
||||
|
||||
|
||||
async def obtain_cli_sso_token_via_poll_flow(
|
||||
session,
|
||||
*,
|
||||
|
|
@ -424,13 +393,7 @@ async def obtain_cli_sso_token_via_poll_flow(
|
|||
browser_complete_token=browser_complete_token,
|
||||
)
|
||||
if not seeded:
|
||||
return _mint_cli_sso_token(
|
||||
user_id=user_id,
|
||||
user_email=user_email,
|
||||
team_id=team_id,
|
||||
team_alias=team_alias,
|
||||
models=models,
|
||||
)
|
||||
pytest.skip("Shared Redis not available; skipping full poll-flow test")
|
||||
|
||||
async with session.post(
|
||||
f"{PROXY_BASE}/sso/cli/complete/{login_id}",
|
||||
|
|
@ -469,6 +432,7 @@ async def _seed_cli_sso_flow_in_shared_redis(
|
|||
) -> bool:
|
||||
"""Seed the CLI SSO flow in Redis when tests share the proxy's Redis instance."""
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
|
||||
try:
|
||||
|
|
@ -509,18 +473,21 @@ async def _seed_cli_sso_flow_in_shared_redis(
|
|||
if not isinstance(flow, dict):
|
||||
return False
|
||||
|
||||
flow["sso_complete"] = True
|
||||
flow["user_code_verified"] = False
|
||||
flow["session_data"] = {
|
||||
"user_id": user_id,
|
||||
"user_role": "internal_user",
|
||||
"models": models,
|
||||
"user_email": user_email,
|
||||
"teams": [team_id],
|
||||
"team_details": [{"team_id": team_id, "team_alias": team_alias}],
|
||||
updated_flow = {
|
||||
**flow,
|
||||
"sso_complete": True,
|
||||
"user_code_verified": False,
|
||||
"session_data": {
|
||||
"user_id": user_id,
|
||||
"user_role": "internal_user",
|
||||
"models": models,
|
||||
"user_email": user_email,
|
||||
"teams": [team_id],
|
||||
"team_details": [{"team_id": team_id, "team_alias": team_alias}],
|
||||
},
|
||||
"browser_complete_token_hash": _hash_cli_sso_secret(browser_complete_token),
|
||||
}
|
||||
flow["browser_complete_token_hash"] = _hash_cli_sso_secret(browser_complete_token)
|
||||
client.setex(cache_key, 600, str(flow))
|
||||
client.setex(cache_key, 600, json.dumps(updated_flow))
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue