use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; use axum::async_trait; use como_core::users::UserService; use como_domain::Context; use rand_core::OsRng; use crate::database::ConnectionPool; pub struct DefaultUserService { pool: ConnectionPool, } impl DefaultUserService { pub fn new(pool: ConnectionPool) -> Self { Self { pool } } fn hash_password(&self, _context: &Context, password: String) -> anyhow::Result { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(password.as_bytes(), &salt) .map_err(|e| anyhow::anyhow!(e))? .to_string(); Ok(password_hash) } fn validate_password( &self, _context: &Context, password: String, hashed_password: String, ) -> anyhow::Result { let argon2 = Argon2::default(); let parsed_hash = PasswordHash::new(&hashed_password).map_err(|e| anyhow::anyhow!(e))?; match argon2.verify_password(password.as_bytes(), &parsed_hash) { Ok(..) => Ok(true), Err(..) => Ok(false), } } } #[async_trait] impl UserService for DefaultUserService { async fn add_user( &self, context: &Context, username: String, password: String, ) -> anyhow::Result { let hashed_password = self.hash_password(context, password)?; let rec = sqlx::query!( r#" INSERT INTO users (username, password_hash) VALUES ( $1, $2 ) RETURNING id "#, username, hashed_password ) .fetch_one(&self.pool) .await?; Ok(rec.id.to_string()) } async fn validate_user( &self, context: &Context, username: String, password: String, ) -> anyhow::Result> { let rec = sqlx::query!( r#" SELECT * from users where username=$1 "#, username, ) .fetch_optional(&self.pool) .await?; match rec { Some(user) => match self.validate_password(context, password, user.password_hash)? { true => Ok(Some(user.id.to_string())), false => Ok(None), }, None => Ok(None), } } }