auth_framework/utils/
password.rs

1//! Password utility functions for the AuthFramework.
2
3use crate::errors::{AuthError, Result};
4use argon2::{
5    Argon2,
6    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
7};
8
9/// Password strength levels
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PasswordStrengthLevel {
12    Weak,
13    Medium,
14    Strong,
15    VeryStrong,
16}
17
18/// Password strength result with level and feedback
19#[derive(Debug, Clone)]
20pub struct PasswordStrength {
21    pub level: PasswordStrengthLevel,
22    pub feedback: Vec<String>,
23}
24
25/// Hash a password using Argon2
26pub fn hash_password(password: &str) -> Result<String> {
27    let salt = SaltString::generate(&mut OsRng);
28    let argon2 = Argon2::default();
29
30    let password_hash = argon2
31        .hash_password(password.as_bytes(), &salt)
32        .map_err(|e| AuthError::internal(format!("Failed to hash password: {}", e)))?;
33
34    Ok(password_hash.to_string())
35}
36
37/// Verify a password against its hash
38pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
39    let parsed_hash = PasswordHash::new(hash)
40        .map_err(|e| AuthError::internal(format!("Invalid password hash: {}", e)))?;
41
42    let argon2 = Argon2::default();
43
44    match argon2.verify_password(password.as_bytes(), &parsed_hash) {
45        Ok(()) => Ok(true),
46        Err(_) => Ok(false),
47    }
48}
49
50/// Check password strength based on various criteria
51pub fn check_password_strength(password: &str) -> PasswordStrength {
52    let length = password.len();
53    let has_lowercase = password.chars().any(|c| c.is_lowercase());
54    let has_uppercase = password.chars().any(|c| c.is_uppercase());
55    let has_digit = password.chars().any(|c| c.is_numeric());
56    let has_special = password.chars().any(|c| !c.is_alphanumeric());
57
58    let criteria_met = [has_lowercase, has_uppercase, has_digit, has_special]
59        .iter()
60        .map(|&b| if b { 1 } else { 0 })
61        .sum::<i32>();
62
63    let mut feedback = Vec::new();
64
65    if length < 8 {
66        feedback.push("Password should be at least 8 characters long".to_string());
67    }
68    if !has_lowercase {
69        feedback.push("Add lowercase letters".to_string());
70    }
71    if !has_uppercase {
72        feedback.push("Add uppercase letters".to_string());
73    }
74    if !has_digit {
75        feedback.push("Add numbers".to_string());
76    }
77    if !has_special {
78        feedback.push("Add special characters".to_string());
79    }
80
81    let level = match (length, criteria_met) {
82        (0..=6, _) => PasswordStrengthLevel::Weak,
83        (7..=10, 0..=2) => PasswordStrengthLevel::Weak,
84        (7..=10, 3) => PasswordStrengthLevel::Medium,
85        (7..=10, 4) => PasswordStrengthLevel::Medium,
86        (11..=14, 0..=2) => PasswordStrengthLevel::Medium,
87        (11..=14, 3..=4) => PasswordStrengthLevel::Strong,
88        (15.., 0..=2) => PasswordStrengthLevel::Strong,
89        (15.., 3..=4) => PasswordStrengthLevel::VeryStrong,
90        _ => PasswordStrengthLevel::VeryStrong,
91    };
92
93    PasswordStrength { level, feedback }
94}
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_password_hashing() {
101        let password = "testpassword123";
102        let hash = hash_password(password).unwrap();
103
104        assert!(verify_password(password, &hash).unwrap());
105        assert!(!verify_password("wrongpassword", &hash).unwrap());
106    }
107
108    #[test]
109    fn test_password_strength() {
110        assert_eq!(
111            check_password_strength("weak").level,
112            PasswordStrengthLevel::Weak
113        );
114        assert_eq!(
115            check_password_strength("Medium123").level,
116            PasswordStrengthLevel::Medium
117        );
118        assert_eq!(
119            check_password_strength("Strong123!").level,
120            PasswordStrengthLevel::Medium
121        );
122        assert_eq!(
123            check_password_strength("VeryStrong123!@#").level,
124            PasswordStrengthLevel::VeryStrong
125        );
126    }
127}