Skip to main content

webx_core/security/
password.rs

1//! Password hashing port ([`IPasswordHasher`]).
2//!
3//! Applications hash and verify passwords through this port instead of carrying
4//! their own Argon2 (or bcrypt, or scrypt) code. Hashes are **PHC strings** —
5//! the self-describing `$argon2id$v=19$m=…,t=…,p=…$salt$hash` format — so the
6//! algorithm and its parameters travel with the stored value and can be upgraded
7//! without a schema change.
8//!
9//! The default implementation is `Argon2PasswordHasher` in `rust-webx-host`
10//! (feature `password-argon2`, on by default); it runs Argon2 on a blocking
11//! thread so a login does not stall the async runtime. Register a different
12//! implementation in DI to replace it — handlers keep injecting
13//! `Arc<dyn IPasswordHasher>` and never need to change.
14
15use crate::error::Result;
16
17/// Hash and verify passwords (Argon2id by default).
18///
19/// ```ignore
20/// let phc = hasher.hash(&password).await?;             // login / registration
21/// let ok = hasher.verify(&password, &stored.phc).await?; // 401 on false
22/// if ok && hasher.needs_rehash(&stored.phc) {
23///     stored.phc = hasher.hash(&password).await?;      // transparent upgrade
24/// }
25/// ```
26#[async_trait::async_trait]
27pub trait IPasswordHasher: Send + Sync {
28    /// Hash one password with a fresh random salt; returns a PHC string.
29    ///
30    /// The implementation must not panic on any input; failures (including
31    /// misconfigured parameters) come back as a typed
32    /// [`Error`](crate::error::Error).
33    async fn hash(&self, password: &str) -> Result<String>;
34
35    /// Verify `password` against a stored PHC hash.
36    ///
37    /// * `Ok(true)`  — the password matches.
38    /// * `Ok(false)` — the password does not match (a client error, not a server
39    ///   error: do not log the stored hash or the password).
40    /// * `Err(...)`  — the stored hash is malformed, uses an unsupported
41    ///   algorithm, or the hashing backend failed. This is **server-side** data
42    ///   or configuration corruption; reporting it as `Ok(false)` would silently
43    ///   turn a broken account into "wrong password".
44    async fn verify(&self, password: &str, stored_hash: &str) -> Result<bool>;
45
46    /// Whether a stored hash was produced with weaker/other parameters than the
47    /// ones this hasher currently uses.
48    ///
49    /// Call after a successful [`IPasswordHasher::verify`] and re-hash the
50    /// password when it returns `true` — this is how cost parameters are raised
51    /// without a migration. A hash that cannot be parsed, or that uses a
52    /// different algorithm, also returns `true` (it cannot be trusted as-is).
53    fn needs_rehash(&self, stored_hash: &str) -> bool;
54}