Skip to main content

auth_framework/auth_modular/
mod.rs

1//! Modular authentication framework with component-based architecture.
2//!
3//! This module provides a modular approach to authentication and authorization,
4//! allowing fine-grained control over individual components while maintaining
5//! the same high-level API as the main `AuthFramework`.
6//!
7//! # Architecture
8//!
9//! The modular framework separates concerns into distinct managers:
10//! - **MFA Manager**: Multi-factor authentication coordination
11//! - **Session Manager**: Session lifecycle and security
12//! - **User Manager**: User account and profile management
13//! - **Token Manager**: JWT token creation and validation
14//! - **Permission Checker**: Authorization and access control
15//!
16//! # Benefits of Modular Design
17//!
18//! - **Composability**: Use only the components you need
19//! - **Testability**: Test individual components in isolation
20//! - **Extensibility**: Replace or extend specific managers
21//! - **Memory Efficiency**: Reduced memory footprint for specialized use cases
22//! - **Performance**: Optimized component interactions
23//!
24//! # Component Independence
25//!
26//! Each manager can operate independently while sharing common storage
27//! and configuration. This allows for:
28//! - Microservice deployment patterns
29//! - Custom authentication flows
30//! - Progressive feature adoption
31//! - A/B testing of authentication methods
32//!
33//! # Example
34//!
35//! ```rust,no_run
36//! use auth_framework::auth_modular::AuthFramework;
37//! use auth_framework::config::AuthConfig;
38//!
39//! // Create modular framework
40//! let config = AuthConfig::default();
41//! let auth = AuthFramework::new(config).expect("valid modular auth framework config");
42//!
43//! // Access individual managers
44//! let mfa_manager = auth.mfa_manager();
45//! let session_manager = auth.session_manager();
46//! let user_manager = auth.user_manager();
47//! ```
48//!
49//! # Migration from Monolithic Framework
50//!
51//! The modular framework maintains API compatibility with the main framework,
52//! making migration straightforward while providing additional flexibility.
53//!
54//! # When To Use This Module
55//!
56//! Prefer [`crate::AuthFramework`] for most applications.
57//! Reach for [`crate::ModularAuthFramework`] only when you need direct access
58//! to manager-level composition such as `session_manager()` or `user_manager()`.
59
60pub mod authorization_manager;
61pub mod mfa;
62pub mod session_manager;
63pub mod user_manager;
64
65use crate::authentication::credentials::{Credential, CredentialMetadata};
66use crate::config::AuthConfig;
67use crate::errors::{AuthError, MfaError, Result};
68use crate::methods::{AuthMethod, AuthMethodEnum, MethodResult, MfaChallenge};
69use crate::permissions::{Permission, PermissionChecker};
70use crate::storage::{AuthStorage, MemoryStorage};
71use crate::tokens::{AuthToken, TokenManager};
72use crate::utils::rate_limit::RateLimiter;
73use std::collections::HashMap;
74use std::sync::Arc;
75use tokio::sync::RwLock;
76use tracing::{debug, error, info, warn};
77
78pub use authorization_manager::AuthorizationManager;
79pub use mfa::MfaManager;
80pub use session_manager::SessionManager;
81pub use user_manager::{UserInfo, UserManager};
82
83/// Result of an authentication attempt — alias for the canonical [`crate::auth::AuthResult`].
84pub use crate::auth::AuthResult;
85
86/// Main authentication framework - now focused and modular
87pub struct AuthFramework {
88    /// Configuration
89    config: AuthConfig,
90
91    /// Registered authentication methods
92    methods: HashMap<String, AuthMethodEnum>,
93
94    /// Token manager
95    token_manager: TokenManager,
96
97    /// Storage backend
98    storage: Arc<dyn AuthStorage>,
99
100    /// Permission checker
101    permission_checker: Arc<RwLock<PermissionChecker>>,
102
103    /// Rate limiter
104    rate_limiter: Option<RateLimiter>,
105
106    /// MFA manager
107    mfa_manager: MfaManager,
108
109    /// Session manager
110    session_manager: SessionManager,
111
112    /// User manager
113    user_manager: UserManager,
114
115    /// Framework initialization state
116    initialized: bool,
117}
118
119impl AuthFramework {
120    /// Create a new authentication framework.
121    ///
122    /// Returns a descriptive error if the configuration is invalid rather than
123    /// panicking, so callers can decide how to handle startup failures.
124    ///
125    /// Equivalent to [`AuthFramework::try_new`].
126    ///
127    /// # Example
128    /// ```rust,ignore
129    /// use auth_framework::{AuthFramework, config::AuthConfig};
130    ///
131    /// let fw = AuthFramework::new(AuthConfig::default())?;
132    /// ```
133    pub fn new(config: AuthConfig) -> crate::errors::Result<Self> {
134        Self::try_new(config)
135    }
136
137    /// Create a new authentication framework, returning an error instead of panicking.
138    ///
139    /// This is the preferred constructor for library callers and server startup code where
140    /// configuration errors should be handled gracefully rather than aborting the process.
141    ///
142    /// # Example
143    /// ```rust,ignore
144    /// let fw = AuthFramework::try_new(AuthConfig::default())?;
145    /// ```
146    pub fn try_new(config: AuthConfig) -> crate::errors::Result<Self> {
147        // Validate configuration
148        config.validate().map_err(|e| {
149            crate::errors::AuthError::configuration(format!("Invalid configuration: {e}"))
150        })?;
151
152        // Create token manager
153        let token_manager = if let Some(secret) = &config.security.secret_key {
154            if secret.len() < 32 {
155                tracing::warn!(
156                    "JWT secret is shorter than 32 characters. Consider using a longer secret for better security."
157                );
158            }
159            TokenManager::new_hmac(secret.as_bytes(), "auth-framework", "auth-framework")
160        } else if let Some(secret) = &config.secret {
161            if secret.len() < 32 {
162                tracing::warn!(
163                    "JWT secret is shorter than 32 characters. Consider using a longer secret for better security."
164                );
165            }
166            TokenManager::new_hmac(secret.as_bytes(), "auth-framework", "auth-framework")
167        } else if let Ok(jwt_secret) = std::env::var("JWT_SECRET") {
168            if jwt_secret.len() < 32 {
169                tracing::warn!(
170                    "JWT_SECRET is shorter than 32 characters. Consider using a longer secret for better security."
171                );
172            }
173            TokenManager::new_hmac(jwt_secret.as_bytes(), "auth-framework", "auth-framework")
174        } else {
175            return Err(crate::errors::AuthError::configuration(
176                "JWT secret not set! Please set JWT_SECRET env variable or provide in config.\n\
177                   For security reasons, no default secret is provided.\n\
178                   Generate a secure secret with: openssl rand -base64 32",
179            ));
180        };
181
182        // Create storage backend
183        let storage: Arc<dyn AuthStorage> = match &config.storage {
184            #[cfg(feature = "redis-storage")]
185            crate::config::StorageConfig::Redis { url, key_prefix } => Arc::new(
186                crate::storage::RedisStorage::new(url, key_prefix).map_err(|e| {
187                    crate::errors::AuthError::configuration(format!(
188                        "Failed to create Redis storage: {e}"
189                    ))
190                })?,
191            ),
192            _ => Arc::new(MemoryStorage::new()),
193        };
194
195        // Create rate limiter if enabled
196        let rate_limiter = if config.rate_limiting.enabled {
197            Some(RateLimiter::new(
198                config.rate_limiting.max_requests,
199                config.rate_limiting.window,
200            ))
201        } else {
202            None
203        };
204
205        // Create specialized managers
206        let mfa_manager = MfaManager::new(storage.clone());
207        let session_manager = SessionManager::new(storage.clone());
208        let user_manager = UserManager::new(storage.clone());
209
210        Ok(Self {
211            config,
212            methods: HashMap::new(),
213            token_manager,
214            storage,
215            permission_checker: Arc::new(RwLock::new(PermissionChecker::new())),
216            rate_limiter,
217            mfa_manager,
218            session_manager,
219            user_manager,
220            initialized: false,
221        })
222    }
223
224    /// Replace the storage backend with a custom implementation.
225    ///
226    /// This will swap the internal storage Arc and recreate dependent managers so
227    /// they use the provided storage instance.
228    pub fn replace_storage(&mut self, storage: Arc<dyn AuthStorage>) {
229        // Replace storage
230        self.storage = storage.clone();
231
232        // Recreate managers that depend on storage
233        self.mfa_manager = MfaManager::new(self.storage.clone());
234        self.session_manager = SessionManager::new(self.storage.clone());
235        self.user_manager = UserManager::new(self.storage.clone());
236    }
237
238    /// Convenience constructor that creates a framework with a custom storage instance.
239    ///
240    /// # Example
241    /// ```rust,ignore
242    /// let fw = AuthFramework::new_with_storage(config, Arc::new(MyStorage::new()))?;
243    /// ```
244    pub fn new_with_storage(
245        config: AuthConfig,
246        storage: Arc<dyn AuthStorage>,
247    ) -> crate::errors::Result<Self> {
248        let mut framework = Self::new(config)?;
249        framework.replace_storage(storage);
250        Ok(framework)
251    }
252
253    /// Create a new framework with SMSKit configuration.
254    ///
255    /// # Example
256    /// ```rust,ignore
257    /// let fw = AuthFramework::new_with_smskit_config(config, smskit_cfg)?;
258    /// ```
259    #[cfg(feature = "smskit")]
260    pub fn new_with_smskit_config(
261        config: AuthConfig,
262        smskit_config: crate::auth_modular::mfa::SmsKitConfig,
263    ) -> Result<Self> {
264        // First create the framework normally
265        let mut framework = Self::new(config)?;
266
267        // Then replace the MFA manager with one configured for SMSKit
268        framework.mfa_manager = crate::auth_modular::mfa::MfaManager::new_with_smskit_config(
269            framework.storage.clone(),
270            smskit_config,
271        )?;
272
273        Ok(framework)
274    }
275
276    /// Register an authentication method.
277    ///
278    /// # Example
279    /// ```rust,ignore
280    /// fw.register_method("password", AuthMethodEnum::Password(PasswordAuth::default()));
281    /// ```
282    pub fn register_method(&mut self, name: impl Into<String>, method: AuthMethodEnum) {
283        let name = name.into();
284        info!("Registering authentication method: {}", name);
285
286        // Validate method configuration
287        if let Err(e) = method.validate_config() {
288            error!("Method '{}' configuration validation failed: {}", name, e);
289            return;
290        }
291
292        self.methods.insert(name, method);
293    }
294
295    /// Initialize the authentication framework.
296    ///
297    /// Sets up default roles and marks the framework as ready. Must be called
298    /// before `authenticate` or `validate_token`.
299    ///
300    /// # Example
301    /// ```rust,ignore
302    /// fw.initialize().await?;
303    /// ```
304    pub async fn initialize(&mut self) -> Result<()> {
305        if self.initialized {
306            return Ok(());
307        }
308
309        info!("Initializing authentication framework");
310
311        // Initialize permission checker with default roles
312        {
313            let mut checker = self.permission_checker.write().await;
314            checker.create_default_roles();
315        }
316
317        // Perform any necessary cleanup
318        self.cleanup_expired_data().await?;
319
320        self.initialized = true;
321        info!("Authentication framework initialized successfully");
322
323        Ok(())
324    }
325
326    /// Authenticate a user with the specified method.
327    ///
328    /// Delegates to [`authenticate_with_metadata`](Self::authenticate_with_metadata)
329    /// with empty metadata.
330    ///
331    /// # Example
332    /// ```rust,ignore
333    /// let result = fw.authenticate("jwt", Credential::jwt(token)).await?;
334    /// match result {
335    ///     AuthResult::Success(token) => println!("authenticated"),
336    ///     AuthResult::MfaRequired(challenge) => println!("MFA needed"),
337    ///     AuthResult::Failure(msg) => eprintln!("failed: {msg}"),
338    /// }
339    /// ```
340    pub async fn authenticate(
341        &self,
342        method_name: &str,
343        credential: Credential,
344    ) -> Result<AuthResult> {
345        self.authenticate_with_metadata(method_name, credential, CredentialMetadata::new())
346            .await
347    }
348
349    /// Authenticate a user with the specified method and additional metadata.
350    ///
351    /// Metadata can carry client IP, user-agent, and other contextual information
352    /// for adaptive risk scoring and audit logging.
353    ///
354    /// # Example
355    /// ```rust,ignore
356    /// let mut meta = CredentialMetadata::new();
357    /// meta.client_ip = Some("203.0.113.1".to_string());
358    /// let result = fw.authenticate_with_metadata("jwt", credential, meta).await?;
359    /// ```
360    pub async fn authenticate_with_metadata(
361        &self,
362        method_name: &str,
363        credential: Credential,
364        metadata: CredentialMetadata,
365    ) -> Result<AuthResult> {
366        use std::time::Instant;
367        use tokio::time::{Duration as TokioDuration, sleep};
368
369        let start_time = Instant::now();
370
371        if !self.initialized {
372            return Err(AuthError::internal("Framework not initialized"));
373        }
374
375        // Perform the authentication logic
376        let result = self
377            .authenticate_internal(method_name, credential, metadata)
378            .await;
379
380        // Ensure minimum response time to prevent timing attacks
381        let min_duration = TokioDuration::from_millis(100);
382        let elapsed = start_time.elapsed();
383        if elapsed < min_duration {
384            sleep(min_duration - elapsed).await;
385        }
386
387        result
388    }
389
390    /// Internal authentication logic without timing protection
391    async fn authenticate_internal(
392        &self,
393        method_name: &str,
394        credential: Credential,
395        metadata: CredentialMetadata,
396    ) -> Result<AuthResult> {
397        // Check rate limiting
398        if let Some(ref rate_limiter) = self.rate_limiter {
399            let rate_key = format!(
400                "auth:{}:{}",
401                method_name,
402                metadata.client_ip.as_deref().unwrap_or("unknown")
403            );
404
405            if !rate_limiter.is_allowed(&rate_key) {
406                warn!(
407                    "Rate limit exceeded for method '{}' from IP {:?}",
408                    method_name, metadata.client_ip
409                );
410                return Err(AuthError::rate_limit("Too many authentication attempts"));
411            }
412        }
413
414        if method_name == "jwt" {
415            return match credential {
416                Credential::Jwt { token } | Credential::Bearer { token } => {
417                    self.authenticate_jwt_builtin(&token, &metadata, "jwt")
418                        .await
419                }
420                _ => Ok(AuthResult::Failure(
421                    "JWT authentication expects Credential::jwt or Credential::bearer".to_string(),
422                )),
423            };
424        }
425
426        if matches!(method_name, "api_key" | "api-key") {
427            return match credential {
428                Credential::ApiKey { key } => {
429                    self.authenticate_api_key_builtin(&key, &metadata, "api_key")
430                        .await
431                }
432                _ => Ok(AuthResult::Failure(
433                    "API key authentication expects Credential::api_key".to_string(),
434                )),
435            };
436        }
437
438        if method_name == "oauth2" {
439            return self
440                .authenticate_oauth2_builtin(credential, &metadata)
441                .await;
442        }
443
444        // Get the authentication method
445        let method = self.methods.get(method_name).ok_or_else(|| {
446            AuthError::auth_method(method_name, "Authentication method not found".to_string())
447        })?;
448
449        // Log authentication attempt
450        debug!(
451            "Authentication attempt with method '{}' for credential: {}",
452            method_name,
453            credential.safe_display()
454        );
455
456        // Perform authentication
457        let result = method.authenticate(credential, metadata.clone()).await?;
458
459        // Handle the result
460        match &result {
461            MethodResult::Success(token) => {
462                info!(
463                    "Authentication successful for user '{}' with method '{}'",
464                    token.user_id, method_name
465                );
466
467                // Store token
468                self.storage.store_token(token).await?;
469
470                // Log audit event
471                self.log_audit_event("auth_success", &token.user_id, method_name, &metadata)
472                    .await;
473
474                Ok(AuthResult::Success(token.clone()))
475            }
476
477            MethodResult::MfaRequired(challenge) => {
478                info!(
479                    "MFA required for user '{}' with method '{}'",
480                    challenge.user_id, method_name
481                );
482
483                // Store MFA challenge
484                self.mfa_manager
485                    .store_challenge((**challenge).clone())
486                    .await?;
487
488                // Log audit event
489                self.log_audit_event("mfa_required", &challenge.user_id, method_name, &metadata)
490                    .await;
491
492                Ok(AuthResult::MfaRequired(challenge.clone()))
493            }
494
495            MethodResult::Failure { reason } => {
496                warn!(
497                    "Authentication failed for method '{}': {}",
498                    method_name, reason
499                );
500
501                // Log audit event
502                self.log_audit_event("auth_failure", "unknown", method_name, &metadata)
503                    .await;
504
505                Ok(AuthResult::Failure(reason.clone()))
506            }
507        }
508    }
509
510    async fn authenticate_jwt_builtin(
511        &self,
512        token: &str,
513        metadata: &CredentialMetadata,
514        auth_method: &str,
515    ) -> Result<AuthResult> {
516        if token.is_empty() {
517            return Ok(AuthResult::Failure("JWT token cannot be empty".to_string()));
518        }
519
520        match self.token_manager.validate_jwt_token(token) {
521            Ok(claims) => {
522                let token =
523                    Self::build_validated_jwt_auth_token(token, claims, metadata, auth_method);
524                Ok(AuthResult::Success(Box::new(token)))
525            }
526            Err(error) => {
527                if let Some(reason) = Self::credential_failure_reason(&error) {
528                    Ok(AuthResult::Failure(reason))
529                } else {
530                    Err(error)
531                }
532            }
533        }
534    }
535
536    async fn authenticate_api_key_builtin(
537        &self,
538        api_key: &str,
539        metadata: &CredentialMetadata,
540        auth_method: &str,
541    ) -> Result<AuthResult> {
542        if api_key.is_empty() {
543            return Ok(AuthResult::Failure("API key cannot be empty".to_string()));
544        }
545
546        match self.user_manager.validate_api_key(api_key).await {
547            Ok(user) => {
548                let scopes: Vec<String> = if user.roles.is_empty() {
549                    vec!["api_user".to_string()]
550                } else {
551                    user.roles.to_vec()
552                };
553                let mut token = self
554                    .token_manager
555                    .create_auth_token(&user.id, scopes.clone(), auth_method, None)?
556                    .with_roles(user.roles)
557                    .with_scopes(scopes);
558                token.metadata.issued_ip = metadata.client_ip.clone();
559                token.metadata.user_agent = metadata.user_agent.clone();
560                Ok(AuthResult::Success(Box::new(token)))
561            }
562            Err(error) => {
563                if let Some(reason) = Self::credential_failure_reason(&error) {
564                    Ok(AuthResult::Failure(reason))
565                } else {
566                    Err(error)
567                }
568            }
569        }
570    }
571
572    async fn authenticate_oauth2_builtin(
573        &self,
574        credential: Credential,
575        metadata: &CredentialMetadata,
576    ) -> Result<AuthResult> {
577        match credential {
578            Credential::OAuth {
579                authorization_code, ..
580            } => {
581                if authorization_code.is_empty() {
582                    return Ok(AuthResult::Failure(
583                        "OAuth authorization code cannot be empty".to_string(),
584                    ));
585                }
586                Ok(AuthResult::Failure(
587                    "OAuth 2.0 authorization codes must be exchanged through an OAuth provider or server endpoint before authentication completes"
588                        .to_string(),
589                ))
590            }
591            Credential::OAuthRefresh { refresh_token } => {
592                if refresh_token.is_empty() {
593                    return Ok(AuthResult::Failure(
594                        "OAuth refresh token cannot be empty".to_string(),
595                    ));
596                }
597                Ok(AuthResult::Failure(
598                    "OAuth 2.0 refresh tokens must be exchanged through an OAuth provider or server endpoint before authentication completes"
599                        .to_string(),
600                ))
601            }
602            Credential::Jwt { token }
603            | Credential::Bearer { token }
604            | Credential::OpenIdConnect { id_token: token, .. } => {
605                self.authenticate_jwt_builtin(&token, metadata, "oauth2").await
606            }
607            _ => Ok(AuthResult::Failure(
608                "OAuth2 authentication expects Credential::oauth_code, Credential::oauth_refresh, Credential::jwt, Credential::bearer, or Credential::openid_connect"
609                    .to_string(),
610            )),
611        }
612    }
613
614    fn build_validated_jwt_auth_token(
615        raw_token: &str,
616        claims: crate::tokens::JwtClaims,
617        metadata: &CredentialMetadata,
618        auth_method: &str,
619    ) -> AuthToken {
620        let crate::tokens::JwtClaims {
621            sub,
622            iss,
623            exp,
624            iat,
625            jti,
626            scope,
627            permissions,
628            roles,
629            client_id,
630            ..
631        } = claims;
632
633        let now = chrono::Utc::now();
634        let issued_at = chrono::DateTime::<chrono::Utc>::from_timestamp(iat, 0).unwrap_or(now);
635        let expires_at = chrono::DateTime::<chrono::Utc>::from_timestamp(exp, 0)
636            .unwrap_or(now + chrono::Duration::hours(1));
637        let lifetime = (expires_at - now)
638            .to_std()
639            .unwrap_or_else(|_| std::time::Duration::from_secs(1));
640        let scopes = if scope.trim().is_empty() {
641            Vec::new()
642        } else {
643            scope.split_whitespace().map(str::to_string).collect()
644        };
645
646        let mut token = AuthToken::new(sub.clone(), raw_token.to_string(), lifetime, auth_method)
647            .with_scopes(scopes)
648            .with_permissions(permissions.unwrap_or_default())
649            .with_roles(roles.unwrap_or_default());
650        token.token_id = jti;
651        token.subject = Some(sub);
652        token.issuer = Some(iss);
653        token.issued_at = issued_at;
654        token.expires_at = expires_at;
655        token.metadata.issued_ip = metadata.client_ip.clone();
656        token.metadata.user_agent = metadata.user_agent.clone();
657        if let Some(client_id) = client_id {
658            token.client_id = Some(client_id);
659        }
660        token
661    }
662
663    fn credential_failure_reason(error: &AuthError) -> Option<String> {
664        match error {
665            AuthError::Token(_) | AuthError::Jwt(_) => Some(error.to_string()),
666            AuthError::Validation { message } => Some(message.clone()),
667            AuthError::UserNotFound => Some("User not found".to_string()),
668            _ => None,
669        }
670    }
671
672    /// Complete multi-factor authentication.
673    ///
674    /// # Example
675    /// ```rust,ignore
676    /// let token = fw.complete_mfa(challenge, "123456").await?;
677    /// ```
678    pub async fn complete_mfa(&self, challenge: MfaChallenge, mfa_code: &str) -> Result<AuthToken> {
679        debug!("Completing MFA for challenge '{}'", challenge.id);
680
681        // Check if challenge exists and is valid
682        let stored_challenge = self
683            .mfa_manager
684            .get_challenge(&challenge.id)
685            .await?
686            .ok_or(MfaError::ChallengeExpired)?;
687
688        if stored_challenge.is_expired() {
689            self.mfa_manager.remove_challenge(&challenge.id).await?;
690            return Err(MfaError::ChallengeExpired.into());
691        }
692
693        // Verify MFA code based on challenge type
694        let is_valid = match &stored_challenge.mfa_type {
695            crate::methods::MfaType::Totp => {
696                self.mfa_manager
697                    .totp
698                    .verify_code(&stored_challenge.user_id, mfa_code)
699                    .await?
700            }
701            crate::methods::MfaType::Sms { .. } => {
702                self.mfa_manager
703                    .sms
704                    .verify_code(&challenge.id, mfa_code)
705                    .await?
706            }
707            crate::methods::MfaType::Email { .. } => {
708                self.mfa_manager
709                    .email
710                    .verify_code(&challenge.id, mfa_code)
711                    .await?
712            }
713            crate::methods::MfaType::BackupCode => {
714                self.mfa_manager
715                    .backup_codes
716                    .verify_code(&stored_challenge.user_id, mfa_code)
717                    .await?
718            }
719            _ => false,
720        };
721
722        if !is_valid {
723            return Err(MfaError::InvalidCode.into());
724        }
725
726        // Remove the challenge
727        self.mfa_manager.remove_challenge(&challenge.id).await?;
728
729        // Look up user roles from storage
730        let user_key = format!("user:{}", challenge.user_id);
731        let scopes = if let Ok(Some(data)) = self.storage.get_kv(&user_key).await {
732            serde_json::from_slice::<serde_json::Value>(&data)
733                .ok()
734                .and_then(|v| {
735                    v.get("roles").and_then(|r| {
736                        r.as_array().map(|arr| {
737                            arr.iter()
738                                .filter_map(|v| v.as_str().map(String::from))
739                                .collect::<Vec<_>>()
740                        })
741                    })
742                })
743                .unwrap_or_else(|| vec!["user".to_string()])
744        } else {
745            vec!["user".to_string()]
746        };
747
748        // Create authentication token
749        let token =
750            self.token_manager
751                .create_auth_token(&challenge.user_id, scopes, "mfa", None)?;
752
753        // Store the token
754        self.storage.store_token(&token).await?;
755
756        info!(
757            "MFA completed successfully for user '{}'",
758            challenge.user_id
759        );
760
761        Ok(token)
762    }
763
764    /// Validate a token.
765    ///
766    /// # Example
767    /// ```rust,ignore
768    /// let valid = fw.validate_token(&token).await?;
769    /// ```
770    pub async fn validate_token(&self, token: &AuthToken) -> Result<bool> {
771        if !self.initialized {
772            return Err(AuthError::internal("Framework not initialized"));
773        }
774
775        // Check basic token validity
776        if !token.is_valid() {
777            return Ok(false);
778        }
779
780        // Validate with token manager
781        self.token_manager.validate_auth_token(token)?;
782
783        // Check if token exists in storage
784        if let Some(stored_token) = self.storage.get_token(&token.token_id).await? {
785            // Update last used time
786            let mut updated_token = stored_token;
787            updated_token.mark_used();
788            self.storage.update_token(&updated_token).await?;
789
790            Ok(true)
791        } else {
792            Ok(false)
793        }
794    }
795
796    /// Get user information from a token.
797    ///
798    /// # Example
799    /// ```rust,ignore
800    /// let info = fw.get_user_info(&token).await?;
801    /// println!("username: {}", info.username);
802    /// ```
803    pub async fn get_user_info(&self, token: &AuthToken) -> Result<UserInfo> {
804        if !self.validate_token(token).await? {
805            return Err(AuthError::auth_method("token", "Invalid token".to_string()));
806        }
807
808        self.user_manager.get_user_info(&token.user_id).await
809    }
810
811    /// Check if a token has a specific permission.
812    ///
813    /// # Example
814    /// ```rust,ignore
815    /// let allowed = fw.check_permission(&token, "read", "users").await?;
816    /// ```
817    pub async fn check_permission(
818        &self,
819        token: &AuthToken,
820        action: &str,
821        resource: &str,
822    ) -> Result<bool> {
823        if !self.validate_token(token).await? {
824            return Ok(false);
825        }
826
827        let permission = Permission::new(action, resource);
828        let mut checker = self.permission_checker.write().await;
829        checker.check_token_permission(token, &permission)
830    }
831
832    /// Get the token manager.
833    ///
834    /// # Example
835    /// ```rust,ignore
836    /// let tm = fw.token_manager();
837    /// ```
838    pub fn token_manager(&self) -> &TokenManager {
839        &self.token_manager
840    }
841
842    /// Get the MFA manager.
843    ///
844    /// # Example
845    /// ```rust,ignore
846    /// let mfa = fw.mfa_manager();
847    /// ```
848    pub fn mfa_manager(&self) -> &MfaManager {
849        &self.mfa_manager
850    }
851
852    /// Get the session manager.
853    ///
854    /// # Example
855    /// ```rust,ignore
856    /// let sm = fw.session_manager();
857    /// ```
858    pub fn session_manager(&self) -> &SessionManager {
859        &self.session_manager
860    }
861
862    /// Get the user manager.
863    ///
864    /// # Example
865    /// ```rust,ignore
866    /// let um = fw.user_manager();
867    /// ```
868    pub fn user_manager(&self) -> &UserManager {
869        &self.user_manager
870    }
871
872    /// Initiate SMS challenge (uses SMSKit).
873    ///
874    /// # Example
875    /// ```rust,ignore
876    /// let challenge_id = fw.initiate_sms_challenge("user-1").await?;
877    /// ```
878    pub async fn initiate_sms_challenge(&self, user_id: &str) -> Result<String> {
879        self.mfa_manager.sms.initiate_challenge(user_id).await
880    }
881
882    /// Send SMS code (uses SMSKit).
883    ///
884    /// # Example
885    /// ```rust,ignore
886    /// fw.send_sms_code(&challenge_id, "+1234567890").await?;
887    /// ```
888    pub async fn send_sms_code(&self, challenge_id: &str, phone_number: &str) -> Result<()> {
889        self.mfa_manager
890            .sms
891            .send_code(challenge_id, phone_number)
892            .await
893    }
894
895    /// Generate SMS code (uses SMSKit).
896    ///
897    /// # Example
898    /// ```rust,ignore
899    /// let code = fw.generate_sms_code(&challenge_id).await?;
900    /// ```
901    pub async fn generate_sms_code(&self, challenge_id: &str) -> Result<String> {
902        self.mfa_manager.sms.generate_code(challenge_id).await
903    }
904
905    /// Verify SMS code (uses SMSKit).
906    ///
907    /// # Example
908    /// ```rust,ignore
909    /// let ok = fw.verify_sms_code(&challenge_id, "123456").await?;
910    /// ```
911    pub async fn verify_sms_code(&self, challenge_id: &str, code: &str) -> Result<bool> {
912        self.mfa_manager.sms.verify_code(challenge_id, code).await
913    }
914
915    /// Clean up expired data (sessions, MFA challenges, rate limiter entries).
916    ///
917    /// # Example
918    /// ```rust,ignore
919    /// fw.cleanup_expired_data().await?;
920    /// ```
921    pub async fn cleanup_expired_data(&self) -> Result<()> {
922        debug!("Cleaning up expired data");
923
924        // Clean up storage
925        self.storage.cleanup_expired().await?;
926
927        // Clean up MFA challenges
928        self.mfa_manager.cleanup_expired_challenges().await?;
929
930        // Clean up sessions
931        self.session_manager.cleanup_expired_sessions().await?;
932
933        // Clean up rate limiter
934        if let Some(ref rate_limiter) = self.rate_limiter {
935            let _ = rate_limiter.cleanup().ok();
936        }
937
938        Ok(())
939    }
940
941    /// Get authentication framework statistics.
942    ///
943    /// # Example
944    /// ```rust,ignore
945    /// let stats = fw.get_stats().await?;
946    /// println!("methods: {:?}", stats.registered_methods);
947    /// ```
948    pub async fn get_stats(&self) -> Result<AuthStats> {
949        let mut stats = AuthStats::default();
950
951        for method in self.methods.keys() {
952            stats.registered_methods.push(method.clone());
953        }
954
955        stats.active_mfa_challenges = self.mfa_manager.get_active_challenge_count().await as u64;
956
957        // Count tokens from storage as a proxy for tokens_issued
958        stats.tokens_issued = self.storage.count_active_sessions().await.unwrap_or(0) as u64;
959
960        Ok(stats)
961    }
962
963    /// Log an audit event
964    async fn log_audit_event(
965        &self,
966        event_type: &str,
967        user_id: &str,
968        method: &str,
969        metadata: &CredentialMetadata,
970    ) {
971        if self.config.audit.enabled {
972            let should_log = match event_type {
973                "auth_success" => self.config.audit.log_success,
974                "auth_failure" => self.config.audit.log_failures,
975                "mfa_required" => self.config.audit.log_success,
976                _ => true,
977            };
978
979            if should_log {
980                info!(
981                    target: "auth_audit",
982                    event_type = event_type,
983                    user_id = user_id,
984                    method = method,
985                    client_ip = metadata.client_ip.as_deref().unwrap_or("unknown"),
986                    user_agent = metadata.user_agent.as_deref().unwrap_or("unknown"),
987                    timestamp = chrono::Utc::now().to_rfc3339(),
988                    "Authentication event"
989                );
990            }
991        }
992    }
993}
994
995/// Authentication framework statistics
996#[derive(Debug, Clone, Default)]
997pub struct AuthStats {
998    /// Number of registered authentication methods
999    pub registered_methods: Vec<String>,
1000
1001    /// Number of active MFA challenges
1002    pub active_mfa_challenges: u64,
1003
1004    /// Number of tokens issued (this would need proper tracking)
1005    pub tokens_issued: u64,
1006
1007    /// Number of authentication attempts (this would need proper tracking)
1008    pub auth_attempts: u64,
1009}
1010
1011#[cfg(test)]
1012mod tests {
1013    use super::*;
1014    use crate::authentication::credentials::Credential;
1015    use crate::config::{AuthConfig, SecurityConfig};
1016    use std::time::Duration;
1017
1018    async fn initialized_framework(config: AuthConfig) -> AuthFramework {
1019        let mut framework = AuthFramework::new(config).expect("test config should be valid");
1020        framework
1021            .initialize()
1022            .await
1023            .expect("framework initialization should succeed");
1024        framework
1025    }
1026
1027    fn test_config() -> AuthConfig {
1028        AuthConfig::new().secret("test_secret_key_32_bytes_long!!!!")
1029    }
1030
1031    #[tokio::test]
1032    async fn test_modular_framework_initialization() {
1033        let config = AuthConfig::new().security(SecurityConfig {
1034            min_password_length: 8,
1035            require_password_complexity: false,
1036            password_hash_algorithm: crate::config::PasswordHashAlgorithm::Bcrypt,
1037            jwt_algorithm: crate::config::JwtAlgorithm::HS256,
1038            secret_key: Some("test_secret_key_32_bytes_long!!!!".to_string()),
1039            secure_cookies: false,
1040            cookie_same_site: crate::config::CookieSameSite::Lax,
1041            csrf_protection: false,
1042            session_timeout: Duration::from_secs(3600),
1043            previous_secret_key: None,
1044        });
1045        let mut framework = AuthFramework::new(config).expect("test config should be valid");
1046
1047        assert!(framework.initialize().await.is_ok());
1048        assert!(framework.initialized);
1049    }
1050
1051    #[tokio::test]
1052    async fn test_mfa_manager_access() {
1053        let config = AuthConfig::new().security(SecurityConfig {
1054            min_password_length: 8,
1055            require_password_complexity: false,
1056            password_hash_algorithm: crate::config::PasswordHashAlgorithm::Bcrypt,
1057            jwt_algorithm: crate::config::JwtAlgorithm::HS256,
1058            secret_key: Some("test_secret_key_32_bytes_long!!!!".to_string()),
1059            secure_cookies: false,
1060            cookie_same_site: crate::config::CookieSameSite::Lax,
1061            csrf_protection: false,
1062            session_timeout: Duration::from_secs(3600),
1063            previous_secret_key: None,
1064        });
1065        let framework = AuthFramework::new(config).expect("test config should be valid");
1066
1067        // Test that we can access specialized managers
1068        let _mfa_manager = framework.mfa_manager();
1069        let _session_manager = framework.session_manager();
1070        let _user_manager = framework.user_manager();
1071    }
1072
1073    #[tokio::test]
1074    async fn test_authenticate_with_metadata_enforces_minimum_duration() {
1075        let framework = initialized_framework(test_config()).await;
1076        let start = std::time::Instant::now();
1077
1078        let result = framework
1079            .authenticate_with_metadata(
1080                "oauth2",
1081                Credential::oauth_refresh(""),
1082                CredentialMetadata::new(),
1083            )
1084            .await
1085            .expect("empty refresh token should return failure, not error");
1086
1087        assert!(
1088            start.elapsed() >= std::time::Duration::from_millis(90),
1089            "timing floor should add a noticeable delay"
1090        );
1091        assert!(matches!(result, AuthResult::Failure(_)));
1092    }
1093
1094    #[tokio::test]
1095    async fn test_authenticate_oauth2_refresh_empty_returns_failure() {
1096        let framework = initialized_framework(test_config()).await;
1097
1098        let result = framework
1099            .authenticate("oauth2", Credential::oauth_refresh(""))
1100            .await
1101            .expect("empty refresh token should return failure, not error");
1102
1103        match result {
1104            AuthResult::Failure(reason) => {
1105                assert!(reason.contains("refresh token cannot be empty"));
1106            }
1107            _ => panic!("expected failure result for empty OAuth refresh token"),
1108        }
1109    }
1110
1111    #[tokio::test]
1112    async fn test_authenticate_oauth2_openid_connect_routes_to_jwt_validation() {
1113        let framework = initialized_framework(test_config()).await;
1114        let jwt = framework
1115            .token_manager()
1116            .create_jwt_token("oidc-user", vec!["openid".to_string()], None)
1117            .expect("jwt creation should succeed");
1118
1119        let result = framework
1120            .authenticate("oauth2", Credential::openid_connect(jwt))
1121            .await
1122            .expect("valid OIDC credential should authenticate");
1123
1124        match result {
1125            AuthResult::Success(token) => {
1126                assert_eq!(token.user_id, "oidc-user");
1127                assert_eq!(token.auth_method, "oauth2");
1128            }
1129            _ => panic!("expected success result for OpenID Connect credential"),
1130        }
1131    }
1132
1133    #[tokio::test]
1134    async fn test_authenticate_oauth2_unsupported_credential_returns_failure() {
1135        let framework = initialized_framework(test_config()).await;
1136
1137        let result = framework
1138            .authenticate("oauth2", Credential::password("alice", "secret"))
1139            .await
1140            .expect("unsupported credential should return failure, not error");
1141
1142        match result {
1143            AuthResult::Failure(reason) => {
1144                assert!(reason.contains("OAuth2 authentication expects"));
1145            }
1146            _ => panic!("expected failure result for unsupported OAuth2 credential"),
1147        }
1148    }
1149
1150    #[tokio::test]
1151    async fn test_rate_limiting_without_client_ip_uses_shared_unknown_bucket() {
1152        let config = AuthConfig::new()
1153            .secret("test_secret_key_32_bytes_long!!!!")
1154            .rate_limiting(crate::config::RateLimitConfig {
1155                enabled: true,
1156                max_requests: 1,
1157                window: Duration::from_secs(60),
1158                burst: 0,
1159            });
1160        let framework = initialized_framework(config).await;
1161
1162        let first = framework
1163            .authenticate_with_metadata(
1164                "oauth2",
1165                Credential::oauth_code("first-code"),
1166                CredentialMetadata::new(),
1167            )
1168            .await
1169            .expect("first request should pass rate limiting");
1170        assert!(matches!(first, AuthResult::Failure(_)));
1171
1172        let second = framework
1173            .authenticate_with_metadata(
1174                "oauth2",
1175                Credential::oauth_code("second-code"),
1176                CredentialMetadata::new(),
1177            )
1178            .await;
1179        assert!(second.is_err(), "second request should be rate limited");
1180        assert!(second
1181            .unwrap_err()
1182            .to_string()
1183            .contains("Too many authentication attempts"));
1184    }
1185
1186    #[tokio::test]
1187    async fn test_complete_mfa_missing_challenge_returns_expired_error() {
1188        let framework = initialized_framework(test_config()).await;
1189        let challenge = MfaChallenge::new(
1190            crate::methods::MfaType::BackupCode,
1191            "user-123",
1192            Duration::from_secs(60),
1193        );
1194
1195        let result = framework.complete_mfa(challenge, "123456").await;
1196        assert!(result.is_err());
1197        assert!(result.unwrap_err().to_string().contains("expired"));
1198    }
1199
1200    #[tokio::test]
1201    async fn test_complete_mfa_expired_challenge_is_removed() {
1202        let framework = initialized_framework(test_config()).await;
1203        let mut challenge = MfaChallenge::new(
1204            crate::methods::MfaType::BackupCode,
1205            "user-123",
1206            Duration::from_secs(60),
1207        );
1208        challenge.expires_at = chrono::Utc::now() - chrono::Duration::seconds(1);
1209        framework
1210            .mfa_manager
1211            .store_challenge(challenge.clone())
1212            .await
1213            .expect("storing challenge should succeed");
1214
1215        let result = framework.complete_mfa(challenge.clone(), "123456").await;
1216        assert!(result.is_err());
1217        assert!(result.unwrap_err().to_string().contains("expired"));
1218        assert!(framework
1219            .mfa_manager
1220            .get_challenge(&challenge.id)
1221            .await
1222            .expect("challenge lookup should succeed")
1223            .is_none());
1224    }
1225
1226    #[tokio::test]
1227    async fn test_validate_token_returns_false_for_expired_token() {
1228        let framework = initialized_framework(test_config()).await;
1229        let mut token = framework
1230            .token_manager()
1231            .create_auth_token("user-123", vec!["read".to_string()], "jwt", None)
1232            .expect("token creation should succeed");
1233        token.expires_at = chrono::Utc::now() - chrono::Duration::seconds(1);
1234
1235        let valid = framework
1236            .validate_token(&token)
1237            .await
1238            .expect("expired token should return false, not error");
1239        assert!(!valid);
1240    }
1241
1242    #[tokio::test]
1243    async fn test_validate_token_returns_false_when_not_in_storage() {
1244        let framework = initialized_framework(test_config()).await;
1245        let token = framework
1246            .token_manager()
1247            .create_auth_token("user-123", vec!["read".to_string()], "jwt", None)
1248            .expect("token creation should succeed");
1249
1250        let valid = framework
1251            .validate_token(&token)
1252            .await
1253            .expect("missing stored token should return false, not error");
1254        assert!(!valid);
1255    }
1256}