2025-10-06 10:30:17 -03:00
|
|
|
use argon2::{
|
|
|
|
|
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
|
|
|
|
Argon2,
|
|
|
|
|
};
|
2025-10-11 12:29:03 -03:00
|
|
|
use diesel::pg::PgConnection;
|
2025-10-12 13:27:48 -03:00
|
|
|
use diesel::prelude::*;
|
2025-10-06 10:30:17 -03:00
|
|
|
use redis::Client;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
2025-10-12 13:27:48 -03:00
|
|
|
use crate::shared;
|
|
|
|
|
|
2025-10-06 10:30:17 -03:00
|
|
|
pub struct AuthService {
|
2025-10-11 12:29:03 -03:00
|
|
|
pub conn: PgConnection,
|
2025-10-06 10:30:17 -03:00
|
|
|
pub redis: Option<Arc<Client>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AuthService {
|
2025-10-11 12:29:03 -03:00
|
|
|
pub fn new(conn: PgConnection, redis: Option<Arc<Client>>) -> Self {
|
|
|
|
|
Self { conn, redis }
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 12:29:03 -03:00
|
|
|
pub fn verify_user(
|
|
|
|
|
&mut self,
|
2025-10-06 10:30:17 -03:00
|
|
|
username: &str,
|
|
|
|
|
password: &str,
|
2025-10-06 19:12:13 -03:00
|
|
|
) -> Result<Option<Uuid>, Box<dyn std::error::Error + Send + Sync>> {
|
2025-10-11 12:29:03 -03:00
|
|
|
use crate::shared::models::users;
|
2025-10-12 13:27:48 -03:00
|
|
|
|
2025-10-11 12:29:03 -03:00
|
|
|
let user = users::table
|
|
|
|
|
.filter(users::username.eq(username))
|
|
|
|
|
.filter(users::is_active.eq(true))
|
|
|
|
|
.select((users::id, users::password_hash))
|
|
|
|
|
.first::<(Uuid, String)>(&mut self.conn)
|
|
|
|
|
.optional()?;
|
|
|
|
|
|
|
|
|
|
if let Some((user_id, password_hash)) = user {
|
2025-10-06 10:30:17 -03:00
|
|
|
if let Ok(parsed_hash) = PasswordHash::new(&password_hash) {
|
|
|
|
|
if Argon2::default()
|
|
|
|
|
.verify_password(password.as_bytes(), &parsed_hash)
|
|
|
|
|
.is_ok()
|
|
|
|
|
{
|
|
|
|
|
return Ok(Some(user_id));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 12:29:03 -03:00
|
|
|
pub fn create_user(
|
|
|
|
|
&mut self,
|
2025-10-06 10:30:17 -03:00
|
|
|
username: &str,
|
|
|
|
|
email: &str,
|
|
|
|
|
password: &str,
|
2025-10-06 19:12:13 -03:00
|
|
|
) -> Result<Uuid, Box<dyn std::error::Error + Send + Sync>> {
|
2025-10-11 12:29:03 -03:00
|
|
|
use crate::shared::models::users;
|
|
|
|
|
use diesel::insert_into;
|
2025-10-12 13:27:48 -03:00
|
|
|
|
2025-10-06 10:30:17 -03:00
|
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
|
|
|
let argon2 = Argon2::default();
|
2025-10-12 13:27:48 -03:00
|
|
|
let password_hash = argon2
|
|
|
|
|
.hash_password(password.as_bytes(), &salt)
|
2025-10-11 12:29:03 -03:00
|
|
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?
|
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
|
|
let user_id = Uuid::new_v4();
|
2025-10-12 13:27:48 -03:00
|
|
|
|
2025-10-11 12:29:03 -03:00
|
|
|
insert_into(users::table)
|
|
|
|
|
.values((
|
|
|
|
|
users::id.eq(user_id),
|
|
|
|
|
users::username.eq(username),
|
|
|
|
|
users::email.eq(email),
|
|
|
|
|
users::password_hash.eq(password_hash),
|
|
|
|
|
))
|
|
|
|
|
.execute(&mut self.conn)?;
|
|
|
|
|
|
|
|
|
|
Ok(user_id)
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn delete_user_cache(
|
|
|
|
|
&self,
|
|
|
|
|
username: &str,
|
2025-10-06 19:12:13 -03:00
|
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
2025-10-06 10:30:17 -03:00
|
|
|
if let Some(redis_client) = &self.redis {
|
|
|
|
|
let mut conn = redis_client.get_multiplexed_async_connection().await?;
|
|
|
|
|
let cache_key = format!("auth:user:{}", username);
|
|
|
|
|
|
|
|
|
|
let _: () = redis::Cmd::del(&cache_key).query_async(&mut conn).await?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 12:29:03 -03:00
|
|
|
pub fn update_user_password(
|
|
|
|
|
&mut self,
|
2025-10-06 10:30:17 -03:00
|
|
|
user_id: Uuid,
|
|
|
|
|
new_password: &str,
|
2025-10-06 19:12:13 -03:00
|
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
2025-10-11 12:29:03 -03:00
|
|
|
use crate::shared::models::users;
|
|
|
|
|
use diesel::update;
|
2025-10-12 13:27:48 -03:00
|
|
|
|
2025-10-06 10:30:17 -03:00
|
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
|
|
|
let argon2 = Argon2::default();
|
2025-10-12 13:27:48 -03:00
|
|
|
let password_hash = argon2
|
|
|
|
|
.hash_password(new_password.as_bytes(), &salt)
|
2025-10-11 12:29:03 -03:00
|
|
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?
|
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
|
|
update(users::table.filter(users::id.eq(user_id)))
|
|
|
|
|
.set((
|
|
|
|
|
users::password_hash.eq(&password_hash),
|
|
|
|
|
users::updated_at.eq(diesel::dsl::now),
|
|
|
|
|
))
|
|
|
|
|
.execute(&mut self.conn)?;
|
|
|
|
|
|
|
|
|
|
if let Some(username) = users::table
|
|
|
|
|
.filter(users::id.eq(user_id))
|
|
|
|
|
.select(users::username)
|
|
|
|
|
.first::<String>(&mut self.conn)
|
|
|
|
|
.optional()?
|
2025-10-06 10:30:17 -03:00
|
|
|
{
|
2025-10-11 12:29:03 -03:00
|
|
|
// Note: This would need to be handled differently in async context
|
|
|
|
|
// For now, we'll just log it
|
|
|
|
|
log::info!("Would delete cache for user: {}", username);
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-10-12 13:27:48 -03:00
|
|
|
pub(crate) fn get_user_by_id(
|
|
|
|
|
&mut self,
|
|
|
|
|
uid: Uuid,
|
|
|
|
|
) -> Result<Option<shared::models::User>, Box<dyn std::error::Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::users;
|
|
|
|
|
|
|
|
|
|
let user = users::table
|
|
|
|
|
.filter(users::id.eq(uid))
|
|
|
|
|
.filter(users::is_active.eq(true))
|
|
|
|
|
.first::<shared::models::User>(&mut self.conn)
|
|
|
|
|
.optional()?;
|
|
|
|
|
|
|
|
|
|
Ok(user)
|
|
|
|
|
}
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|