//! Password Management Service //! //! Secure password hashing and verification using Argon2id algorithm. use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use crate::error::{auth, ControlCenterError, Result}; /// Password strength levels #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum PasswordStrength { /// Weak password (< 8 chars, no complexity) Weak, /// Fair password (8+ chars, limited complexity) Fair, /// Good password (10+ chars, good complexity) Good, /// Strong password (12+ chars, high complexity) Strong, /// Very strong password (16+ chars, full complexity) VeryStrong, } /// Password management service pub struct PasswordService { argon2: Argon2<'static>, } impl PasswordService { /// Create new password service with default Argon2 configuration pub fn new() -> Self { Self { argon2: Argon2::default(), } } /// Hash password using Argon2id /// /// Uses cryptographically secure random salt pub fn hash_password(&self, password: &str) -> Result { let salt = SaltString::generate(&mut OsRng); self.argon2 .hash_password(password.as_bytes(), &salt) .map(|hash| hash.to_string()) .map_err(|e| { ControlCenterError::Auth(auth::AuthError::Authentication(format!( "Password hashing failed: {}", e ))) }) } /// Verify password against hash pub fn verify_password(&self, password: &str, hash: &str) -> Result { let parsed_hash = PasswordHash::new(hash).map_err(|e| { ControlCenterError::Auth(auth::AuthError::Authentication(format!( "Invalid password hash: {}", e ))) })?; Ok(self .argon2 .verify_password(password.as_bytes(), &parsed_hash) .is_ok()) } /// Evaluate password strength pub fn evaluate_strength(&self, password: &str) -> PasswordStrength { let len = password.len(); let has_lowercase = password.chars().any(|c| c.is_lowercase()); let has_uppercase = password.chars().any(|c| c.is_uppercase()); let has_digit = password.chars().any(|c| c.is_ascii_digit()); let has_special = password.chars().any(|c| !c.is_alphanumeric()); let complexity_score = [has_lowercase, has_uppercase, has_digit, has_special] .iter() .filter(|&&x| x) .count(); match (len, complexity_score) { (0..=7, _) => PasswordStrength::Weak, (8..=9, 0..=2) => PasswordStrength::Fair, (8..=9, 3..=4) => PasswordStrength::Good, (10..=11, 0..=2) => PasswordStrength::Fair, (10..=11, 3..=4) => PasswordStrength::Good, (12..=15, 0..=2) => PasswordStrength::Good, (12..=15, 3..=4) => PasswordStrength::Strong, (16.., 0..=2) => PasswordStrength::Good, (16.., 3) => PasswordStrength::Strong, (16.., 4..) => PasswordStrength::VeryStrong, _ => PasswordStrength::Weak, } } /// Check if password meets minimum requirements /// /// Requirements: /// - At least 8 characters /// - At least 2 character types (lowercase, uppercase, digit, special) pub fn meets_requirements(&self, password: &str) -> bool { matches!( self.evaluate_strength(password), PasswordStrength::Fair | PasswordStrength::Good | PasswordStrength::Strong | PasswordStrength::VeryStrong ) } } impl Default for PasswordService { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_password_hashing() { let service = PasswordService::new(); let password = "test_password_123"; let hash = service.hash_password(password).unwrap(); assert!(!hash.is_empty()); assert!(hash.starts_with("$argon2")); } #[test] fn test_password_verification() { let service = PasswordService::new(); let password = "test_password_123"; let hash = service.hash_password(password).unwrap(); // Correct password should verify assert!(service.verify_password(password, &hash).unwrap()); // Wrong password should fail assert!(!service.verify_password("wrong_password", &hash).unwrap()); } #[test] fn test_password_strength_weak() { let service = PasswordService::new(); assert_eq!(service.evaluate_strength("abc"), PasswordStrength::Weak); assert_eq!(service.evaluate_strength("1234567"), PasswordStrength::Weak); } #[test] fn test_password_strength_fair() { let service = PasswordService::new(); assert_eq!( service.evaluate_strength("Password1"), PasswordStrength::Fair ); } #[test] fn test_password_strength_good() { let service = PasswordService::new(); assert_eq!( service.evaluate_strength("Password123"), PasswordStrength::Good ); } #[test] fn test_password_strength_strong() { let service = PasswordService::new(); assert_eq!( service.evaluate_strength("Password123!"), PasswordStrength::Strong ); } #[test] fn test_password_strength_very_strong() { let service = PasswordService::new(); assert_eq!( service.evaluate_strength("MyP@ssw0rd!2024Secure"), PasswordStrength::VeryStrong ); } #[test] fn test_password_requirements() { let service = PasswordService::new(); // Too weak assert!(!service.meets_requirements("abc")); assert!(!service.meets_requirements("1234567")); // Meets requirements assert!(service.meets_requirements("Password1")); assert!(service.meets_requirements("MyPassword123")); assert!(service.meets_requirements("Secure!Pass2024")); } #[test] fn test_different_salts() { let service = PasswordService::new(); let password = "test_password"; let hash1 = service.hash_password(password).unwrap(); let hash2 = service.hash_password(password).unwrap(); // Different salts should produce different hashes assert_ne!(hash1, hash2); // Both should verify correctly assert!(service.verify_password(password, &hash1).unwrap()); assert!(service.verify_password(password, &hash2).unwrap()); } }