use std::fmt; use argon2::Argon2; use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; use rand_core::OsRng; use crate::normalize::nfc; #[derive(sqlx::Type)] #[sqlx(transparent)] pub struct StoredHash(String); impl StoredHash { pub fn verify(&self, password: &Password) -> Result { let hash = PasswordHash::new(&self.0)?; match Argon2::default().verify_password(password.as_bytes(), &hash) { // Successful authentication, not an error Ok(()) => Ok(true), // Unsuccessful authentication, also not an error Err(password_hash::errors::Error::Password) => Ok(false), // Password validation failed for some other reason, treat as an error Err(err) => Err(err), } } } impl fmt::Debug for StoredHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("StoredHash").field(&"********").finish() } } #[derive(serde::Deserialize)] #[serde(transparent)] pub struct Password(nfc::String); impl Password { pub fn hash(&self) -> Result { let Self(password) = self; let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let hash = argon2 .hash_password(password.as_bytes(), &salt)? .to_string(); Ok(StoredHash(hash)) } fn as_bytes(&self) -> &[u8] { let Self(value) = self; value.as_bytes() } } impl fmt::Debug for Password { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Password").field(&"********").finish() } } impl From for Password { fn from(password: String) -> Self { Password(password.into()) } }