62 lines
1.9 KiB
Rust
62 lines
1.9 KiB
Rust
#[cfg(debug_assertions)]
|
|
use argon2::{Algorithm, Params, Version};
|
|
use argon2::{
|
|
Argon2,
|
|
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
|
|
};
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum HashPasswordError {
|
|
#[error("failed to initialize password hasher: {0}")]
|
|
Hasher(String),
|
|
#[error("failed to hash password: {0}")]
|
|
Hash(String),
|
|
}
|
|
|
|
pub fn hash_password(password: &str, pepper: &str) -> Result<String, HashPasswordError> {
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let password = format!("{password}{pepper}");
|
|
password_hasher()?
|
|
.hash_password(password.as_bytes(), &salt)
|
|
.map(|hash| hash.to_string())
|
|
.map_err(|error| HashPasswordError::Hash(error.to_string()))
|
|
}
|
|
|
|
pub fn verify_password(password: &str, pepper: &str, password_hash: &str) -> bool {
|
|
let Ok(parsed) = PasswordHash::new(password_hash) else {
|
|
return false;
|
|
};
|
|
let password = format!("{password}{pepper}");
|
|
match password_hasher() {
|
|
Ok(argon2) => argon2.verify_password(password.as_bytes(), &parsed).is_ok(),
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
fn password_hasher() -> Result<Argon2<'static>, HashPasswordError> {
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
let params = Params::new(8 * 1024, 1, 1, None)
|
|
.map_err(|error| HashPasswordError::Hasher(error.to_string()))?;
|
|
Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, params))
|
|
}
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
{
|
|
Ok(Argon2::default())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{hash_password, verify_password};
|
|
|
|
#[test]
|
|
fn hashes_and_verifies_password() {
|
|
let password_hash = hash_password("test-password", "pepper").unwrap();
|
|
|
|
assert!(verify_password("test-password", "pepper", &password_hash));
|
|
assert!(!verify_password("wrong-password", "pepper", &password_hash));
|
|
}
|
|
}
|