ofdb_entities/
password.rs1use 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}