botserver/src/auth/mod.rs

276 lines
9.1 KiB
Rust
Raw Normal View History

2025-10-18 12:01:39 -03:00
use actix_web::{web, HttpResponse, Result};
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-18 12:01:39 -03:00
use log::{error, warn};
2025-10-06 10:30:17 -03:00
use redis::Client;
2025-10-18 12:01:39 -03:00
use std::collections::HashMap;
2025-10-06 10:30:17 -03:00
use std::sync::Arc;
use uuid::Uuid;
2025-10-12 13:27:48 -03:00
use crate::shared;
2025-10-18 12:01:39 -03:00
use crate::shared::state::AppState;
2025-10-12 13:27:48 -03:00
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,
2025-10-12 13:27:48 -03:00
) -> Result<Option<shared::models::User>, Box<dyn std::error::Error + Send + Sync>> {
use crate::shared::models::users;
let user = users::table
// TODO: .filter(users::id.eq(uid))
2025-10-12 13:27:48 -03:00
.filter(users::is_active.eq(true))
.first::<shared::models::User>(&mut self.conn)
.optional()?;
Ok(user)
}
2025-10-06 10:30:17 -03:00
}
2025-10-18 12:01:39 -03:00
#[actix_web::get("/api/auth")]
async fn auth_handler(
data: web::Data<AppState>,
web::Query(params): web::Query<HashMap<String, String>>,
) -> Result<HttpResponse> {
let _token = params.get("token").cloned().unwrap_or_default();
// Create or get anonymous user with proper UUID
let user_id = {
let mut sm = data.session_manager.lock().await;
match sm.get_or_create_anonymous_user(None) {
Ok(uid) => uid,
Err(e) => {
error!("Failed to create anonymous user: {}", e);
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Failed to create user"})));
}
}
};
2025-10-18 12:01:39 -03:00
let bot_id = if let Ok(bot_guid) = std::env::var("BOT_GUID") {
match Uuid::parse_str(&bot_guid) {
Ok(uuid) => uuid,
Err(e) => {
warn!("Invalid BOT_GUID from env: {}", e);
return Ok(HttpResponse::BadRequest()
.json(serde_json::json!({"error": "Invalid BOT_GUID"})));
}
}
} else {
// BOT_GUID not set, get first available bot from database
use crate::shared::models::schema::bots::dsl::*;
use diesel::prelude::*;
let mut db_conn = data.conn.lock().unwrap();
match bots
.filter(is_active.eq(true))
.select(id)
.first::<Uuid>(&mut *db_conn)
.optional()
{
Ok(Some(first_bot_id)) => {
log::info!(
"BOT_GUID not set, using first available bot: {}",
first_bot_id
);
first_bot_id
}
Ok(None) => {
error!("No active bots found in database");
return Ok(HttpResponse::ServiceUnavailable()
.json(serde_json::json!({"error": "No bots available"})));
}
Err(e) => {
error!("Failed to query bots: {}", e);
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Failed to query bots"})));
}
}
2025-10-18 12:01:39 -03:00
};
let session = {
let mut sm = data.session_manager.lock().await;
match sm.get_or_create_user_session(user_id, bot_id, "Auth Session") {
Ok(Some(s)) => s,
Ok(None) => {
error!("Failed to create session");
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Failed to create session"})));
}
Err(e) => {
error!("Failed to create session: {}", e);
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": e.to_string()})));
}
}
};
let session_id_clone = session.id.clone();
let auth_script_path = "./templates/annoucements.gbai/annoucements.gbdialog/auth.bas";
let auth_script = match std::fs::read_to_string(auth_script_path) {
Ok(content) => content,
Err(_) => r#"SET_USER "00000000-0000-0000-0000-000000000001""#.to_string(),
};
let script_service = crate::basic::ScriptService::new(Arc::clone(&data), session.clone());
match script_service
.compile(&auth_script)
.and_then(|ast| script_service.run(&ast))
{
Ok(result) => {
if result.to_string() == "false" {
error!("Auth script returned false, authentication failed");
return Ok(HttpResponse::Unauthorized()
.json(serde_json::json!({"error": "Authentication failed"})));
}
}
Err(e) => {
error!("Failed to run auth script: {}", e);
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Auth failed"})));
}
}
let session = {
let mut sm = data.session_manager.lock().await;
match sm.get_session_by_id(session_id_clone) {
Ok(Some(s)) => s,
Ok(None) => {
error!("Failed to retrieve session");
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Failed to retrieve session"})));
}
Err(e) => {
error!("Failed to retrieve session: {}", e);
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": e.to_string()})));
}
}
};
Ok(HttpResponse::Ok().json(serde_json::json!({
"user_id": session.user_id,
"session_id": session.id,
"status": "authenticated"
})))
}