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