feat: per-profile DEKs + multi-profile (Phase A2, #3) #9
1 changed files with 18 additions and 5 deletions
|
|
@ -1,6 +1,9 @@
|
|||
use anyhow::Result;
|
||||
use pbkdf2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
password_hash::{
|
||||
rand_core::OsRng, Error as PasswordHashError, PasswordHash, PasswordHasher,
|
||||
PasswordVerifier, SaltString,
|
||||
},
|
||||
Pbkdf2,
|
||||
};
|
||||
|
||||
|
|
@ -15,14 +18,24 @@ impl PasswordService {
|
|||
Ok(password_hash.to_string())
|
||||
}
|
||||
|
||||
/// Verify a password against a stored PHC string.
|
||||
///
|
||||
/// Returns `Ok(true)` on a match, `Ok(false)` on a mismatch (wrong
|
||||
/// password — an expected outcome, not an error), and `Err(...)` only for
|
||||
/// genuine failures (unparseable hash, malformed input, etc.). Returning
|
||||
/// `Ok(false)` for a wrong password lets the login handler distinguish
|
||||
/// "invalid credentials" (401) from a real backend fault (500).
|
||||
pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
|
||||
let parsed_hash = PasswordHash::new(hash)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse password hash: {}", e))?;
|
||||
|
||||
Pbkdf2
|
||||
.verify_password(password.as_bytes(), &parsed_hash)
|
||||
.map(|_| true)
|
||||
.map_err(|e| anyhow::anyhow!("Password verification failed: {}", e))
|
||||
match Pbkdf2.verify_password(password.as_bytes(), &parsed_hash) {
|
||||
Ok(()) => Ok(true),
|
||||
// `Error::Password` is the password-hash crate's signal for a
|
||||
// mismatch — the one expected "failure" during normal auth.
|
||||
Err(PasswordHashError::Password) => Ok(false),
|
||||
Err(e) => Err(anyhow::anyhow!("Password verification failed: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue