auth_framework/api/
auth.rs

1//! Authentication API Endpoints
2//!
3//! Handles login, logout, token refresh, and related authentication operations
4
5use crate::api::{ApiResponse, ApiState, extract_bearer_token};
6use axum::{Json, extract::State, http::HeaderMap};
7use serde::{Deserialize, Serialize};
8
9/// Login request payload
10#[derive(Debug, Deserialize)]
11pub struct LoginRequest {
12    pub username: String,
13    pub password: String,
14    #[serde(default)]
15    pub mfa_code: Option<String>,
16    #[serde(default)]
17    pub remember_me: bool,
18}
19
20/// Login response data
21#[derive(Debug, Serialize)]
22pub struct LoginResponse {
23    pub access_token: String,
24    pub refresh_token: String,
25    pub token_type: String,
26    pub expires_in: u64,
27    pub user: UserInfo,
28}
29
30/// User information in login response
31#[derive(Debug, Serialize)]
32pub struct UserInfo {
33    pub id: String,
34    pub username: String,
35    pub roles: Vec<String>,
36    pub permissions: Vec<String>,
37}
38
39/// Token refresh request
40#[derive(Debug, Deserialize)]
41pub struct RefreshRequest {
42    pub refresh_token: String,
43}
44
45/// Token refresh response
46#[derive(Debug, Serialize)]
47pub struct RefreshResponse {
48    pub access_token: String,
49    pub token_type: String,
50    pub expires_in: u64,
51}
52
53/// Logout request
54#[derive(Debug, Deserialize)]
55pub struct LogoutRequest {
56    #[serde(default)]
57    pub refresh_token: Option<String>,
58}
59
60/// POST /auth/login
61pub async fn login(
62    State(state): State<ApiState>,
63    Json(req): Json<LoginRequest>,
64) -> ApiResponse<LoginResponse> {
65    // Validate required fields
66    if req.username.is_empty() || req.password.is_empty() {
67        return ApiResponse::validation_error_typed("Username and password are required");
68    }
69
70    // Create credential for authentication
71    let credential = crate::authentication::credentials::Credential::Password {
72        username: req.username.clone(),
73        password: req.password.clone(),
74    };
75
76    // Attempt authentication
77    match state
78        .auth_framework
79        .authenticate("password", credential)
80        .await
81    {
82        Ok(auth_result) => match auth_result {
83            crate::auth::AuthResult::Success(token) => {
84                // Create response with token information
85                let user_info = UserInfo {
86                    id: token.user_id.clone(),
87                    username: req.username,
88                    roles: token.roles.clone(),
89                    permissions: token.permissions.clone(),
90                };
91
92                // Generate actual JWT access token
93                let token_lifetime = std::time::Duration::from_secs(3600); // 1 hour
94                let access_token = match state.auth_framework.token_manager().create_jwt_token(
95                    &token.user_id,
96                    token.permissions.clone(),
97                    Some(token_lifetime),
98                ) {
99                    Ok(jwt) => jwt,
100                    Err(e) => {
101                        tracing::error!("Failed to create JWT token: {}", e);
102                        return ApiResponse::error_typed(
103                            "TOKEN_CREATION_FAILED",
104                            "Failed to create access token",
105                        );
106                    }
107                };
108
109                // Generate refresh token with longer lifetime
110                let refresh_token_lifetime = std::time::Duration::from_secs(86400 * 7); // 7 days
111                let refresh_token = match state.auth_framework.token_manager().create_jwt_token(
112                    &token.user_id,
113                    vec!["refresh".to_string()],
114                    Some(refresh_token_lifetime),
115                ) {
116                    Ok(jwt) => jwt,
117                    Err(e) => {
118                        tracing::error!("Failed to create refresh token: {}", e);
119                        return ApiResponse::error_typed(
120                            "TOKEN_CREATION_FAILED",
121                            "Failed to create refresh token",
122                        );
123                    }
124                };
125
126                let response = LoginResponse {
127                    access_token,
128                    refresh_token,
129                    token_type: "Bearer".to_string(),
130                    expires_in: 3600, // 1 hour
131                    user: user_info,
132                };
133
134                ApiResponse::success(response)
135            }
136            crate::auth::AuthResult::MfaRequired(_challenge) => {
137                // In real implementation, return MFA challenge info
138                ApiResponse::error_typed("MFA_REQUIRED", "Multi-factor authentication required")
139            }
140            crate::auth::AuthResult::Failure(reason) => {
141                tracing::warn!(
142                    "Authentication failed for user '{}': {}",
143                    req.username,
144                    reason
145                );
146                ApiResponse::unauthorized_typed(
147                    "INVALID_CREDENTIALS",
148                    "Invalid username or password",
149                )
150            }
151        },
152        Err(e) => {
153            // Convert auth error to API error - return 401 for authentication failures
154            tracing::warn!("Authentication failed for user '{}': {}", req.username, e);
155            if matches!(e, crate::errors::AuthError::AuthMethod { .. }) {
156                ApiResponse::unauthorized_typed(
157                    "INVALID_CREDENTIALS",
158                    "Invalid username or password",
159                )
160            } else {
161                ApiResponse::unauthorized_typed("AUTH_ERROR", "Authentication failed")
162            }
163        }
164    }
165}
166
167/// POST /auth/refresh
168pub async fn refresh_token(
169    State(state): State<ApiState>,
170    Json(req): Json<RefreshRequest>,
171) -> ApiResponse<RefreshResponse> {
172    if req.refresh_token.is_empty() {
173        return ApiResponse::validation_error_typed("Refresh token is required");
174    }
175
176    // Validate the refresh token
177    match state
178        .auth_framework
179        .token_manager()
180        .validate_jwt_token(&req.refresh_token)
181    {
182        Ok(claims) => {
183            // Check if this is actually a refresh token
184            if !claims.scope.contains("refresh") {
185                return ApiResponse::unauthorized_typed(
186                    "INVALID_TOKEN",
187                    "Token is not a refresh token",
188                );
189            }
190
191            // Create new access token
192            let token_lifetime = std::time::Duration::from_secs(3600); // 1 hour
193            let new_access_token = match state.auth_framework.token_manager().create_jwt_token(
194                &claims.sub,
195                vec!["read".to_string(), "write".to_string()], // Default permissions
196                Some(token_lifetime),
197            ) {
198                Ok(jwt) => jwt,
199                Err(e) => {
200                    tracing::error!("Failed to create new access token: {}", e);
201                    return ApiResponse::error_typed(
202                        "TOKEN_CREATION_FAILED",
203                        "Failed to create new access token",
204                    );
205                }
206            };
207
208            let response = RefreshResponse {
209                access_token: new_access_token,
210                token_type: "Bearer".to_string(),
211                expires_in: 3600,
212            };
213
214            ApiResponse::success(response)
215        }
216        Err(e) => {
217            tracing::warn!("Invalid refresh token: {}", e);
218            ApiResponse::unauthorized_typed("INVALID_TOKEN", "Invalid or expired refresh token")
219        }
220    }
221}
222
223/// POST /auth/logout
224pub async fn logout(
225    State(_state): State<ApiState>,
226    headers: HeaderMap,
227    Json(req): Json<LogoutRequest>,
228) -> ApiResponse<()> {
229    // Extract token from Authorization header
230    if let Some(token) = extract_bearer_token(&headers) {
231        // In a real implementation, invalidate the token
232        tracing::info!("Logging out user with token: {}", &token[..10]);
233    }
234
235    // If refresh token provided, invalidate it too
236    if let Some(ref refresh_token) = req.refresh_token {
237        tracing::info!("Invalidating refresh token: {}", &refresh_token[..10]);
238    }
239
240    ApiResponse::<()>::ok_with_message("Successfully logged out")
241}
242
243/// GET /auth/validate
244/// Validate current token and return user information
245pub async fn validate_token(
246    State(state): State<ApiState>,
247    headers: HeaderMap,
248) -> ApiResponse<UserInfo> {
249    match extract_bearer_token(&headers) {
250        Some(token) => {
251            match crate::api::validate_api_token(&state.auth_framework, &token).await {
252                Ok(auth_token) => {
253                    // Fetch actual user information from storage
254                    let username = match state
255                        .auth_framework
256                        .get_user_profile(&auth_token.user_id)
257                        .await
258                    {
259                        Ok(profile) => profile
260                            .username
261                            .unwrap_or_else(|| format!("user_{}", auth_token.user_id)),
262                        Err(_) => format!("user_{}", auth_token.user_id), // Fallback if profile fetch fails
263                    };
264
265                    let user_info = UserInfo {
266                        id: auth_token.user_id,
267                        username,
268                        roles: auth_token.roles,
269                        permissions: auth_token.permissions,
270                    };
271                    ApiResponse::success(user_info)
272                }
273                Err(_e) => ApiResponse::error_typed("AUTH_ERROR", "Token validation failed"),
274            }
275        }
276        None => ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required"),
277    }
278}
279
280/// GET /auth/providers
281/// List available OAuth providers
282pub async fn list_providers(State(_state): State<ApiState>) -> ApiResponse<Vec<ProviderInfo>> {
283    let providers = vec![
284        ProviderInfo {
285            name: "google".to_string(),
286            display_name: "Google".to_string(),
287            auth_url: "/oauth/google".to_string(),
288        },
289        ProviderInfo {
290            name: "github".to_string(),
291            display_name: "GitHub".to_string(),
292            auth_url: "/oauth/github".to_string(),
293        },
294        ProviderInfo {
295            name: "microsoft".to_string(),
296            display_name: "Microsoft".to_string(),
297            auth_url: "/oauth/microsoft".to_string(),
298        },
299    ];
300
301    ApiResponse::success(providers)
302}
303
304/// Provider information
305#[derive(Debug, Serialize)]
306pub struct ProviderInfo {
307    pub name: String,
308    pub display_name: String,
309    pub auth_url: String,
310}
311
312/// User registration request
313#[derive(Debug, Deserialize)]
314pub struct RegisterRequest {
315    pub username: String,
316    pub email: String,
317    pub password: String,
318    #[serde(default)]
319    pub full_name: Option<String>,
320}
321
322/// User registration response
323#[derive(Debug, Serialize)]
324pub struct RegisterResponse {
325    pub user_id: String,
326    pub username: String,
327    pub email: String,
328    pub created_at: String,
329}
330
331/// POST /auth/register
332/// Public endpoint for user self-registration
333pub async fn register(
334    State(state): State<ApiState>,
335    Json(req): Json<RegisterRequest>,
336) -> ApiResponse<RegisterResponse> {
337    // Validate input
338    if req.username.is_empty() || req.password.is_empty() || req.email.is_empty() {
339        return ApiResponse::validation_error_typed("Username, password, and email are required");
340    }
341
342    // Use the centralized register_user method
343    match state
344        .auth_framework
345        .register_user(&req.username, &req.email, &req.password)
346        .await
347    {
348        Ok(user_id) => {
349            let created_at = chrono::Utc::now().to_rfc3339();
350
351            tracing::info!(
352                "New user registered via API: {} ({})",
353                req.username,
354                user_id
355            );
356
357            let response = RegisterResponse {
358                user_id,
359                username: req.username,
360                email: req.email,
361                created_at,
362            };
363
364            ApiResponse::success(response)
365        }
366        Err(e) => {
367            tracing::warn!("User registration failed: {:?}", e);
368
369            // Map error types to appropriate HTTP responses
370            let error_msg = e.to_string();
371            if error_msg.contains("Username already exists") {
372                ApiResponse::conflict_typed("USERNAME_EXISTS", "Username already exists")
373            } else if error_msg.contains("Email address already registered") {
374                ApiResponse::conflict_typed("EMAIL_EXISTS", "Email address already registered")
375            } else if error_msg.contains("validation") || error_msg.contains("Password") {
376                ApiResponse::validation_error_typed(&error_msg)
377            } else {
378                ApiResponse::error_typed("REGISTRATION_FAILED", "Failed to create user account")
379            }
380        }
381    }
382}
383
384/// Flexible authentication request that supports multiple authentication methods
385#[derive(Debug, Deserialize)]
386pub struct AuthenticateRequest {
387    pub method: String,
388    pub credential: serde_json::Value,
389}
390
391/// POST /auth/authenticate - Flexible authentication endpoint
392pub async fn authenticate(
393    State(state): State<ApiState>,
394    Json(req): Json<AuthenticateRequest>,
395) -> ApiResponse<LoginResponse> {
396    // Parse credential based on method
397    let credential = match req.method.as_str() {
398        "password" => {
399            let username = req.credential["username"]
400                .as_str()
401                .unwrap_or_default()
402                .to_string();
403            let password = req.credential["password"]
404                .as_str()
405                .unwrap_or_default()
406                .to_string();
407
408            if username.is_empty() || password.is_empty() {
409                return ApiResponse::validation_error_typed("Username and password are required");
410            }
411
412            crate::authentication::credentials::Credential::Password { username, password }
413        }
414        "jwt" => {
415            let token = req.credential["token"]
416                .as_str()
417                .unwrap_or_default()
418                .to_string();
419
420            if token.is_empty() {
421                return ApiResponse::validation_error_typed("JWT token is required");
422            }
423
424            crate::authentication::credentials::Credential::Jwt { token }
425        }
426        "api_key" => {
427            let key = req.credential["key"]
428                .as_str()
429                .unwrap_or_default()
430                .to_string();
431
432            if key.is_empty() {
433                return ApiResponse::validation_error_typed("API key is required");
434            }
435
436            crate::authentication::credentials::Credential::ApiKey { key }
437        }
438        "bearer" => {
439            let token = req.credential["token"]
440                .as_str()
441                .unwrap_or_default()
442                .to_string();
443
444            if token.is_empty() {
445                return ApiResponse::validation_error_typed("Bearer token is required");
446            }
447
448            crate::authentication::credentials::Credential::Bearer { token }
449        }
450        _ => {
451            return ApiResponse::validation_error_typed(format!(
452                "Unsupported authentication method: {}",
453                req.method
454            ));
455        }
456    };
457
458    // Attempt authentication
459    match state
460        .auth_framework
461        .authenticate(&req.method, credential)
462        .await
463    {
464        Ok(auth_result) => match auth_result {
465            crate::auth::AuthResult::Success(token) => {
466                // Extract username from token or credential
467                let username = match req.method.as_str() {
468                    "password" => req.credential["username"]
469                        .as_str()
470                        .unwrap_or("unknown")
471                        .to_string(),
472                    _ => token.user_id.clone(),
473                };
474
475                // Create response with token information
476                let user_info = UserInfo {
477                    id: token.user_id.clone(),
478                    username,
479                    roles: token.roles.clone(),
480                    permissions: token.permissions.clone(),
481                };
482
483                // For JWT/API Key/Bearer methods, the token is already valid
484                let (access_token, refresh_token) = if req.method == "password" {
485                    // Generate new tokens for password authentication
486                    let token_lifetime = std::time::Duration::from_secs(3600); // 1 hour
487                    let access = match state.auth_framework.token_manager().create_jwt_token(
488                        &token.user_id,
489                        token.permissions.clone(),
490                        Some(token_lifetime),
491                    ) {
492                        Ok(jwt) => jwt,
493                        Err(e) => {
494                            tracing::error!("Failed to create JWT token: {}", e);
495                            return ApiResponse::error_typed(
496                                "TOKEN_CREATION_FAILED",
497                                "Failed to create access token",
498                            );
499                        }
500                    };
501
502                    let refresh_token_lifetime = std::time::Duration::from_secs(86400 * 7); // 7 days
503                    let refresh = match state.auth_framework.token_manager().create_jwt_token(
504                        &token.user_id,
505                        vec!["refresh".to_string()],
506                        Some(refresh_token_lifetime),
507                    ) {
508                        Ok(jwt) => jwt,
509                        Err(e) => {
510                            tracing::error!("Failed to create refresh token: {}", e);
511                            return ApiResponse::error_typed(
512                                "TOKEN_CREATION_FAILED",
513                                "Failed to create refresh token",
514                            );
515                        }
516                    };
517
518                    (access, refresh)
519                } else {
520                    // For other methods, return the existing token
521                    (token.access_token.clone(), String::new())
522                };
523
524                let response = LoginResponse {
525                    access_token,
526                    refresh_token,
527                    token_type: "Bearer".to_string(),
528                    expires_in: 3600,
529                    user: user_info,
530                };
531
532                ApiResponse::success(response)
533            }
534            crate::auth::AuthResult::MfaRequired(_challenge) => {
535                ApiResponse::error_typed("MFA_REQUIRED", "Multi-factor authentication required")
536            }
537            crate::auth::AuthResult::Failure(reason) => {
538                tracing::warn!("Authentication failed: {}", reason);
539                ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required")
540            }
541        },
542        Err(e) => {
543            tracing::error!("Authentication error: {:?}", e);
544            ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required")
545        }
546    }
547}
548
549/// API Key creation request
550#[derive(Debug, Deserialize)]
551pub struct CreateApiKeyRequest {
552    pub name: String,
553    #[serde(default)]
554    pub scopes: Vec<String>,
555    #[serde(default)]
556    pub permissions: Vec<String>,
557    #[serde(default)]
558    pub expires_in_days: Option<u64>,
559}
560
561/// API Key response
562#[derive(Debug, Serialize)]
563pub struct ApiKeyResponse {
564    pub key: String,
565    pub name: String,
566    pub user_id: String,
567    pub scopes: Vec<String>,
568    pub permissions: Vec<String>,
569    pub created_at: String,
570    pub expires_at: Option<String>,
571}
572
573/// API Key list item
574#[derive(Debug, Serialize)]
575pub struct ApiKeyInfo {
576    pub name: String,
577    pub key_prefix: String, // Only show first 8 characters
578    pub scopes: Vec<String>,
579    pub created_at: String,
580    pub expires_at: Option<String>,
581    pub last_used: Option<String>,
582    pub use_count: u64,
583}
584
585/// POST /api-keys - Create a new API key
586pub async fn create_api_key(
587    State(state): State<ApiState>,
588    headers: HeaderMap,
589    Json(req): Json<CreateApiKeyRequest>,
590) -> ApiResponse<ApiKeyResponse> {
591    // Extract and validate bearer token
592    let token = match extract_bearer_token(&headers) {
593        Some(t) => t,
594        None => return ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required"),
595    };
596
597    // Validate the token
598    let claims = match state
599        .auth_framework
600        .token_manager()
601        .validate_jwt_token(&token)
602    {
603        Ok(c) => c,
604        Err(_) => {
605            return ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required");
606        }
607    };
608
609    let user_id = claims.sub.clone();
610
611    // Validate name
612    if req.name.is_empty() {
613        return ApiResponse::validation_error_typed("API key name is required");
614    }
615
616    // Generate API key
617    let api_key = format!("ak_{}", uuid::Uuid::new_v4().to_string().replace("-", ""));
618
619    // Calculate expiration
620    let created_at = chrono::Utc::now();
621    let expires_at = req
622        .expires_in_days
623        .map(|days| created_at + chrono::Duration::days(days as i64));
624
625    // Set default scopes and permissions if not provided
626    let scopes = if req.scopes.is_empty() {
627        vec!["api_access".to_string()]
628    } else {
629        req.scopes
630    };
631
632    let permissions = if req.permissions.is_empty() {
633        vec!["read".to_string()]
634    } else {
635        req.permissions
636    };
637
638    // Store API key data
639    let key_data = serde_json::json!({
640        "user_id": user_id,
641        "name": req.name,
642        "scopes": scopes,
643        "permissions": permissions,
644        "created_at": created_at.to_rfc3339(),
645        "expires_at": expires_at.map(|dt| dt.to_rfc3339()),
646        "last_used": Option::<String>::None,
647        "use_count": 0,
648    });
649
650    let key_data_str = serde_json::to_string(&key_data).unwrap();
651    let storage_key = format!("api_key:{}", api_key);
652
653    match state
654        .auth_framework
655        .storage()
656        .store_kv(&storage_key, key_data_str.as_bytes(), None)
657        .await
658    {
659        Ok(_) => {
660            // Add key to user's index
661            let index_key = format!("user_api_keys:{}", user_id);
662            let mut key_ids = match state.auth_framework.storage().get_kv(&index_key).await {
663                Ok(Some(data)) => serde_json::from_slice::<Vec<String>>(&data).unwrap_or_default(),
664                _ => Vec::new(),
665            };
666
667            key_ids.push(api_key.clone());
668
669            if let Ok(index_data) = serde_json::to_vec(&key_ids) {
670                let _ = state
671                    .auth_framework
672                    .storage()
673                    .store_kv(&index_key, &index_data, None)
674                    .await;
675            }
676
677            tracing::info!("API key created for user {}: {}", user_id, req.name);
678
679            let response = ApiKeyResponse {
680                key: api_key,
681                name: req.name,
682                user_id,
683                scopes,
684                permissions,
685                created_at: created_at.to_rfc3339(),
686                expires_at: expires_at.map(|dt| dt.to_rfc3339()),
687            };
688
689            ApiResponse::success(response)
690        }
691        Err(e) => {
692            tracing::error!("Failed to store API key: {:?}", e);
693            ApiResponse::error_typed("API_KEY_CREATION_FAILED", "Failed to create API key")
694        }
695    }
696}
697
698/// GET /api-keys - List user's API keys
699pub async fn list_api_keys(
700    State(state): State<ApiState>,
701    headers: HeaderMap,
702) -> ApiResponse<Vec<ApiKeyInfo>> {
703    // Extract and validate bearer token
704    let token = match extract_bearer_token(&headers) {
705        Some(t) => t,
706        None => return ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required"),
707    };
708
709    // Validate the token
710    let claims = match state
711        .auth_framework
712        .token_manager()
713        .validate_jwt_token(&token)
714    {
715        Ok(c) => c,
716        Err(_) => {
717            return ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required");
718        }
719    };
720
721    let user_id = claims.sub.clone();
722
723    // Get user's API key IDs from index
724    let index_key = format!("user_api_keys:{}", user_id);
725    let key_ids = match state.auth_framework.storage().get_kv(&index_key).await {
726        Ok(Some(data)) => match serde_json::from_slice::<Vec<String>>(&data) {
727            Ok(ids) => ids,
728            Err(e) => {
729                tracing::error!("Failed to deserialize key index: {}", e);
730                return ApiResponse::error_typed("INTERNAL_ERROR", "Failed to read API keys");
731            }
732        },
733        Ok(None) => Vec::new(),
734        Err(e) => {
735            tracing::error!("Failed to read key index: {}", e);
736            return ApiResponse::error_typed("INTERNAL_ERROR", "Failed to read API keys");
737        }
738    };
739
740    // Fetch details for each key
741    let mut keys = Vec::new();
742    for key_id in key_ids {
743        let storage_key = format!("api_key:{}", key_id);
744        if let Ok(Some(data)) = state.auth_framework.storage().get_kv(&storage_key).await
745            && let Ok(key_data) = serde_json::from_slice::<serde_json::Value>(&data)
746        {
747            // Only show first 12 characters of key for security
748            let key_prefix = if key_id.len() > 12 {
749                format!("{}...", &key_id[..12])
750            } else {
751                key_id.clone()
752            };
753
754            keys.push(ApiKeyInfo {
755                name: key_data["name"].as_str().unwrap_or("Unknown").to_string(),
756                key_prefix,
757                scopes: key_data["scopes"]
758                    .as_array()
759                    .and_then(|arr| {
760                        arr.iter()
761                            .filter_map(|v| v.as_str().map(|s| s.to_string()))
762                            .collect::<Vec<_>>()
763                            .into()
764                    })
765                    .unwrap_or_default(),
766                created_at: key_data["created_at"].as_str().unwrap_or("").to_string(),
767                expires_at: key_data["expires_at"].as_str().map(|s| s.to_string()),
768                last_used: key_data["last_used"].as_str().map(|s| s.to_string()),
769                use_count: key_data["use_count"].as_u64().unwrap_or(0),
770            });
771        }
772    }
773
774    tracing::info!("Listed {} API keys for user {}", keys.len(), user_id);
775    ApiResponse::success(keys)
776}
777
778/// DELETE /api-keys/:key_id - Revoke an API key
779#[derive(Debug, Deserialize)]
780pub struct RevokeApiKeyRequest {
781    pub key_id: String,
782}
783
784pub async fn revoke_api_key(
785    State(state): State<ApiState>,
786    headers: HeaderMap,
787    Json(req): Json<RevokeApiKeyRequest>,
788) -> ApiResponse<serde_json::Value> {
789    // Extract and validate bearer token
790    let token = match extract_bearer_token(&headers) {
791        Some(t) => t,
792        None => return ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required"),
793    };
794
795    // Validate the token
796    let claims = match state
797        .auth_framework
798        .token_manager()
799        .validate_jwt_token(&token)
800    {
801        Ok(c) => c,
802        Err(_) => {
803            return ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required");
804        }
805    };
806
807    let user_id = claims.sub.clone();
808
809    // Delete the API key
810    let storage_key = format!("api_key:{}", req.key_id);
811
812    // First verify the key belongs to this user
813    match state.auth_framework.storage().get_kv(&storage_key).await {
814        Ok(Some(data)) => {
815            let key_data_str = String::from_utf8_lossy(&data);
816            let key_data: serde_json::Value = match serde_json::from_str(&key_data_str) {
817                Ok(d) => d,
818                Err(_) => return ApiResponse::error_typed("INVALID_KEY_DATA", "Invalid key data"),
819            };
820
821            let key_user_id = key_data["user_id"].as_str().unwrap_or("");
822            if key_user_id != user_id {
823                return ApiResponse::error_typed("FORBIDDEN", "API key does not belong to user");
824            }
825
826            // Delete the key
827            match state.auth_framework.storage().delete_kv(&storage_key).await {
828                Ok(_) => {
829                    // Remove key from user's index
830                    let index_key = format!("user_api_keys:{}", user_id);
831                    if let Ok(Some(data)) = state.auth_framework.storage().get_kv(&index_key).await
832                        && let Ok(mut key_ids) = serde_json::from_slice::<Vec<String>>(&data)
833                    {
834                        key_ids.retain(|id| id != &req.key_id);
835                        if let Ok(index_data) = serde_json::to_vec(&key_ids) {
836                            let _ = state
837                                .auth_framework
838                                .storage()
839                                .store_kv(&index_key, &index_data, None)
840                                .await;
841                        }
842                    }
843
844                    tracing::info!("API key revoked: {} by user {}", req.key_id, user_id);
845                    ApiResponse::success(serde_json::json!({
846                        "message": "API key revoked successfully"
847                    }))
848                }
849                Err(e) => {
850                    tracing::error!("Failed to delete API key: {:?}", e);
851                    ApiResponse::error_typed("REVOCATION_FAILED", "Failed to revoke API key")
852                }
853            }
854        }
855        Ok(None) => ApiResponse::error_typed("NOT_FOUND", "API key not found"),
856        Err(e) => {
857            tracing::error!("Failed to retrieve API key: {:?}", e);
858            ApiResponse::error_typed("RETRIEVAL_FAILED", "Failed to retrieve API key")
859        }
860    }
861}