feat(auth): refactor authentication handler and update logging

- Changed RUST_LOG level from 'trace' to 'debug' in VSCode launch config
- Simplified auth handler by removing direct bot ID lookup logic
- Added HttpRequest parameter to auth handler
- Implemented bot_from_url helper for bot identification
- Updated auth script path to use bot-specific directory structure
- Removed unused warn import
- Improved error handling for auth script reading and execution

The changes streamline the authentication process by:
1. Moving bot identification to a dedicated helper function
2. Making the auth script path dynamic based on bot name
3. Reducing log verbosity in development
4. Cleaning up unused imports and code
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-10-31 15:40:52 -03:00
parent 8c53161129
commit a3555c1a84
5 changed files with 113 additions and 86 deletions

2
.vscode/launch.json vendored
View file

@ -17,7 +17,7 @@
},
"args": [],
"env": {
"RUST_LOG": "trace"
"RUST_LOG": "debug"
},
"cwd": "${workspaceFolder}"
},

View file

@ -1,11 +1,11 @@
use actix_web::{web, HttpResponse, Result};
use actix_web::{HttpRequest, HttpResponse, Result, web};
use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
use diesel::pg::PgConnection;
use diesel::prelude::*;
use log::{error, warn};
use log::{error};
use redis::Client;
use std::collections::HashMap;
use std::sync::Arc;
@ -148,6 +148,7 @@ impl AuthService {
#[actix_web::get("/api/auth")]
async fn auth_handler(
req: HttpRequest,
data: web::Data<AppState>,
web::Query(params): web::Query<HashMap<String, String>>,
) -> Result<HttpResponse> {
@ -166,45 +167,10 @@ async fn auth_handler(
}
};
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"})));
}
}
let mut db_conn = data.conn.lock().unwrap();
let (bot_id, bot_name) = match crate::bot::bot_from_url(&mut *db_conn, req.path()) {
Ok((id, name)) => (id, name),
Err(res) => return Ok(res),
};
let session = {
@ -224,35 +190,40 @@ async fn auth_handler(
}
};
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"})));
let auth_script_path = format!("./work/{}.gbai/{}.gbdialog/auth.ast", bot_name, bot_name);
if std::path::Path::new(&auth_script_path).exists() {
let auth_script = match std::fs::read_to_string(&auth_script_path) {
Ok(content) => content,
Err(e) => {
error!("Failed to read auth script: {}", e);
return Ok(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Failed to read auth script"})));
}
};
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"})));
}
}
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) {
match sm.get_session_by_id(session.id) {
Ok(Some(s)) => s,
Ok(None) => {
error!("Failed to retrieve session");

View file

@ -3,6 +3,7 @@ use crate::shared::models::{BotResponse, UserMessage, UserSession};
use crate::shared::state::AppState;
use actix_web::{web, HttpRequest, HttpResponse, Result};
use actix_ws::Message as WsMessage;
use diesel::PgConnection;
use log::{debug, error, info, warn};
use chrono::Utc;
use serde_json;
@ -826,6 +827,52 @@ impl BotOrchestrator {
}
}
pub fn bot_from_url(
db_conn: &mut PgConnection,
path: &str
) -> Result<(Uuid, String), HttpResponse> {
use crate::shared::models::schema::bots::dsl::*;
use diesel::prelude::*;
// Extract bot name from first path segment
if let Some(bot_name) = path.split('/').nth(1).filter(|s| !s.is_empty()) {
match bots
.filter(name.eq(bot_name))
.filter(is_active.eq(true))
.select((id, name))
.first::<(Uuid, String)>(db_conn)
.optional()
{
Ok(Some((bot_id, bot_name))) => return Ok((bot_id, bot_name)),
Ok(None) => warn!("No active bot found with name: {}", bot_name),
Err(e) => error!("Failed to query bot by name: {}", e),
}
}
// Fall back to first available bot
match bots
.filter(is_active.eq(true))
.select((id, name))
.first::<(Uuid, String)>(db_conn)
.optional()
{
Ok(Some((first_bot_id, first_bot_name))) => {
log::info!("Using first available bot: {} ({})", first_bot_id, first_bot_name);
Ok((first_bot_id, first_bot_name))
}
Ok(None) => {
error!("No active bots found in database");
Err(HttpResponse::ServiceUnavailable()
.json(serde_json::json!({"error": "No bots available"})))
}
Err(e) => {
error!("Failed to query bots: {}", e);
Err(HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Failed to query bots"})))
}
}
}
impl Default for BotOrchestrator {
fn default() -> Self {
Self {
@ -1013,7 +1060,9 @@ async fn websocket_handler(
error!("Error processing WebSocket message {}: {}", message_count, e);
}
}
WsMessage::Close(_) => {
WsMessage::Close(reason) => {
debug!("WebSocket closing for session {} - reason: {:?}", session_id_clone2, reason);
let bot_id = {
use crate::shared::models::schema::bots::dsl::*;
use diesel::prelude::*;
@ -1037,7 +1086,8 @@ async fn websocket_handler(
}
};
orchestrator
debug!("Sending session_end event for {}", session_id_clone2);
if let Err(e) = orchestrator
.send_event(
&user_id_clone,
&bot_id,
@ -1047,12 +1097,19 @@ async fn websocket_handler(
serde_json::json!({}),
)
.await
.ok();
{
error!("Failed to send session_end event: {}", e);
}
debug!("Removing WebSocket connection for {}", session_id_clone2);
web_adapter.remove_connection(&session_id_clone2).await;
debug!("Unregistering response channel for {}", session_id_clone2);
orchestrator
.unregister_response_channel(&session_id_clone2)
.await;
info!("WebSocket fully closed for session {}", session_id_clone2);
break;
}
_ => {}

View file

@ -285,25 +285,21 @@ async fn main() -> std::io::Result<()> {
.wrap(cors)
.wrap(Logger::default())
.wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i"))
.app_data(web::Data::from(app_state_clone));
app = app
.service(upload_file)
.service(index)
.service(static_files)
.service(websocket_handler)
.app_data(web::Data::from(app_state_clone))
.service(auth_handler)
.service(whatsapp_webhook_verify)
.service(chat_completions_local)
.service(create_session)
.service(embeddings_local)
.service(get_session_history)
.service(get_sessions)
.service(index)
.service(start_session)
.service(upload_file)
.service(voice_start)
.service(voice_stop)
.service(create_session)
.service(get_sessions)
.service(start_session)
.service(get_session_history)
.service(chat_completions_local)
.service(embeddings_local)
.service(bot_index); // Must be last - catches all remaining paths
.service(whatsapp_webhook_verify)
.service(websocket_handler);
#[cfg(feature = "email")]
{
app = app
@ -313,9 +309,12 @@ async fn main() -> std::io::Result<()> {
.service(send_email)
.service(save_draft)
.service(save_click);
}
}
app = app.service(static_files);
app = app.service(bot_index);
app
})
.workers(worker_count)
.bind((config.server.host.clone(), config.server.port))?

View file

@ -26,7 +26,7 @@ async fn bot_index(req: HttpRequest) -> Result<HttpResponse> {
}
}
#[actix_web::get("/{filename:.*}")]
#[actix_web::get("/static/{filename:.*}")]
async fn static_files(req: HttpRequest) -> Result<HttpResponse> {
let filename = req.match_info().query("filename");
let path = format!("web/html/{}", filename);