use bcrypt::{hash, verify, BcryptError, DEFAULT_COST};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PasswordError {
#[error("Failed to hash password: {0}")]
HashError(String),
#[error("Failed to verify password: {0}")]
VerifyError(String),
#[error("Invalid password")]
InvalidPassword,
}
impl From<BcryptError> for PasswordError {
fn from(err: BcryptError) -> Self {
PasswordError::HashError(err.to_string())
}
}
pub fn hash_password(password: &str) -> Result<String, PasswordError> {
hash(password, DEFAULT_COST).map_err(Into::into)
}
pub fn hash_password_with_cost(password: &str, cost: u32) -> Result<String, PasswordError> {
hash(password, cost).map_err(Into::into)
}
pub fn verify_password(password: &str, hash: &str) -> Result<bool, PasswordError> {
verify(password, hash).map_err(|e| PasswordError::VerifyError(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_and_verify() {
let password = "test_password_123";
let hashed = hash_password(password).unwrap();
assert_ne!(password, hashed);
assert!(verify_password(password, &hashed).unwrap());
assert!(!verify_password("wrong_password", &hashed).unwrap());
}
#[test]
fn test_custom_cost() {
let password = "test_password";
let hashed = hash_password_with_cost(password, 10).unwrap();
assert!(verify_password(password, &hashed).unwrap());
}
}