Skip to main content

auth_framework/api/
mod.rs

1//! REST API Server Module
2//!
3//! This module provides a comprehensive REST API server implementation
4//! that exposes all AuthFramework functionality through HTTP endpoints.
5
6pub mod admin;
7pub mod auth;
8pub mod email_verification;
9pub mod error_codes;
10pub mod health;
11pub mod metrics;
12pub mod mfa;
13pub mod middleware;
14pub mod oauth2;
15pub mod oauth_advanced; pub mod advanced_protocols;
16pub mod openapi;
17pub mod responses;
18pub mod saml;
19pub mod security;
20pub mod security_simple;
21pub mod server;
22pub mod users;
23pub mod validation;
24pub mod versioning;
25pub mod webauthn;
26
27#[cfg(feature = "enhanced-rbac")]
28#[cfg(feature = "role-system")]
29pub mod rbac_endpoints;
30
31pub use responses::{ApiError, ApiResponse, ApiResult};
32pub use security::SecurityManager;
33pub use server::ApiServer;
34
35use crate::AuthFramework;
36use crate::distributed::rate_limiting::{DistributedRateLimiter, RateLimitConfig};
37use crate::errors::AuthError;
38use std::sync::Arc;
39
40/// API server state
41#[derive(Clone)]
42pub struct ApiState {
43    pub auth_framework: Arc<AuthFramework>,
44    pub rate_limiter: Arc<DistributedRateLimiter>,
45    #[cfg(feature = "enhanced-rbac")]
46    pub authorization_service: Arc<crate::authorization_enhanced::AuthorizationService>,
47}
48
49impl ApiState {
50    /// Create an [`ApiState`] wrapping the given [`AuthFramework`].
51    ///
52    /// A balanced distributed rate-limiter is initialised automatically.
53    ///
54    /// # Example
55    /// ```rust,ignore
56    /// let state = ApiState::new(Arc::new(auth_framework)).await?;
57    /// ```
58    pub async fn new(auth_framework: Arc<AuthFramework>) -> crate::errors::Result<Self> {
59        let rate_limiter =
60            Arc::new(DistributedRateLimiter::new(RateLimitConfig::balanced()).await?);
61        Ok(Self {
62            auth_framework,
63            rate_limiter,
64            #[cfg(feature = "enhanced-rbac")]
65            authorization_service: Arc::new(
66                crate::authorization_enhanced::AuthorizationService::new().await?,
67            ),
68        })
69    }
70
71    /// Build an [`ApiState`] with a pre-constructed authorization service.
72    ///
73    /// # Example
74    /// ```rust,ignore
75    /// let state = ApiState::with_authorization_service(fw, limiter, authz);
76    /// ```
77    #[cfg(feature = "enhanced-rbac")]
78    pub fn with_authorization_service(
79        auth_framework: Arc<AuthFramework>,
80        rate_limiter: Arc<DistributedRateLimiter>,
81        authorization_service: Arc<crate::authorization_enhanced::AuthorizationService>,
82    ) -> Self {
83        Self {
84            auth_framework,
85            rate_limiter,
86            authorization_service,
87        }
88    }
89}
90
91/// Extract bearer token from Authorization header
92pub fn extract_bearer_token(headers: &axum::http::HeaderMap) -> Option<String> {
93    headers
94        .get("authorization")
95        .and_then(|header| header.to_str().ok())
96        .and_then(|auth_str| auth_str.strip_prefix("Bearer "))
97        .filter(|token| !token.is_empty())
98        .map(|token| token.to_string())
99}
100
101/// Validate API token and extract user information
102pub async fn validate_api_token(
103    auth_framework: &AuthFramework,
104    token: &str,
105) -> Result<crate::tokens::AuthToken, AuthError> {
106    // Validate the JWT signature and expiry
107    let token_obj = auth_framework.token_manager().validate_jwt_token(token)?;
108
109    // Check whether this token has been explicitly revoked (e.g. via logout)
110    let revocation_key = format!("revoked_token:{}", token_obj.jti);
111    match auth_framework.storage().get_kv(&revocation_key).await {
112        Ok(Some(_)) => {
113            return Err(AuthError::Unauthorized(
114                "Token has been revoked".to_string(),
115            ));
116        }
117        Ok(None) => {} // Not revoked — proceed
118        Err(e) => {
119            tracing::error!("Could not check token revocation list: {}", e);
120            return Err(AuthError::Unauthorized(
121                "Unable to verify token status".to_string(),
122            ));
123        }
124    }
125
126    // Convert the validated token claims to AuthToken.
127    // Roles are not embedded in JWT claims (create_jwt_token sets roles: None),
128    // so we load them from the user record in KV storage to allow role-based
129    // access checks (e.g. admin endpoints) to work correctly.
130    let user_id_str = token_obj.sub.clone();
131    let roles = {
132        let user_key = format!("user:{}", user_id_str);
133        match auth_framework.storage().get_kv(&user_key).await {
134            Ok(Some(bytes)) => {
135                let json: serde_json::Value = match serde_json::from_slice(&bytes) {
136                    Ok(v) => v,
137                    Err(e) => {
138                        tracing::warn!(user_id = %user_id_str, "Failed to parse user record JSON for role extraction: {}", e);
139                        serde_json::Value::default()
140                    }
141                };
142                // Check if account is active
143                if json["active"].as_bool() == Some(false) {
144                    return Err(AuthError::Unauthorized("Account is disabled".to_string()));
145                }
146                json["roles"]
147                    .as_array()
148                    .map(|arr| {
149                        arr.iter()
150                            .filter_map(|v| v.as_str())
151                            .map(|s| s.to_string())
152                            .collect::<Vec<_>>()
153                    })
154                    .unwrap_or_else(|| token_obj.roles.clone().unwrap_or_default())
155            }
156            _ => token_obj.roles.clone().unwrap_or_default(),
157        }
158    };
159
160    Ok(crate::tokens::AuthToken {
161        token_id: token_obj.jti.clone(),
162        user_id: user_id_str.clone(),
163        access_token: token.to_string(),
164        token_type: Some("Bearer".to_string()),
165        subject: Some(user_id_str.clone()),
166        issuer: Some(token_obj.iss.clone()),
167        refresh_token: None,
168        issued_at: chrono::DateTime::from_timestamp(token_obj.iat, 0)
169            .unwrap_or_else(chrono::Utc::now),
170        expires_at: chrono::DateTime::from_timestamp(token_obj.exp, 0)
171            .unwrap_or_else(chrono::Utc::now),
172        scopes: token_obj
173            .scope
174            .split_whitespace()
175            .map(|s| s.to_string())
176            .collect(),
177        auth_method: "jwt".to_string(),
178        client_id: token_obj.client_id,
179        user_profile: None,
180        permissions: token_obj.permissions.unwrap_or_default().into(),
181        roles: roles.into(),
182        metadata: crate::tokens::TokenMetadata {
183            session_id: None, // JWT tokens don't have session_id in claims by default
184            ..Default::default()
185        },
186    })
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use axum::http::HeaderMap;
193
194    #[test]
195    fn test_extract_bearer_token_valid() {
196        let mut headers = HeaderMap::new();
197        headers.insert("authorization", "Bearer mytoken123".parse().unwrap());
198        assert_eq!(
199            extract_bearer_token(&headers),
200            Some("mytoken123".to_string())
201        );
202    }
203
204    #[test]
205    fn test_extract_bearer_token_missing() {
206        let headers = HeaderMap::new();
207        assert_eq!(extract_bearer_token(&headers), None);
208    }
209
210    #[test]
211    fn test_extract_bearer_token_not_bearer() {
212        let mut headers = HeaderMap::new();
213        headers.insert("authorization", "Basic abc123".parse().unwrap());
214        assert_eq!(extract_bearer_token(&headers), None);
215    }
216
217    #[test]
218    fn test_extract_bearer_token_empty_value() {
219        let mut headers = HeaderMap::new();
220        headers.insert("authorization", "Bearer ".parse().unwrap());
221        // SEC-6: Empty bearer tokens are rejected
222        assert_eq!(extract_bearer_token(&headers), None);
223    }
224
225    #[tokio::test]
226    async fn test_validate_api_token_invalid() {
227        let config = crate::AuthConfig::new().secret("a]Bc!d@e#f$g%h^i&j*k(l)m_n-o+p=q");
228        let fw = AuthFramework::new(config);
229        let result = validate_api_token(&fw, "not.a.valid.token").await;
230        assert!(result.is_err());
231    }
232
233    #[tokio::test]
234    async fn test_validate_api_token_revoked() {
235        let config = crate::AuthConfig::new().secret("a]Bc!d@e#f$g%h^i&j*k(l)m_n-o+p=q");
236        let mut fw = AuthFramework::new(config);
237        fw.initialize().await.unwrap();
238
239        // Create a valid JWT token
240        let token = fw
241            .token_manager()
242            .create_jwt_token("user1", vec!["user".into()], None)
243            .unwrap();
244        let token_obj = fw.token_manager().validate_jwt_token(&token).unwrap();
245
246        // Revoke it
247        let revocation_key = format!("revoked_token:{}", token_obj.jti);
248        fw.storage()
249            .store_kv(&revocation_key, b"revoked", None)
250            .await
251            .unwrap();
252
253        // Should fail with "revoked"
254        let result = validate_api_token(&fw, &token).await;
255        assert!(result.is_err());
256        let err_msg = format!("{}", result.unwrap_err());
257        assert!(err_msg.contains("revoked"));
258    }
259
260    #[tokio::test]
261    async fn test_validate_api_token_success() {
262        let config = crate::AuthConfig::new().secret("a]Bc!d@e#f$g%h^i&j*k(l)m_n-o+p=q");
263        let mut fw = AuthFramework::new(config);
264        fw.initialize().await.unwrap();
265
266        let token = fw
267            .token_manager()
268            .create_jwt_token("user_abc", vec!["user".into()], None)
269            .unwrap();
270
271        let auth_token = validate_api_token(&fw, &token).await.unwrap();
272        assert_eq!(auth_token.user_id, "user_abc");
273        assert_eq!(auth_token.auth_method, "jwt");
274        assert_eq!(auth_token.token_type.as_deref(), Some("Bearer"));
275    }
276
277    #[tokio::test]
278    async fn test_validate_api_token_with_roles_from_storage() {
279        let config = crate::AuthConfig::new().secret("a]Bc!d@e#f$g%h^i&j*k(l)m_n-o+p=q");
280        let mut fw = AuthFramework::new(config);
281        fw.initialize().await.unwrap();
282
283        // Store a user profile with an admin role
284        let user_json = serde_json::json!({"user_id": "role_user", "roles": ["admin", "editor"]});
285        fw.storage()
286            .store_kv(
287                "user:role_user",
288                serde_json::to_vec(&user_json).unwrap().as_slice(),
289                None,
290            )
291            .await
292            .unwrap();
293
294        let token = fw
295            .token_manager()
296            .create_jwt_token("role_user", vec!["user".into()], None)
297            .unwrap();
298
299        let auth_token = validate_api_token(&fw, &token).await.unwrap();
300        assert!(auth_token.roles.contains(&"admin".to_string()));
301        assert!(auth_token.roles.contains(&"editor".to_string()));
302    }
303}