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, TapFallible};
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 verify(self.0.as_bytes(), hash)
36 .tap_err_dbg(|err| tracing::debug!(error = %err))
37 .is_ok()
38 }
39}
40
41impl Deref for Password {
42 type Target = str;
43
44 fn deref(&self) -> &Self::Target {
45 self.0.as_str()
46 }
47}
48
49impl fmt::Debug for Password {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.debug_tuple("Password")
52 .field(&"***")
53 .finish()
54 }
55}
56
57fn verify(password: &[u8], hash: &str) -> Result<()> {
58 let hash = PasswordHash::new(hash)?;
59 Argon2::default()
60 .verify_password(password, &hash)
61 .map_err(Into::into)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::Password;
67
68 #[test]
69 fn verify_password() {
70 let password = Password::new("foo123");
71 let hash = password.hash().unwrap();
72 assert!(password.verify(&hash));
73
74 let other_password = Password::new("bar456");
75 assert!(!other_password.verify(&hash));
76 }
77}