pub use argon2::password_hash::Error as PasswordError;
use argon2::{
Argon2,
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
};
use serde::{Deserialize, Serialize};
use crate::{
fnd::{
authn::{Authn, UserHandle},
identifier::{Uid, UserIdentifier},
},
prelude::*,
};
impl<U> Authn<U>
where
U: UserHandle,
{
pub async fn verify_user_password(
&self,
depot: &mut Depot,
user_ident: &UserIdentifier,
password: String,
) -> Result<Uid, ApiError> {
let uid_password_hash = self.user_handle.get_password_hash(depot, user_ident).await?;
if !Password(password)
.verify_password(uid_password_hash.password_hash.as_str())
.unwrap_or_default()
{
return Err(api_err!(
ET_USER_VERIF_CODE,
"Password is not matched.",
&ERR_PATH_AUTHN
));
}
Ok(uid_password_hash.user_id)
}
pub async fn reset_user_password(
&self,
depot: &mut Depot,
user_ident: &UserIdentifier,
new_password: String,
) -> Result<(), ApiError> {
let password_hash = Password(new_password)
.password_hash()
.map_err(|err| api_err!(ET_USER_REQ_PARAM, &ERR_PATH_AUTHN).with_source(err.into_error(), true))?;
self.user_handle()
.set_password_hash(depot, user_ident, password_hash)
.await
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::exhaustive_structs)]
pub struct Password(
pub String,
);
impl From<&str> for Password {
fn from(value: &str) -> Self {
Self(value.to_owned())
}
}
impl From<String> for Password {
fn from(value: String) -> Self {
Self(value)
}
}
impl Password {
pub fn password_hash(&self) -> Result<String, PasswordError> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
Ok(argon2.hash_password(self.0.as_bytes(), &salt)?.to_string())
}
pub fn verify_password(&self, password_hash: &str) -> Result<bool, PasswordError> {
let parsed_hash = PasswordHash::new(password_hash)?;
Argon2::default()
.verify_password(self.0.as_bytes(), &parsed_hash)
.map(|_| true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() -> Result<(), PasswordError> {
let pwd = Password("super_secret_password".to_string());
let password_hash = pwd.password_hash()?;
println!("Hashed password: {}", password_hash);
let is_valid = pwd.verify_password(&password_hash)?;
println!("Password is valid: {}", is_valid);
Ok(())
}
}