11"""API key management and metrics routes."""
2+ from typing import Any , Dict
3+ from uuid import UUID
4+
25from fastapi import APIRouter , HTTPException , Depends
36from pydantic import BaseModel
47
1518router = APIRouter (prefix = "" , tags = ["API Keys" ])
1619
1720
21+ MAX_KEYS_PER_USER = 5
22+
23+
1824class CreateAPIKeyRequest (BaseModel ):
1925 name : str
20- tier : str = "free"
2126
2227
2328@router .get ("/metrics" )
@@ -46,37 +51,54 @@ async def generate_api_key(
4651 auth : AuthContext = Depends (require_auth )
4752):
4853 """Generate a new API key."""
49- set_operation_context ("generate_api_key" , user_id = auth .user_id , tier = request .tier )
50- add_breadcrumb ("API key generation requested" , category = "api_keys" , tier = request .tier )
54+ if not auth .user_id :
55+ raise HTTPException (status_code = 401 , detail = "User ID required" )
56+
57+ set_operation_context ("generate_api_key" , user_id = auth .user_id , tier = auth .tier )
58+ add_breadcrumb ("API key generation requested" , category = "api_keys" , tier = auth .tier )
5159
5260 logger .info (
5361 "API key generation requested" ,
5462 user_id = auth .user_id ,
5563 key_name = request .name ,
56- tier = request .tier
64+ tier = auth .tier
5765 )
5866
67+ # Tier is locked to the user's auth tier (no self-escalation)
68+ tier = auth .tier
69+
70+ # Enforce key limit per user
71+ key_count = api_key_manager .count_keys (auth .user_id )
72+ if key_count >= MAX_KEYS_PER_USER :
73+ raise HTTPException (
74+ status_code = 403 ,
75+ detail = f"Maximum { MAX_KEYS_PER_USER } active API keys allowed. Revoke an existing key first."
76+ )
77+
5978 try :
60- with track_time ("generate_api_key" , tier = request . tier ):
61- new_key = api_key_manager .generate_key (
79+ with track_time ("generate_api_key" , tier = tier ):
80+ result = api_key_manager .generate_key (
6281 name = request .name ,
63- tier = request . tier ,
82+ tier = tier ,
6483 user_id = auth .user_id
6584 )
6685
6786 logger .info (
6887 "API key generated successfully" ,
6988 user_id = auth .user_id ,
7089 key_name = request .name ,
71- tier = request . tier
90+ tier = tier
7291 )
7392
7493 return {
75- "api_key" : new_key ,
76- "tier" : request .tier ,
94+ "api_key" : result ["key" ],
95+ "id" : result ["id" ],
96+ "tier" : tier ,
7797 "name" : request .name ,
7898 "message" : "Save this key securely - it won't be shown again"
7999 }
100+ except HTTPException :
101+ raise
80102 except Exception as e :
81103 logger .error (
82104 "API key generation failed" ,
@@ -93,6 +115,49 @@ async def generate_api_key(
93115 raise HTTPException (status_code = 500 , detail = "Failed to generate API key" )
94116
95117
118+ @router .get ("/keys" )
119+ async def list_api_keys (
120+ auth : AuthContext = Depends (require_auth )
121+ ) -> Dict [str , Any ]:
122+ """List all API keys for the authenticated user."""
123+ if not auth .user_id :
124+ raise HTTPException (status_code = 401 , detail = "User ID required" )
125+
126+ try :
127+ keys = api_key_manager .list_keys (auth .user_id )
128+ return {"keys" : keys }
129+ except Exception as e :
130+ logger .error ("Failed to list API keys" , user_id = auth .user_id , error = str (e ))
131+ capture_exception (e , operation = "list_api_keys" , user_id = auth .user_id )
132+ raise HTTPException (status_code = 500 , detail = "Failed to list API keys" )
133+
134+
135+ @router .delete ("/keys/{key_id}" )
136+ async def revoke_api_key (
137+ key_id : UUID ,
138+ auth : AuthContext = Depends (require_auth )
139+ ) -> Dict [str , Any ]:
140+ """Revoke an API key by ID. Soft-deletes (sets active=false)."""
141+ if not auth .user_id :
142+ raise HTTPException (status_code = 401 , detail = "User ID required" )
143+
144+ try :
145+ success = api_key_manager .revoke_key_by_id (str (key_id ), auth .user_id )
146+ if not success :
147+ raise HTTPException (
148+ status_code = 404 ,
149+ detail = "API key not found or not owned by you"
150+ )
151+ logger .info ("API key revoked" , user_id = auth .user_id , key_id = key_id )
152+ return {"message" : "API key revoked" , "key_id" : key_id }
153+ except HTTPException :
154+ raise
155+ except Exception as e :
156+ logger .error ("Failed to revoke API key" , user_id = auth .user_id , error = str (e ))
157+ capture_exception (e , operation = "revoke_api_key" , user_id = auth .user_id )
158+ raise HTTPException (status_code = 500 , detail = "Failed to revoke API key" )
159+
160+
96161@router .get ("/keys/usage" )
97162async def get_api_usage (
98163 auth : AuthContext = Depends (require_auth )
0 commit comments