use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use redis::aio::Connection as ConnectionManager; use redis::AsyncCommands; use sqlx::PgPool; use std::sync::Arc; use uuid::Uuid; pub struct AuthService { pub pool: PgPool, pub redis: Option>, } impl AuthService { pub fn new(pool: PgPool, redis: Option>) -> Self { Self { pool, redis } } pub async fn verify_user( &self, username: &str, password: &str, ) -> Result, Box> { // Try Redis cache first if let Some(redis) = &self.redis { let cache_key = format!("auth:user:{}", username); if let Ok(user_id_str) = redis.clone().get::<_, String>(cache_key).await { if let Ok(user_id) = Uuid::parse_str(&user_id_str) { return Ok(Some(user_id)); } } } // Fallback to database let user = sqlx::query( "SELECT id, password_hash FROM users WHERE username = $1 AND is_active = true", ) .bind(username) .fetch_optional(&self.pool) .await?; if let Some(row) = user { let user_id: Uuid = row.get("id"); let password_hash: String = row.get("password_hash"); let parsed_hash = PasswordHash::new(&password_hash)?; if Argon2::default() .verify_password(password.as_bytes(), &parsed_hash) .is_ok() { // Cache in Redis if let Some(redis) = &self.redis { let cache_key = format!("auth:user:{}", username); let _: () = redis .clone() .set_ex(cache_key, user_id.to_string(), 3600) .await?; } return Ok(Some(user_id)); } } Ok(None) } pub async fn create_user( &self, username: &str, email: &str, password: &str, ) -> Result> { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(password.as_bytes(), &salt)? .to_string(); let user_id = sqlx::query( "INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id", ) .bind(username) .bind(email) .bind(password_hash) .fetch_one(&self.pool) .await? .get("id"); Ok(user_id) } }