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 tap::Pipe;
14
15#[derive(Clone, Default, From, Into, PartialEq, Eq, Hash, Deserialize, Serialize)]
16#[from(String, &str, Box<str>, Cow<'_, str>)]
17#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
18pub struct Password(Box<str>);
19
20impl Password {
21 #[inline]
22 pub fn new(password: &str) -> Self {
23 Self(Box::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 #[cfg_attr(miri, ignore)]
81 fn verify_password() -> Result<()> {
82 let password = Password::new("foo123");
83 let hash = password.hash().unwrap();
84 assert!(password.verify(&hash)?);
85
86 let other_password = Password::new("bar456");
87 assert!(!other_password.verify(&hash)?);
88
89 Ok(())
90 }
91}