1616
1717import math
1818from datetime import datetime , timezone
19- from typing import AsyncGenerator
19+ from typing import Any , AsyncGenerator
2020
2121from app .core .dependencies import get_current_user_id
2222from app .services .rate_limit .config import (
4141from shared .core .logging import log_context
4242from shared .core .state_machine .states import JobStatus
4343from shared .models .database .api_key import APIKey
44- from shared .utils .api_key_hashing import hash_api_key
4544from shared .models .database .job import Job
4645from shared .models .database .user_balance import UserBalance
46+ from shared .utils .api_key_hashing import get_api_key_lookup_hashes
4747
4848_DEFAULT_TIER : str = "free"
4949_ACTIVE_JOB_STATES : tuple [str , ...] = (
@@ -140,6 +140,21 @@ async def _resolve_apikey_cache_ttl_seconds(api_key_hash: str) -> int:
140140 return max_ttl_seconds
141141
142142
143+ async def _get_cached_apikey_identity (
144+ redis_service : Any ,
145+ api_key_hashes : list [str ],
146+ ) -> tuple [dict | None , str | None ]:
147+ """Return cached API key identity and the hash key that matched it."""
148+ for api_key_hash in api_key_hashes :
149+ cached = await identity_cache .get_cached_identity (
150+ redis_service ,
151+ identity_cache ._apikey_key (api_key_hash ),
152+ )
153+ if cached is not None :
154+ return cached , api_key_hash
155+ return None , None
156+
157+
143158# ---------------------------------------------------------------------------
144159# with_current_user -- Layer 0 (matched system limit)
145160# ---------------------------------------------------------------------------
@@ -172,11 +187,16 @@ async def with_current_user(
172187 if isinstance (user_tier , str ) and stashed_user_id == user_id :
173188 if cached_identity_hit is False :
174189 token = _extract_bearer_token (request .headers .get ("authorization" ))
175- api_key_hash = None
190+ api_key_hash = getattr ( request . state , "api_key_hash" , None )
176191 is_api_key_auth = isinstance (token , str ) and token .startswith ("sk_" )
177- if token is not None and is_api_key_auth :
178- api_key_hash = hash_api_key (token )
179- if is_api_key_auth and api_key_hash :
192+ if (
193+ is_api_key_auth
194+ and not api_key_hash
195+ and isinstance (token , str )
196+ ):
197+ api_key_hashes = get_api_key_lookup_hashes (token )
198+ api_key_hash = api_key_hashes [0 ] if api_key_hashes else None
199+ if is_api_key_auth and isinstance (api_key_hash , str ):
180200 try :
181201 ttl_seconds = await _resolve_apikey_cache_ttl_seconds (api_key_hash )
182202 await identity_cache .set_apikey_identity (
@@ -194,24 +214,35 @@ async def with_current_user(
194214 )
195215 else :
196216 token = _extract_bearer_token (request .headers .get ("authorization" ))
197- api_key_hash = None
217+ api_key_hashes : list [ str ] = []
198218 is_api_key_auth = isinstance (token , str ) and token .startswith ("sk_" )
199219 if token is not None and is_api_key_auth :
200- api_key_hash = hash_api_key (token )
220+ api_key_hashes = get_api_key_lookup_hashes (token )
201221 cache_key : str = (
202- identity_cache ._apikey_key (api_key_hash )
203- if is_api_key_auth and api_key_hash
222+ identity_cache ._apikey_key (api_key_hashes [ 0 ] )
223+ if is_api_key_auth and api_key_hashes
204224 else identity_cache ._jwt_key (user_id )
205225 )
206226 try :
207- cached : dict | None = await identity_cache .get_cached_identity (
208- redis_service , cache_key
209- )
227+ if is_api_key_auth and api_key_hashes :
228+ cached , matched_api_key_hash = await _get_cached_apikey_identity (
229+ redis_service ,
230+ api_key_hashes ,
231+ )
232+ else :
233+ cached = await identity_cache .get_cached_identity (
234+ redis_service ,
235+ cache_key ,
236+ )
237+ matched_api_key_hash = None
210238 if cached is not None :
211239 user_tier = cached .get ("user_tier" , _DEFAULT_TIER )
240+ if matched_api_key_hash :
241+ request .state .api_key_hash = matched_api_key_hash
212242 else :
213243 user_tier = await _resolve_user_tier_from_db (user_id )
214- if is_api_key_auth and api_key_hash :
244+ api_key_hash = getattr (request .state , "api_key_hash" , None )
245+ if is_api_key_auth and isinstance (api_key_hash , str ):
215246 ttl_seconds = await _resolve_apikey_cache_ttl_seconds (api_key_hash )
216247 await identity_cache .set_apikey_identity (
217248 redis_service ,
0 commit comments