2024-04-23 16:31:00 +00:00
|
|
|
use anyhow::{anyhow, Result};
|
|
|
|
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
|
|
|
use argon2::Argon2;
|
|
|
|
use rand_core::OsRng;
|
2024-04-23 10:10:10 +00:00
|
|
|
|
|
|
|
use crate::prelude::log;
|
|
|
|
use crate::repo::Storage;
|
|
|
|
|
|
|
|
pub enum Verdict {
|
|
|
|
Authenticated,
|
|
|
|
UserNotFound,
|
|
|
|
InvalidPassword,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum UpdatePasswordResult {
|
|
|
|
PasswordUpdated,
|
|
|
|
UserNotFound,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Authenticator<'a> {
|
|
|
|
storage: &'a Storage,
|
|
|
|
}
|
|
|
|
impl<'a> Authenticator<'a> {
|
|
|
|
pub fn new(storage: &'a Storage) -> Self {
|
|
|
|
Self { storage }
|
|
|
|
}
|
|
|
|
|
2024-04-26 10:16:23 +00:00
|
|
|
#[tracing::instrument(skip(self, provided_password), name = "Authenticator::authenticate")]
|
2024-04-23 10:10:10 +00:00
|
|
|
pub async fn authenticate(&self, login: &str, provided_password: &str) -> Result<Verdict> {
|
|
|
|
let Some(stored_user) = self.storage.retrieve_user_by_name(login).await? else {
|
|
|
|
return Ok(Verdict::UserNotFound);
|
|
|
|
};
|
2024-04-23 16:31:00 +00:00
|
|
|
if let Some(argon2_hash) = stored_user.argon2_hash {
|
|
|
|
let argon2 = Argon2::default();
|
|
|
|
let password_hash =
|
|
|
|
PasswordHash::new(&argon2_hash).map_err(|e| anyhow!("Failed to parse password hash: {e:?}"))?;
|
|
|
|
let password_verifier = argon2.verify_password(provided_password.as_bytes(), &password_hash);
|
|
|
|
if password_verifier.is_ok() {
|
|
|
|
return Ok(Verdict::Authenticated);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(expected_password) = stored_user.password {
|
|
|
|
if expected_password == provided_password {
|
|
|
|
return Ok(Verdict::Authenticated);
|
|
|
|
}
|
2024-04-23 10:10:10 +00:00
|
|
|
}
|
|
|
|
Ok(Verdict::InvalidPassword)
|
|
|
|
}
|
|
|
|
|
2024-04-26 10:16:23 +00:00
|
|
|
#[tracing::instrument(skip(self, provided_password), name = "Authenticator::set_password")]
|
2024-04-23 10:10:10 +00:00
|
|
|
pub async fn set_password(&self, login: &str, provided_password: &str) -> Result<UpdatePasswordResult> {
|
2024-04-23 16:31:00 +00:00
|
|
|
let Some(u) = self.storage.retrieve_user_by_name(login).await? else {
|
2024-04-23 10:10:10 +00:00
|
|
|
return Ok(UpdatePasswordResult::UserNotFound);
|
|
|
|
};
|
2024-04-23 16:31:00 +00:00
|
|
|
|
|
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
|
|
let argon2 = Argon2::default();
|
|
|
|
let password_hash = argon2
|
|
|
|
.hash_password(provided_password.as_bytes(), &salt)
|
|
|
|
.map_err(|e| anyhow!("Failed to hash password: {e:?}"))?;
|
|
|
|
|
|
|
|
self.storage.set_argon2_challenge(u.id, password_hash.to_string().as_str()).await?;
|
2024-04-23 10:10:10 +00:00
|
|
|
log::info!("Password changed for player {login}");
|
|
|
|
Ok(UpdatePasswordResult::PasswordUpdated)
|
|
|
|
}
|
|
|
|
}
|