steid

@jamesgill /

26d9828feat: password hashing behind a port1mo
1//! Service ports the application layer needs.
2//!
3//! Repository ports live in `domain::repository`; these are the non-persistence
4//! collaborators. Adapters live in `infrastructure`.
5
6use crate::domain::PasswordHash;
7
8/// Hashes and verifies passwords.
9///
10/// A port rather than a direct Argon2 call so tests can substitute a fast stub —
11/// Argon2 is deliberately slow, and a use case suite that hashes for real takes
12/// seconds per test.
13pub trait PasswordHasher: Send + Sync {
14 /// Hashes a plaintext password.
15 fn hash(&self, plaintext: &str) -> Result<PasswordHash, PasswordError>;
16
17 /// Checks a plaintext password against a stored hash.
18 ///
19 /// Returns `Ok(false)` for a wrong password and `Err` only when the hash itself
20 /// cannot be parsed — a corrupt stored hash is a different problem from a failed
21 /// login, and collapsing them hides real faults.
22 fn verify(&self, plaintext: &str, hash: &PasswordHash) -> Result<bool, PasswordError>;
23}
24
25/// A password could not be hashed or a stored hash could not be parsed.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct PasswordError(String);
28
29impl PasswordError {
30 pub fn new(message: impl Into<String>) -> Self {
31 Self(message.into())
32 }
33}
34
35impl std::fmt::Display for PasswordError {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "password hashing failed: {}", self.0)
38 }
39}
40
41impl std::error::Error for PasswordError {}