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;
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}