1use crate::error::Result;
5use anyhow::anyhow;
6use argon2::password_hash::{Error as PasswordHashError, PasswordHasher};
7use argon2::{Argon2, PasswordHash, PasswordVerifier};
8use derive_more::{From, Into};
9use serde::{Deserialize, Serialize};
10use std::borrow::Cow;
11use std::fmt;
12use std::ops::Deref;
13use std::sync::Arc;
14use tap::Pipe;
15
16#[derive(Clone, Default, From, Into, PartialEq, Eq, Hash, Deserialize, Serialize)]
17#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
18pub struct Password(Arc<str>);
19
20impl Password {
21 #[inline]
22 pub fn new(password: &str) -> Self {
23 Self(Arc::from(password))
24 }
25
26 pub fn hash(&self) -> Result<Box<str>> {
27 argon2()
28 .hash_password(self.0.as_bytes())
29 .map_err(|err| anyhow!("Failed to hash password").context(err))?
30 .to_string()
31 .into_boxed_str()
32 .pipe(Ok)
33 }
34
35 pub fn verify(&self, hash: &str) -> Result<bool> {
36 match verify(self.0.as_bytes(), hash) {
37 Ok(()) => Ok(true),
38 Err(PasswordHashError::PasswordInvalid) => Ok(false),
39 Err(err) => {
40 #[cfg(debug_assertions)]
41 tracing::trace!(error = %err);
42
43 Err(err.into())
44 }
45 }
46 }
47}
48
49impl Deref for Password {
50 type Target = str;
51
52 fn deref(&self) -> &Self::Target {
53 self.0.as_str()
54 }
55}
56
57impl fmt::Debug for Password {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.debug_tuple("Password")
60 .field(&"***")
61 .finish()
62 }
63}
64
65fn argon2() -> Argon2<'static> {
66 Argon2::default()
67}
68
69fn verify(password: &[u8], hash: &str) -> Result<(), PasswordHashError> {
70 let hash = PasswordHash::new(hash)?;
71 argon2().verify_password(password, &hash)
72}
73
74#[cfg(test)]
75mod tests {
76 use super::Password;
77 use anyhow::Result;
78
79 #[test]
80 fn verify_password() -> Result<()> {
81 let password = Password::new("foo123");
82 let hash = password.hash().unwrap();
83 assert!(password.verify(&hash)?);
84
85 let other_password = Password::new("bar456");
86 assert!(!other_password.verify(&hash)?);
87
88 Ok(())
89 }
90}