gbserver/src/auth/mod.rs

91 lines
2.7 KiB
Rust
Raw Normal View History

2025-10-04 20:42:49 -03:00
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<Arc<ConnectionManager>>,
}
impl AuthService {
pub fn new(pool: PgPool, redis: Option<Arc<ConnectionManager>>) -> Self {
Self { pool, redis }
}
pub async fn verify_user(
&self,
username: &str,
password: &str,
) -> Result<Option<Uuid>, Box<dyn std::error::Error>> {
// 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<Uuid, Box<dyn std::error::Error>> {
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)
}
}