use argon2::Argon2;
use argon2::password_hash::phc::PasswordHash;
use argon2::password_hash::{PasswordHasher, PasswordVerifier};
use sha2::{Digest, Sha256};
use crate::{Error, Result};
const DUMMY_HASH: &str = "$argon2id$v=19$m=19456,t=2,p=1$vYr3jd8Ylw+62+VMY1gp6w$e6xmLuAnUlkoGiWLEsmz/NNQ/+5MSk8XXiCPZlRw73Q";
pub fn hash_password(password: &str) -> Result<String> {
Argon2::default()
.hash_password(password.as_bytes())
.map(|hash| hash.to_string())
.map_err(|err| Error::Auth(format!("failed to hash password: {err}")))
}
pub fn verify_password(password: &str, hash: &str) -> bool {
PasswordHash::new(hash).is_ok_and(|parsed| {
Argon2::default()
.verify_password(password.as_bytes(), &parsed)
.is_ok()
})
}
pub fn verify_password_or_dummy(password: &str, hash: Option<&str>) -> bool {
match hash {
Some(hash) => verify_password(password, hash),
None => {
let _ = verify_password(password, DUMMY_HASH);
false
}
}
}
pub fn generate_token() -> Result<String> {
let mut bytes = [0u8; 32];
getrandom::fill(&mut bytes)
.map_err(|err| Error::Auth(format!("failed to generate token: {err}")))?;
Ok(hex(&bytes))
}
pub fn hash_token(token: &str) -> String {
hex(&Sha256::digest(token.as_bytes()))
}
fn hex(bytes: &[u8]) -> String {
use std::fmt::Write as _;
bytes
.iter()
.fold(String::with_capacity(bytes.len() * 2), |mut out, b| {
let _ = write!(out, "{b:02x}");
out
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hashes_and_verifies_passwords() {
let hash = hash_password("correct horse").unwrap();
assert!(hash.starts_with("$argon2id$"), "{hash}");
assert!(verify_password("correct horse", &hash));
assert!(!verify_password("wrong horse", &hash));
assert_ne!(
hash,
hash_password("correct horse").unwrap(),
"salts differ"
);
}
#[test]
fn malformed_hashes_never_verify() {
assert!(!verify_password("x", "not a hash"));
assert!(!verify_password_or_dummy("x", None));
}
#[test]
fn dummy_hash_is_well_formed() {
assert!(PasswordHash::new(DUMMY_HASH).is_ok());
}
#[test]
fn tokens_are_random_and_hash_stably() {
let (a, b) = (generate_token().unwrap(), generate_token().unwrap());
assert_eq!(a.len(), 64);
assert_ne!(a, b);
assert_eq!(hash_token(&a), hash_token(&a));
assert_ne!(hash_token(&a), a);
assert_eq!(
hash_token("abc"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
}