lmrc_http_common/auth/
password.rs

1//! Password hashing and verification utilities
2
3use bcrypt::{hash, verify, BcryptError, DEFAULT_COST};
4use thiserror::Error;
5
6/// Password hashing/verification errors
7#[derive(Debug, Error)]
8pub enum PasswordError {
9    #[error("Failed to hash password: {0}")]
10    HashError(String),
11
12    #[error("Failed to verify password: {0}")]
13    VerifyError(String),
14
15    #[error("Invalid password")]
16    InvalidPassword,
17}
18
19impl From<BcryptError> for PasswordError {
20    fn from(err: BcryptError) -> Self {
21        PasswordError::HashError(err.to_string())
22    }
23}
24
25/// Hash a password using bcrypt
26///
27/// # Example
28///
29/// ```rust
30/// use lmrc_http_common::auth::hash_password;
31///
32/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
33/// let hashed = hash_password("my_secure_password")?;
34/// assert_ne!(hashed, "my_secure_password");
35/// # Ok(())
36/// # }
37/// ```
38pub fn hash_password(password: &str) -> Result<String, PasswordError> {
39    hash(password, DEFAULT_COST).map_err(Into::into)
40}
41
42/// Hash a password with a custom cost
43///
44/// Higher cost = more secure but slower. Default is 12.
45/// Recommended range: 10-14
46pub fn hash_password_with_cost(password: &str, cost: u32) -> Result<String, PasswordError> {
47    hash(password, cost).map_err(Into::into)
48}
49
50/// Verify a password against a hash
51///
52/// # Example
53///
54/// ```rust
55/// use lmrc_http_common::auth::{hash_password, verify_password};
56///
57/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
58/// let hashed = hash_password("my_secure_password")?;
59/// assert!(verify_password("my_secure_password", &hashed)?);
60/// assert!(!verify_password("wrong_password", &hashed)?);
61/// # Ok(())
62/// # }
63/// ```
64pub fn verify_password(password: &str, hash: &str) -> Result<bool, PasswordError> {
65    verify(password, hash).map_err(|e| PasswordError::VerifyError(e.to_string()))
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_hash_and_verify() {
74        let password = "test_password_123";
75        let hashed = hash_password(password).unwrap();
76
77        assert_ne!(password, hashed);
78        assert!(verify_password(password, &hashed).unwrap());
79        assert!(!verify_password("wrong_password", &hashed).unwrap());
80    }
81
82    #[test]
83    fn test_custom_cost() {
84        let password = "test_password";
85        let hashed = hash_password_with_cost(password, 10).unwrap();
86
87        assert!(verify_password(password, &hashed).unwrap());
88    }
89}