auth_framework/utils/
password.rs1use crate::errors::{AuthError, Result};
4use argon2::{
5 Argon2,
6 password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PasswordStrengthLevel {
12 Weak,
13 Medium,
14 Strong,
15 VeryStrong,
16}
17
18#[derive(Debug, Clone)]
20pub struct PasswordStrength {
21 pub level: PasswordStrengthLevel,
22 pub feedback: Vec<String>,
23}
24
25pub 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
37pub 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
50pub 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}