use argon2::Argon2;
use argon2::password_hash::{
PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng,
};
use noema::core::{Container, Injectable};
pub trait Hasher: Send + Sync {
fn hash(&self, data: &[u8]) -> String;
fn verify(&self, data: &[u8], hashed: &str) -> bool;
}
#[derive(Clone, Copy, Default)]
pub struct Argon2Hasher;
impl Hasher for Argon2Hasher {
fn hash(&self, data: &[u8]) -> String {
let salt = SaltString::generate(&mut OsRng);
Argon2::default()
.hash_password(data, &salt)
.expect("argon2 hash")
.to_string()
}
fn verify(&self, data: &[u8], hashed: &str) -> bool {
let Ok(parsed) = PasswordHash::new(hashed) else {
return false;
};
Argon2::default().verify_password(data, &parsed).is_ok()
}
}
impl Injectable<Container> for Argon2Hasher {
fn inject(_: &Container) -> Self {
Self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_and_verify() {
let hasher = Argon2Hasher;
let hashed = hasher.hash(b"secret");
assert_ne!(hashed, hasher.hash(b"secret"));
assert!(hasher.verify(b"secret", &hashed));
assert!(!hasher.verify(b"other", &hashed));
assert!(!hasher.verify(b"secret", "not-a-phc"));
}
#[test]
fn hasher_resolve_is_singleton() {
let a = noema::resolve::<dyn Hasher + Send + Sync>();
let b = noema::resolve::<dyn Hasher + Send + Sync>();
assert!(std::sync::Arc::ptr_eq(&a, &b));
assert!(a.verify(b"x", &a.hash(b"x")));
}
}