diff --git a/backend/src/auth/password.rs b/backend/src/auth/password.rs index 04e3bbd..efe5383 100644 --- a/backend/src/auth/password.rs +++ b/backend/src/auth/password.rs @@ -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 { 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)), + } } }