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>)]
18pub struct Password(Arc<str>);
19
20impl Password {
21  #[inline]
22  pub fn new(password: &str) -> Self {
23    Self(Arc::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  fn verify_password() -> Result<()> {
81    let password = Password::new("foo123");
82    let hash = password.hash().unwrap();
83    assert!(password.verify(&hash)?);
84
85    let other_password = Password::new("bar456");
86    assert!(!other_password.verify(&hash)?);
87
88    Ok(())
89  }
90}