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