ofdb_entities/
password.rs

1use pwhash::bcrypt;
2use std::{fmt, str::FromStr};
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
6pub struct Password(String);
7
8impl Password {
9    pub const fn min_len() -> usize {
10        6
11    }
12
13    pub fn verify(&self, password: &str) -> bool {
14        pwhash::bcrypt::verify(password, &self.0)
15    }
16}
17
18impl From<String> for Password {
19    fn from(from: String) -> Self {
20        Self(from)
21    }
22}
23
24impl From<Password> for String {
25    fn from(from: Password) -> Self {
26        from.0
27    }
28}
29
30impl AsRef<str> for Password {
31    fn as_ref(&self) -> &str {
32        &self.0
33    }
34}
35
36#[derive(Debug, Error)]
37pub enum ParseError {
38    #[error("Insufficient password length")]
39    InsufficientLength,
40    #[error("Invalid password")]
41    Invalid,
42}
43
44impl FromStr for Password {
45    type Err = ParseError;
46
47    fn from_str(password: &str) -> Result<Self, Self::Err> {
48        if password.len() < Password::min_len() {
49            return Err(ParseError::InsufficientLength);
50        }
51        let res = Self(bcrypt::hash(password).map_err(|e| match e {
52            pwhash::error::Error::InsufficientLength => ParseError::InsufficientLength,
53            _ => ParseError::Invalid,
54        })?);
55        debug_assert!(res.verify(password));
56        Ok(res)
57    }
58}
59
60impl fmt::Display for Password {
61    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), std::fmt::Error> {
62        write!(f, "{}", self.0)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn should_hash_and_verify_password() {
72        let input = "p^$$w%&7*{}";
73        let password = input.parse::<Password>().unwrap();
74        assert_ne!(password.as_ref(), input);
75        assert!(password.verify(input));
76    }
77
78    #[test]
79    fn should_fail_to_parse_short_passwords() {
80        assert!("a".parse::<Password>().is_err());
81        assert!("ab".parse::<Password>().is_err());
82        assert!("abc".parse::<Password>().is_err());
83        assert!("abcd".parse::<Password>().is_err());
84        assert!("abcde".parse::<Password>().is_err());
85    }
86}