//! Password hashing using Argon2id use anyhow::{anyhow, Result}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; /// Hash a password using Argon2id pub fn hash_password(password: &str) -> Result { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let hash = argon2 .hash_password(password.as_bytes(), &salt) .map_err(|e| anyhow!("Failed to hash password: {}", e))?; Ok(hash.to_string()) } /// Verify a password against a stored hash pub fn verify_password(password: &str, hash: &str) -> Result { let parsed_hash = PasswordHash::new(hash) .map_err(|e| anyhow!("Invalid password hash format: {}", e))?; let argon2 = Argon2::default(); Ok(argon2.verify_password(password.as_bytes(), &parsed_hash).is_ok()) } /// Generate a random password (for initial admin) pub fn generate_random_password(length: usize) -> String { use rand::Rng; const CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%"; let mut rng = rand::thread_rng(); (0..length) .map(|_| { let idx = rng.gen_range(0..CHARSET.len()); CHARSET[idx] as char }) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn test_hash_and_verify() { let password = "test_password_123"; let hash = hash_password(password).unwrap(); assert!(verify_password(password, &hash).unwrap()); assert!(!verify_password("wrong_password", &hash).unwrap()); } #[test] fn test_random_password() { let password = generate_random_password(16); assert_eq!(password.len(), 16); } }