lavina/crates/lavina-core/src/auth.rs

67 lines
2.4 KiB
Rust
Raw Normal View History

use anyhow::{anyhow, Result};
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use argon2::Argon2;
use rand_core::OsRng;
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 }
}
#[tracing::instrument(skip(self, provided_password), name = "Authenticator::authenticate")]
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);
};
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);
}
}
Ok(Verdict::InvalidPassword)
}
#[tracing::instrument(skip(self, provided_password), name = "Authenticator::set_password")]
pub async fn set_password(&self, login: &str, provided_password: &str) -> Result<UpdatePasswordResult> {
let Some(u) = self.storage.retrieve_user_by_name(login).await? else {
return Ok(UpdatePasswordResult::UserNotFound);
};
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?;
log::info!("Password changed for player {login}");
Ok(UpdatePasswordResult::PasswordUpdated)
}
}