mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix: move raise_for_status inside context manager, require user_id, LRU cache eviction
- Move response.raise_for_status() inside httpx async context manager to avoid ResponseClosed errors on streaming responses after context exit - Require explicit user_id in /connect and /status: remove api_key fallback to prevent credential sharing across users and orphaning on key rotation - Switch _byok_cred_cache from plain dict to OrderedDict with move_to_end() for true LRU eviction — frequently-accessed entries are no longer evicted early
This commit is contained in:
parent
e8e38a4bd3
commit
29682d2a67
2 changed files with 22 additions and 12 deletions
|
|
@ -202,9 +202,15 @@ async def openapi_oauth2_connect(
|
|||
detail="Database is not configured. Cannot initiate OAuth2 flow.",
|
||||
)
|
||||
|
||||
user_id = user_api_key_dict.user_id or user_api_key_dict.api_key or ""
|
||||
# Require an explicit user_id so that credentials are scoped to users, not API keys.
|
||||
# Falling back to the raw api_key would allow multiple users sharing a key to
|
||||
# silently share OAuth2 credentials and orphan them when the key is rotated.
|
||||
user_id = user_api_key_dict.user_id or ""
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=400, detail="Cannot determine user identity from token")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="user_id is required for OAuth2 flow. Ensure your API key is tied to a user.",
|
||||
)
|
||||
|
||||
_purge_expired_states()
|
||||
if len(_pending_oauth2_states) >= _STATES_MAX_SIZE:
|
||||
|
|
@ -379,7 +385,7 @@ async def openapi_oauth2_callback( # noqa: PLR0915
|
|||
headers=token_headers,
|
||||
timeout=30.0,
|
||||
)
|
||||
response.raise_for_status()
|
||||
response.raise_for_status()
|
||||
except httpx.HTTPStatusError as exc:
|
||||
verbose_proxy_logger.error(
|
||||
"openapi_oauth2_callback: token exchange HTTP error user=%s server=%s status=%s",
|
||||
|
|
@ -555,7 +561,8 @@ async def openapi_oauth2_status(
|
|||
if server is None:
|
||||
raise HTTPException(status_code=404, detail=f"MCP server '{server_id}' not found")
|
||||
|
||||
user_id = user_api_key_dict.user_id or user_api_key_dict.api_key or ""
|
||||
# Use only user_id (not api_key) to avoid sharing credentials across users.
|
||||
user_id = user_api_key_dict.user_id or ""
|
||||
server_name = server.server_name or server.name or server_id
|
||||
|
||||
if prisma_client is None or not user_id:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import json
|
|||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
Any,
|
||||
|
|
@ -67,7 +68,7 @@ from litellm.utils import Rules, client, function_setup
|
|||
# a brief 401 window (up to _BYOK_CRED_CACHE_TTL seconds) after a token expires at the
|
||||
# provider before the cache entry is evicted and a fresh DB lookup is made. Mitigating
|
||||
# this (e.g. by storing and checking `expires_in`) is a future improvement.
|
||||
_byok_cred_cache: Dict[Tuple[str, str], Tuple[Optional[str], float]] = {}
|
||||
_byok_cred_cache: OrderedDict[Tuple[str, str], Tuple[Optional[str], float]] = OrderedDict()
|
||||
_BYOK_CRED_CACHE_TTL = 60 # seconds
|
||||
_BYOK_CRED_CACHE_MAX_SIZE = 4096 # cap to prevent unbounded growth
|
||||
|
||||
|
|
@ -103,17 +104,19 @@ def _invalidate_byok_cred_cache(user_id: str, server_id: str) -> None:
|
|||
def _write_byok_cred_cache(
|
||||
user_id: str, server_id: str, credential: Optional[str]
|
||||
) -> None:
|
||||
"""Write a credential value to the cache, evicting the oldest entry if at capacity.
|
||||
"""Write a credential value to the cache with LRU eviction.
|
||||
|
||||
Evicts the oldest-inserted entry (FIFO) rather than clearing all at once to
|
||||
avoid a thundering-herd DB spike when the cache fills under load.
|
||||
Uses OrderedDict.move_to_end() so that every write (new or update) moves
|
||||
the entry to the most-recently-used position. When at capacity, the
|
||||
least-recently-used (oldest) entry at the front is evicted.
|
||||
"""
|
||||
cache_key = (user_id, server_id)
|
||||
# Only evict when the key is new — updates to existing entries don't grow the cache.
|
||||
if cache_key not in _byok_cred_cache and len(_byok_cred_cache) >= _BYOK_CRED_CACHE_MAX_SIZE:
|
||||
oldest_key = next(iter(_byok_cred_cache))
|
||||
del _byok_cred_cache[oldest_key]
|
||||
is_new = cache_key not in _byok_cred_cache
|
||||
if is_new and len(_byok_cred_cache) >= _BYOK_CRED_CACHE_MAX_SIZE:
|
||||
_byok_cred_cache.popitem(last=False) # evict LRU (front of OrderedDict)
|
||||
_byok_cred_cache[cache_key] = (credential, time.monotonic())
|
||||
if not is_new:
|
||||
_byok_cred_cache.move_to_end(cache_key) # promote to most-recently-used
|
||||
|
||||
# Check if MCP is available
|
||||
# "mcp" requires python 3.10 or higher, but several litellm users use python 3.8
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue