allowthem_core/
password.rs1use argon2::{Argon2, PasswordVerifier};
2use password_hash::{PasswordHash as PhcHash, PasswordHasher, SaltString, rand_core::OsRng};
3
4use crate::error::AuthError;
5use crate::types::PasswordHash;
6
7pub fn hash_password(plaintext: &str) -> Result<PasswordHash, AuthError> {
12 let salt = SaltString::generate(&mut OsRng);
13 let phc = Argon2::default()
14 .hash_password(plaintext.as_bytes(), &salt)
15 .map_err(|e| AuthError::InvalidPasswordHash(e.to_string()))?;
16 Ok(PasswordHash::new_unchecked(phc.to_string()))
17}
18
19pub fn verify_password(plaintext: &str, hash: &PasswordHash) -> Result<bool, AuthError> {
24 let phc =
25 PhcHash::new(hash.as_str()).map_err(|e| AuthError::InvalidPasswordHash(e.to_string()))?;
26 match Argon2::default().verify_password(plaintext.as_bytes(), &phc) {
27 Ok(()) => Ok(true),
28 Err(password_hash::Error::Password) => Ok(false),
29 Err(e) => Err(AuthError::InvalidPasswordHash(e.to_string())),
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
38 fn test_hash_and_verify_correct_password() {
39 let hash = hash_password("correct-horse-battery-staple").expect("hash_password");
40 let result =
41 verify_password("correct-horse-battery-staple", &hash).expect("verify_password");
42 assert!(result, "correct password must verify as true");
43 }
44
45 #[test]
46 fn test_verify_wrong_password_returns_false() {
47 let hash = hash_password("the-real-password").expect("hash_password");
48 let result = verify_password("wrong-password", &hash).expect("verify_password");
49 assert!(
50 !result,
51 "wrong password must return Ok(false), not an error"
52 );
53 }
54
55 #[test]
56 fn test_verify_garbage_hash_returns_error() {
57 let garbage = PasswordHash::new_unchecked("not-a-phc-string".to_string());
58 let result = verify_password("anything", &garbage);
59 assert!(result.is_err(), "corrupt hash must return Err");
60 }
61
62 #[test]
63 fn test_two_hashes_of_same_password_differ() {
64 let h1 = hash_password("same-input").expect("hash 1");
65 let h2 = hash_password("same-input").expect("hash 2");
66 assert_ne!(
67 h1.as_str(),
68 h2.as_str(),
69 "each hash must have a unique salt — identical outputs would indicate missing salt"
70 );
71 }
72}