use argon2::password_hash::{rand_core::OsRng, SaltString};
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use larust_core::AppError;
pub fn hash_password(password: &str) -> Result<String, AppError> {
let salt = SaltString::generate(&mut OsRng);
Argon2::default()
.hash_password(password.as_bytes(), &salt)
.map(|hash| hash.to_string())
.map_err(internal_error)
}
pub fn verify_password(hash: &str, password: &str) -> Result<bool, AppError> {
let parsed_hash = PasswordHash::new(hash).map_err(internal_error)?;
Ok(Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}
fn internal_error(source: argon2::password_hash::Error) -> AppError {
AppError::Internal(Box::new(std::io::Error::other(source.to_string())))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_and_verify_round_trip() {
let hash = hash_password("correct horse battery staple").unwrap();
assert!(verify_password(&hash, "correct horse battery staple").unwrap());
}
#[test]
fn verify_rejects_wrong_password() {
let hash = hash_password("correct horse battery staple").unwrap();
assert!(!verify_password(&hash, "wrong password").unwrap());
}
#[test]
fn hashing_the_same_password_twice_produces_different_hashes() {
let a = hash_password("correct horse battery staple").unwrap();
let b = hash_password("correct horse battery staple").unwrap();
assert_ne!(a, b);
}
#[test]
fn verify_rejects_malformed_hash() {
assert!(verify_password("not a real hash", "anything").is_err());
}
}