Skip to main content

nil_crypto/
password.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use 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}