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
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);
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
54pub mod mfa;
55pub mod session_manager;
56pub mod user_manager;
57
58use crate::config::AuthConfig;
59use crate::authentication::credentials::{Credential, CredentialMetadata};
60use crate::errors::{AuthError, MfaError, Result};
61use crate::methods::{AuthMethod, AuthMethodEnum, MethodResult, MfaChallenge};
62use crate::permissions::{Permission, PermissionChecker};
63use crate::storage::{AuthStorage, MemoryStorage};
64use crate::tokens::{AuthToken, TokenManager};
65use crate::utils::rate_limit::RateLimiter;
66use std::collections::HashMap;
67use std::sync::Arc;
68use tokio::sync::RwLock;
69use tracing::{debug, error, info, warn};
70
71pub use mfa::MfaManager;
72pub use session_manager::SessionManager;
73pub use user_manager::{UserInfo, UserManager};
74
75/// Result of an authentication attempt
76#[derive(Debug, Clone)]
77pub enum AuthResult {
78    /// Authentication was successful
79    Success(Box<AuthToken>),
80
81    /// Multi-factor authentication is required
82    MfaRequired(Box<MfaChallenge>),
83
84    /// Authentication failed
85    Failure(String),
86}
87
88/// Main authentication framework - now focused and modular
89pub struct AuthFramework {
90    /// Configuration
91    config: AuthConfig,
92
93    /// Registered authentication methods
94    methods: HashMap<String, AuthMethodEnum>,
95
96    /// Token manager
97    token_manager: TokenManager,
98
99    /// Storage backend
100    storage: Arc<dyn AuthStorage>,
101
102    /// Permission checker
103    permission_checker: Arc<RwLock<PermissionChecker>>,
104
105    /// Rate limiter
106    rate_limiter: Option<RateLimiter>,
107
108    /// MFA manager
109    mfa_manager: MfaManager,
110
111    /// Session manager
112    session_manager: SessionManager,
113
114    /// User manager
115    user_manager: UserManager,
116
117    /// Framework initialization state
118    initialized: bool,
119}
120
121impl AuthFramework {
122    /// Create a new authentication framework
123    pub fn new(config: AuthConfig) -> Self {
124        // Validate configuration
125        if let Err(e) = config.validate() {
126            panic!("Invalid configuration: {}", e);
127        }
128
129        // Create token manager
130        let token_manager = if let Some(secret) = &config.security.secret_key {
131            if secret.len() < 32 {
132                eprintln!(
133                    "WARNING: JWT secret is shorter than 32 characters. Consider using a longer secret for better security."
134                );
135            }
136            TokenManager::new_hmac(secret.as_bytes(), "auth-framework", "auth-framework")
137        } else if let Some(secret) = &config.secret {
138            if secret.len() < 32 {
139                eprintln!(
140                    "WARNING: JWT secret is shorter than 32 characters. Consider using a longer secret for better security."
141                );
142            }
143            TokenManager::new_hmac(secret.as_bytes(), "auth-framework", "auth-framework")
144        } else if let Ok(jwt_secret) = std::env::var("JWT_SECRET") {
145            if jwt_secret.len() < 32 {
146                eprintln!(
147                    "WARNING: JWT_SECRET is shorter than 32 characters. Consider using a longer secret for better security."
148                );
149            }
150            TokenManager::new_hmac(jwt_secret.as_bytes(), "auth-framework", "auth-framework")
151        } else {
152            panic!(
153                "JWT secret not set! Please set JWT_SECRET env variable or provide in config.\n\
154                   For security reasons, no default secret is provided.\n\
155                   Generate a secure secret with: openssl rand -base64 32"
156            );
157        };
158
159        // Create storage backend
160        let storage: Arc<dyn AuthStorage> = match &config.storage {
161            #[cfg(feature = "redis-storage")]
162            crate::config::StorageConfig::Redis { url, key_prefix } => Arc::new(
163                crate::storage::RedisStorage::new(url, key_prefix).unwrap_or_else(|e| {
164                    panic!("Failed to create Redis storage: {}", e);
165                }),
166            ),
167            _ => Arc::new(MemoryStorage::new()),
168        };
169
170        // Create rate limiter if enabled
171        let rate_limiter = if config.rate_limiting.enabled {
172            Some(RateLimiter::new(
173                config.rate_limiting.max_requests,
174                config.rate_limiting.window,
175            ))
176        } else {
177            None
178        };
179
180        // Create specialized managers
181        let mfa_manager = MfaManager::new(storage.clone());
182        let session_manager = SessionManager::new(storage.clone());
183        let user_manager = UserManager::new(storage.clone());
184
185        Self {
186            config,
187            methods: HashMap::new(),
188            token_manager,
189            storage,
190            permission_checker: Arc::new(RwLock::new(PermissionChecker::new())),
191            rate_limiter,
192            mfa_manager,
193            session_manager,
194            user_manager,
195            initialized: false,
196        }
197    }
198
199    /// Create a new framework with SMSKit configuration
200    #[cfg(feature = "smskit")]
201    pub fn new_with_smskit_config(
202        config: AuthConfig,
203        smskit_config: crate::auth_modular::mfa::SmsKitConfig,
204    ) -> Result<Self> {
205        // First create the framework normally
206        let mut framework = Self::new(config);
207
208        // Then replace the MFA manager with one configured for SMSKit
209        framework.mfa_manager = crate::auth_modular::mfa::MfaManager::new_with_smskit_config(
210            framework.storage.clone(),
211            smskit_config,
212        )?;
213
214        Ok(framework)
215    }
216
217    /// Register an authentication method
218    pub fn register_method(&mut self, name: impl Into<String>, method: AuthMethodEnum) {
219        let name = name.into();
220        info!("Registering authentication method: {}", name);
221
222        // Validate method configuration
223        if let Err(e) = method.validate_config() {
224            error!("Method '{}' configuration validation failed: {}", name, e);
225            return;
226        }
227
228        self.methods.insert(name, method);
229    }
230
231    /// Initialize the authentication framework
232    pub async fn initialize(&mut self) -> Result<()> {
233        if self.initialized {
234            return Ok(());
235        }
236
237        info!("Initializing authentication framework");
238
239        // Initialize permission checker with default roles
240        {
241            let mut checker = self.permission_checker.write().await;
242            checker.create_default_roles();
243        }
244
245        // Perform any necessary cleanup
246        self.cleanup_expired_data().await?;
247
248        self.initialized = true;
249        info!("Authentication framework initialized successfully");
250
251        Ok(())
252    }
253
254    /// Authenticate a user with the specified method
255    pub async fn authenticate(
256        &self,
257        method_name: &str,
258        credential: Credential,
259    ) -> Result<AuthResult> {
260        self.authenticate_with_metadata(method_name, credential, CredentialMetadata::new())
261            .await
262    }
263
264    /// Authenticate a user with the specified method and metadata
265    pub async fn authenticate_with_metadata(
266        &self,
267        method_name: &str,
268        credential: Credential,
269        metadata: CredentialMetadata,
270    ) -> Result<AuthResult> {
271        use std::time::Instant;
272        use tokio::time::{Duration as TokioDuration, sleep};
273
274        let start_time = Instant::now();
275
276        if !self.initialized {
277            return Err(AuthError::internal("Framework not initialized"));
278        }
279
280        // Perform the authentication logic
281        let result = self
282            .authenticate_internal(method_name, credential, metadata)
283            .await;
284
285        // Ensure minimum response time to prevent timing attacks
286        let min_duration = TokioDuration::from_millis(100);
287        let elapsed = start_time.elapsed();
288        if elapsed < min_duration {
289            sleep(min_duration - elapsed).await;
290        }
291
292        result
293    }
294
295    /// Internal authentication logic without timing protection
296    async fn authenticate_internal(
297        &self,
298        method_name: &str,
299        credential: Credential,
300        metadata: CredentialMetadata,
301    ) -> Result<AuthResult> {
302        // Check rate limiting
303        if let Some(ref rate_limiter) = self.rate_limiter {
304            let rate_key = format!(
305                "auth:{}:{}",
306                method_name,
307                metadata.client_ip.as_deref().unwrap_or("unknown")
308            );
309
310            if !rate_limiter.is_allowed(&rate_key) {
311                warn!(
312                    "Rate limit exceeded for method '{}' from IP {:?}",
313                    method_name, metadata.client_ip
314                );
315                return Err(AuthError::rate_limit("Too many authentication attempts"));
316            }
317        }
318
319        // Get the authentication method
320        let method = self.methods.get(method_name).ok_or_else(|| {
321            AuthError::auth_method(method_name, "Authentication method not found".to_string())
322        })?;
323
324        // Log authentication attempt
325        debug!(
326            "Authentication attempt with method '{}' for credential: {}",
327            method_name,
328            credential.safe_display()
329        );
330
331        // Perform authentication
332        let result = method.authenticate(credential, metadata.clone()).await?;
333
334        // Handle the result
335        match &result {
336            MethodResult::Success(token) => {
337                info!(
338                    "Authentication successful for user '{}' with method '{}'",
339                    token.user_id, method_name
340                );
341
342                // Store token
343                self.storage.store_token(token).await?;
344
345                // Log audit event
346                self.log_audit_event("auth_success", &token.user_id, method_name, &metadata)
347                    .await;
348
349                Ok(AuthResult::Success(token.clone()))
350            }
351
352            MethodResult::MfaRequired(challenge) => {
353                info!(
354                    "MFA required for user '{}' with method '{}'",
355                    challenge.user_id, method_name
356                );
357
358                // Store MFA challenge
359                self.mfa_manager
360                    .store_challenge((**challenge).clone())
361                    .await?;
362
363                // Log audit event
364                self.log_audit_event("mfa_required", &challenge.user_id, method_name, &metadata)
365                    .await;
366
367                Ok(AuthResult::MfaRequired(challenge.clone()))
368            }
369
370            MethodResult::Failure { reason } => {
371                warn!(
372                    "Authentication failed for method '{}': {}",
373                    method_name, reason
374                );
375
376                // Log audit event
377                self.log_audit_event("auth_failure", "unknown", method_name, &metadata)
378                    .await;
379
380                Ok(AuthResult::Failure(reason.clone()))
381            }
382        }
383    }
384
385    /// Complete multi-factor authentication
386    pub async fn complete_mfa(&self, challenge: MfaChallenge, mfa_code: &str) -> Result<AuthToken> {
387        debug!("Completing MFA for challenge '{}'", challenge.id);
388
389        // Check if challenge exists and is valid
390        let stored_challenge = self
391            .mfa_manager
392            .get_challenge(&challenge.id)
393            .await?
394            .ok_or(MfaError::ChallengeExpired)?;
395
396        if stored_challenge.is_expired() {
397            self.mfa_manager.remove_challenge(&challenge.id).await?;
398            return Err(MfaError::ChallengeExpired.into());
399        }
400
401        // Verify MFA code based on challenge type
402        let is_valid = match &stored_challenge.mfa_type {
403            crate::methods::MfaType::Totp => {
404                self.mfa_manager
405                    .totp
406                    .verify_code(&stored_challenge.user_id, mfa_code)
407                    .await?
408            }
409            crate::methods::MfaType::Sms { .. } => {
410                self.mfa_manager
411                    .sms
412                    .verify_code(&challenge.id, mfa_code)
413                    .await?
414            }
415            crate::methods::MfaType::Email { .. } => {
416                self.mfa_manager
417                    .email
418                    .verify_code(&challenge.id, mfa_code)
419                    .await?
420            }
421            crate::methods::MfaType::BackupCode => {
422                self.mfa_manager
423                    .backup_codes
424                    .verify_code(&stored_challenge.user_id, mfa_code)
425                    .await?
426            }
427            _ => false,
428        };
429
430        if !is_valid {
431            return Err(MfaError::InvalidCode.into());
432        }
433
434        // Remove the challenge
435        self.mfa_manager.remove_challenge(&challenge.id).await?;
436
437        // Create authentication token
438        let token = self.token_manager.create_auth_token(
439            &challenge.user_id,
440            vec![], // Scopes would be determined by user permissions
441            "mfa",
442            None,
443        )?;
444
445        // Store the token
446        self.storage.store_token(&token).await?;
447
448        info!(
449            "MFA completed successfully for user '{}'",
450            challenge.user_id
451        );
452
453        Ok(token)
454    }
455
456    /// Validate a token
457    pub async fn validate_token(&self, token: &AuthToken) -> Result<bool> {
458        if !self.initialized {
459            return Err(AuthError::internal("Framework not initialized"));
460        }
461
462        // Check basic token validity
463        if !token.is_valid() {
464            return Ok(false);
465        }
466
467        // Validate with token manager
468        self.token_manager.validate_auth_token(token)?;
469
470        // Check if token exists in storage
471        if let Some(stored_token) = self.storage.get_token(&token.token_id).await? {
472            // Update last used time
473            let mut updated_token = stored_token;
474            updated_token.mark_used();
475            self.storage.update_token(&updated_token).await?;
476
477            Ok(true)
478        } else {
479            Ok(false)
480        }
481    }
482
483    /// Get user information from a token
484    pub async fn get_user_info(&self, token: &AuthToken) -> Result<UserInfo> {
485        if !self.validate_token(token).await? {
486            return Err(AuthError::auth_method("token", "Invalid token".to_string()));
487        }
488
489        self.user_manager.get_user_info(&token.user_id).await
490    }
491
492    /// Check if a token has a specific permission
493    pub async fn check_permission(
494        &self,
495        token: &AuthToken,
496        action: &str,
497        resource: &str,
498    ) -> Result<bool> {
499        if !self.validate_token(token).await? {
500            return Ok(false);
501        }
502
503        let permission = Permission::new(action, resource);
504        let mut checker = self.permission_checker.write().await;
505        checker.check_token_permission(token, &permission)
506    }
507
508    /// Get the token manager
509    pub fn token_manager(&self) -> &TokenManager {
510        &self.token_manager
511    }
512
513    /// Get the MFA manager
514    pub fn mfa_manager(&self) -> &MfaManager {
515        &self.mfa_manager
516    }
517
518    /// Get the session manager
519    pub fn session_manager(&self) -> &SessionManager {
520        &self.session_manager
521    }
522
523    /// Get the user manager
524    pub fn user_manager(&self) -> &UserManager {
525        &self.user_manager
526    }
527
528    /// Initiate SMS challenge (uses SMSKit)
529    pub async fn initiate_sms_challenge(&self, user_id: &str) -> Result<String> {
530        self.mfa_manager.sms.initiate_challenge(user_id).await
531    }
532
533    /// Send SMS code (uses SMSKit)
534    pub async fn send_sms_code(&self, challenge_id: &str, phone_number: &str) -> Result<()> {
535        self.mfa_manager
536            .sms
537            .send_code(challenge_id, phone_number)
538            .await
539    }
540
541    /// Generate SMS code (uses SMSKit)
542    pub async fn generate_sms_code(&self, challenge_id: &str) -> Result<String> {
543        self.mfa_manager.sms.generate_code(challenge_id).await
544    }
545
546    /// Verify SMS code (uses SMSKit)
547    pub async fn verify_sms_code(&self, challenge_id: &str, code: &str) -> Result<bool> {
548        self.mfa_manager.sms.verify_code(challenge_id, code).await
549    }
550
551    /// Clean up expired data
552    pub async fn cleanup_expired_data(&self) -> Result<()> {
553        debug!("Cleaning up expired data");
554
555        // Clean up storage
556        self.storage.cleanup_expired().await?;
557
558        // Clean up MFA challenges
559        self.mfa_manager.cleanup_expired_challenges().await?;
560
561        // Clean up sessions
562        self.session_manager.cleanup_expired_sessions().await?;
563
564        // Clean up rate limiter
565        if let Some(ref rate_limiter) = self.rate_limiter {
566            rate_limiter.cleanup();
567        }
568
569        Ok(())
570    }
571
572    /// Get authentication framework statistics
573    pub async fn get_stats(&self) -> Result<AuthStats> {
574        let mut stats = AuthStats::default();
575
576        for method in self.methods.keys() {
577            stats.registered_methods.push(method.clone());
578        }
579
580        stats.active_mfa_challenges = self.mfa_manager.get_active_challenge_count().await as u64;
581
582        Ok(stats)
583    }
584
585    /// Log an audit event
586    async fn log_audit_event(
587        &self,
588        event_type: &str,
589        user_id: &str,
590        method: &str,
591        metadata: &CredentialMetadata,
592    ) {
593        if self.config.audit.enabled {
594            let should_log = match event_type {
595                "auth_success" => self.config.audit.log_success,
596                "auth_failure" => self.config.audit.log_failures,
597                "mfa_required" => self.config.audit.log_success,
598                _ => true,
599            };
600
601            if should_log {
602                info!(
603                    target: "auth_audit",
604                    event_type = event_type,
605                    user_id = user_id,
606                    method = method,
607                    client_ip = metadata.client_ip.as_deref().unwrap_or("unknown"),
608                    user_agent = metadata.user_agent.as_deref().unwrap_or("unknown"),
609                    timestamp = chrono::Utc::now().to_rfc3339(),
610                    "Authentication event"
611                );
612            }
613        }
614    }
615}
616
617/// Authentication framework statistics
618#[derive(Debug, Clone, Default)]
619pub struct AuthStats {
620    /// Number of registered authentication methods
621    pub registered_methods: Vec<String>,
622
623    /// Number of active MFA challenges
624    pub active_mfa_challenges: u64,
625
626    /// Number of tokens issued (this would need proper tracking)
627    pub tokens_issued: u64,
628
629    /// Number of authentication attempts (this would need proper tracking)
630    pub auth_attempts: u64,
631}
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636    use crate::config::{AuthConfig, SecurityConfig};
637    use std::time::Duration;
638
639    #[tokio::test]
640    async fn test_modular_framework_initialization() {
641        let config = AuthConfig::new().security(SecurityConfig {
642            min_password_length: 8,
643            require_password_complexity: false,
644            password_hash_algorithm: crate::config::PasswordHashAlgorithm::Bcrypt,
645            jwt_algorithm: crate::config::JwtAlgorithm::HS256,
646            secret_key: Some("test_secret_key_32_bytes_long!!!!".to_string()),
647            secure_cookies: false,
648            cookie_same_site: crate::config::CookieSameSite::Lax,
649            csrf_protection: false,
650            session_timeout: Duration::from_secs(3600),
651        });
652        let mut framework = AuthFramework::new(config);
653
654        assert!(framework.initialize().await.is_ok());
655        assert!(framework.initialized);
656    }
657
658    #[tokio::test]
659    async fn test_mfa_manager_access() {
660        let config = AuthConfig::new().security(SecurityConfig {
661            min_password_length: 8,
662            require_password_complexity: false,
663            password_hash_algorithm: crate::config::PasswordHashAlgorithm::Bcrypt,
664            jwt_algorithm: crate::config::JwtAlgorithm::HS256,
665            secret_key: Some("test_secret_key_32_bytes_long!!!!".to_string()),
666            secure_cookies: false,
667            cookie_same_site: crate::config::CookieSameSite::Lax,
668            csrf_protection: false,
669            session_timeout: Duration::from_secs(3600),
670        });
671        let framework = AuthFramework::new(config);
672
673        // Test that we can access specialized managers
674        let _mfa_manager = framework.mfa_manager();
675        let _session_manager = framework.session_manager();
676        let _user_manager = framework.user_manager();
677    }
678}
679
680
681