44import logging
55import secrets
66import string
7+ import time
8+ from collections import OrderedDict
79from datetime import datetime
10+ from threading import RLock
811from typing import List , Optional , Dict , Any
912
1013from src .config import settings
1821# In-memory fallback
1922_in_memory_api_keys : Dict [str , Dict [str , Any ]] = {}
2023
24+ VALIDATION_CACHE_TTL_SECONDS = 120
25+ VALIDATION_CACHE_MAX_SIZE = 2048
26+ _validation_cache : OrderedDict [str , tuple [float , Dict [str , Any ]]] = OrderedDict ()
27+ _validation_cache_lock = RLock ()
28+
2129
2230class APIKeyStore :
2331 """MongoDB-backed storage for API key management with in-memory fallback."""
@@ -79,6 +87,41 @@ def _hash_key(self, key: str) -> str:
7987 """Create SHA-256 hash of the API key."""
8088 return hashlib .sha256 (key .encode ()).hexdigest ()
8189
90+ def _clear_validation_cache (self ) -> None :
91+ """Clear cached API key validation results."""
92+ with _validation_cache_lock :
93+ _validation_cache .clear ()
94+
95+ def _get_cached_validation (self , key_hash : str ) -> Optional [Dict [str , Any ]]:
96+ """Return a cached validation result when it is still fresh."""
97+ with _validation_cache_lock :
98+ cached = _validation_cache .get (key_hash )
99+ if not cached :
100+ return None
101+
102+ expires_at , key_doc = cached
103+ if expires_at <= time .monotonic ():
104+ _validation_cache .pop (key_hash , None )
105+ return None
106+
107+ result = dict (key_doc )
108+
109+ result ["last_used" ] = datetime .utcnow ()
110+ return result
111+
112+ def _cache_validation (self , key_hash : str , key_doc : Dict [str , Any ]) -> None :
113+ """Cache a sanitized active API key document."""
114+ with _validation_cache_lock :
115+ if key_hash in _validation_cache :
116+ _validation_cache .pop (key_hash , None )
117+ while len (_validation_cache ) >= VALIDATION_CACHE_MAX_SIZE :
118+ _validation_cache .popitem (last = False )
119+
120+ _validation_cache [key_hash ] = (
121+ time .monotonic () + VALIDATION_CACHE_TTL_SECONDS ,
122+ dict (key_doc ),
123+ )
124+
82125 def create_api_key (
83126 self ,
84127 user_id : str ,
@@ -174,6 +217,10 @@ def validate_api_key(self, key: str) -> Optional[Dict[str, Any]]:
174217 return result
175218 return None
176219
220+ cached_doc = self ._get_cached_validation (key_hash )
221+ if cached_doc :
222+ return cached_doc
223+
177224 try :
178225 key_doc = self .api_keys .find_one ({
179226 "key_hash" : key_hash ,
@@ -185,10 +232,15 @@ def validate_api_key(self, key: str) -> Optional[Dict[str, Any]]:
185232 {"_id" : key_doc ["_id" ]},
186233 {"$set" : {"last_used" : now }}
187234 )
188- key_doc ["last_used" ] = now
189- key_doc ["id" ] = str (key_doc .pop ("_id" ))
235+ key_doc = {
236+ ** key_doc ,
237+ "id" : str (key_doc ["_id" ]),
238+ "last_used" : now ,
239+ }
240+ key_doc .pop ("_id" , None )
190241 key_doc .pop ("key_hash" , None )
191- return key_doc
242+ self ._cache_validation (key_hash , key_doc )
243+ return dict (key_doc ) if key_doc else None
192244 except Exception as e :
193245 logger .error (f"Database error validating API key: { e } " )
194246 return None
@@ -199,6 +251,7 @@ def revoke_api_key(self, user_id: str, key_id: str) -> bool:
199251 if key_id in _in_memory_api_keys :
200252 if _in_memory_api_keys [key_id ].get ("user_id" ) == user_id :
201253 _in_memory_api_keys [key_id ]["is_active" ] = False
254+ self ._clear_validation_cache ()
202255 return True
203256 return False
204257
@@ -208,7 +261,10 @@ def revoke_api_key(self, user_id: str, key_id: str) -> bool:
208261 {"_id" : ObjectId (key_id ), "user_id" : user_id },
209262 {"$set" : {"is_active" : False }}
210263 )
211- return result .modified_count > 0
264+ success = result .modified_count > 0
265+ if success :
266+ self ._clear_validation_cache ()
267+ return success
212268 except Exception as e :
213269 logger .error (f"Failed to revoke API key { key_id } : { e } " )
214270 return False
@@ -224,6 +280,7 @@ def update_api_key_name(
224280 if key_id in _in_memory_api_keys :
225281 if _in_memory_api_keys [key_id ].get ("user_id" ) == user_id :
226282 _in_memory_api_keys [key_id ]["name" ] = new_name
283+ self ._clear_validation_cache ()
227284 return True
228285 return False
229286
@@ -233,7 +290,10 @@ def update_api_key_name(
233290 {"_id" : ObjectId (key_id ), "user_id" : user_id },
234291 {"$set" : {"name" : new_name }}
235292 )
236- return result .modified_count > 0
293+ success = result .modified_count > 0
294+ if success :
295+ self ._clear_validation_cache ()
296+ return success
237297 except Exception as e :
238298 logger .error (f"Failed to update API key name { key_id } : { e } " )
239299 return False
0 commit comments