@@ -57,17 +57,32 @@ def _clear_refresh_cookie(response: Response) -> None:
5757
5858async def get_current_user_id (
5959 authorization : str = Header (..., description = "Bearer <token>" ),
60+ db : AsyncSession = Depends (get_db ),
6061) -> uuid .UUID :
61- """Extract and validate user ID from JWT."""
62+ """Extract and validate user ID from JWT or permanent API key ."""
6263 if not authorization .startswith ("Bearer " ):
6364 from app .shared .types import AuthenticationError
64-
6565 raise AuthenticationError ("Invalid authorization header" )
66- token = authorization [7 :]
66+ token = authorization [7 :].strip ()
67+ if token .startswith ("wos-" ):
68+ from sqlalchemy import text
69+ result = await db .execute (
70+ text ("SELECT user_id FROM auth.api_keys WHERE key_hash=:key AND is_active=true" ),
71+ {"key" : token }
72+ )
73+ row = result .fetchone ()
74+ if not row :
75+ from app .shared .types import AuthenticationError
76+ raise AuthenticationError ("Invalid API key" )
77+ await db .execute (
78+ text ("UPDATE auth.api_keys SET last_used=NOW() WHERE key_hash=:key" ),
79+ {"key" : token }
80+ )
81+ await db .commit ()
82+ return uuid .UUID (str (row [0 ]))
6783 payload = decode_token (token )
6884 if payload .get ("type" ) != "access" :
6985 from app .shared .types import AuthenticationError
70-
7186 raise AuthenticationError ("Invalid token type" )
7287 return uuid .UUID (payload ["sub" ])
7388
@@ -270,3 +285,35 @@ async def login_history(
270285 rows = await service .list_login_history (user_id = user_id , limit = limit )
271286 payload = [LoginHistoryResponse .model_validate (row ).model_dump (mode = "json" ) for row in rows ]
272287 return success (payload )
288+
289+
290+ @router .post ("/api-keys" )
291+ async def create_api_key (
292+ payload : dict ,
293+ user_id : uuid .UUID = Depends (get_current_user_id ),
294+ db : AsyncSession = Depends (get_db ),
295+ ) -> dict :
296+ import secrets
297+ from sqlalchemy import text
298+ key = f"wos-{ secrets .token_hex (24 )} "
299+ name = payload .get ("name" , "API Key" )
300+ await db .execute (
301+ text ("INSERT INTO auth.api_keys (user_id, key_hash, name) VALUES (:uid, :key, :name)" ),
302+ {"uid" : str (user_id ), "key" : key , "name" : name }
303+ )
304+ await db .commit ()
305+ return success ({"key" : key , "name" : name , "note" : "Save this key — it won't be shown again" })
306+
307+
308+ @router .get ("/api-keys" )
309+ async def list_api_keys (
310+ user_id : uuid .UUID = Depends (get_current_user_id ),
311+ db : AsyncSession = Depends (get_db ),
312+ ) -> dict :
313+ from sqlalchemy import text
314+ result = await db .execute (
315+ text ("SELECT id, name, LEFT(key_hash,12) || '...' as key_hint, created_at FROM auth.api_keys WHERE user_id=:uid" ),
316+ {"uid" : str (user_id )}
317+ )
318+ keys = [dict (r ._mapping ) for r in result .fetchall ()]
319+ return success (keys )
0 commit comments