//! Module de gestion des messages avec filtrage de contenu et sécurité //! //! Ce module fournit une couche de haut niveau pour traiter les messages entrants, //! appliquer les filtres de sécurité et déléguer aux modules métier appropriés. use std::sync::Arc; use tracing::{info, warn}; use uuid::Uuid; use crate::error::{ChatError, Result}; use crate::hub::common::ChatHub; use crate::permissions::Role; use crate::security::{EnhancedSecurity, SecurityAction, ContentFilter}; use crate::security::permission::PermissionService; /// Gestionnaire centralisé pour tous les types de messages pub struct MessageHandler { security: EnhancedSecurity, content_filter: ContentFilter, hub: Arc, permission_service: Arc, } impl MessageHandler { pub fn new(hub: Arc, permission_service: Arc) -> Result { Ok(Self { security: EnhancedSecurity::new()?, content_filter: ContentFilter::new()?, hub, permission_service, }) } /// Gère les messages de salon avec permissions pub async fn handle_room_message( &mut self, user_id: Uuid, username: &str, room: &str, content: &str, session_token: &str, user_ip: &str, parent_id: Option, ) -> Result<()> { // Validation de sécurité self.security.validate_request( user_id, user_ip, session_token, &SecurityAction::SendMessage, Some(content) ).await?; // Filtrage du contenu let clean_room = self.content_filter.validate_content(room)?; let clean_content = self.content_filter.validate_content(content)?; info!( user_id = %user_id, username = %username, room = %clean_room, content_length = %clean_content.len(), "📝 Message de salon filtré et validé" ); // Délégation à la logique métier let room_id = self.get_room_id_by_name(&clean_room).await?; // Vérifier les permissions avant d'envoyer le message self.permission_service .can_send_message(user_id, room_id) .await .map_err(|e| { warn!( user_id = %user_id, room_id = %room_id, error = %e, "Permission refusée pour l'envoi de message dans le salon" ); e })?; crate::hub::channels::send_room_message(&self.hub, room_id, user_id, username, &clean_content, parent_id, None).await?; Ok(()) } /// Gère les messages directs avec permissions pub async fn handle_direct_message( &mut self, from_user: Uuid, from_username: &str, to_user: Uuid, content: &str, session_token: &str, user_ip: &str, parent_id: Option, ) -> Result<()> { // Validation de sécurité self.security.validate_request( from_user, user_ip, session_token, &SecurityAction::SendDM, Some(content) ).await?; // Filtrage du contenu let clean_content = self.content_filter.validate_content(content)?; info!( from_user = %from_user, from_username = %from_username, to_user = %to_user, content_length = %clean_content.len(), "💬 Message direct filtré et validé" ); // Délégation à la logique métier let conversation_id = self.get_or_create_conversation(from_user, to_user).await?; // Vérifier les permissions avant d'envoyer le message self.permission_service .can_send_message(from_user, conversation_id) .await .map_err(|e| { warn!( from_user = %from_user, conversation_id = %conversation_id, error = %e, "Permission refusée pour l'envoi de message direct" ); e })?; crate::hub::direct_messages::send_dm_message(&self.hub, conversation_id, from_user, from_username, &clean_content, parent_id, None).await?; Ok(()) } /// Gère la jointure d'un salon avec permissions pub async fn handle_join_room( &mut self, user_id: Uuid, username: &str, room: &str, session_token: &str, user_ip: &str, ) -> Result<()> { // Validation de sécurité self.security.validate_request( user_id, user_ip, session_token, &SecurityAction::JoinRoom, None ).await?; // Validation du nom de salon let clean_room = self.content_filter.validate_content(room)?; // Vérification que le salon existe ou peut être créé // Pour l'instant, on suppose que le salon existe let room_exists = true; if !room_exists { return Err(ChatError::not_found("Salon", &clean_room)); } info!( user_id = %user_id, username = %username, room = %clean_room, "🚪 Jointure de salon validée" ); // Délégation à la logique métier let room_id = self.get_room_id_by_name(&clean_room).await?; crate::hub::channels::join_room(&self.hub, room_id, user_id).await?; // Envoi de confirmation Ok(()) } /// Gère la récupération d'historique avec permissions pub async fn handle_room_history( &mut self, user_id: Uuid, user_role: &Role, room: &str, limit: Option, session_token: &str, user_ip: &str, ) -> Result> { // Validation de sécurité pour la lecture self.security.validate_request( user_id, user_ip, session_token, &SecurityAction::SendMessage, // Approximation None ).await?; // Validation du nom de salon let clean_room = self.content_filter.validate_content(room)?; let limit = limit.unwrap_or(50).min(100); // Limiter à 100 messages max // Vérification des permissions de lecture if !self.can_read_room_history(user_id, user_role, &clean_room).await? { return Err(ChatError::unauthorized("Lecture de l'historique du salon")); } // Délégation à la logique métier let room_id = self.get_room_id_by_name(&clean_room).await?; let messages = crate::hub::channels::fetch_room_history(&self.hub, room_id, user_id, limit as i64, None).await?; // Envoi de la réponse info!( user_id = %user_id, room = %clean_room, message_count = %messages.len(), "📚 Historique de salon récupéré" ); Ok(messages) } /// Gère la récupération d'historique DM avec permissions pub async fn handle_dm_history( &mut self, user_id: Uuid, with_user: Uuid, limit: Option, session_token: &str, user_ip: &str, ) -> Result> { // Validation de sécurité self.security.validate_request( user_id, user_ip, session_token, &SecurityAction::SendDM, None ).await?; let limit = limit.unwrap_or(50).min(100); // Vérification que l'utilisateur peut lire cette conversation if !self.can_read_dm_conversation(user_id, with_user).await? { return Err(ChatError::unauthorized("Lecture de conversation privée")); } // Délégation à la logique métier let conversation_id = self.get_or_create_conversation(user_id, with_user).await?; let messages = crate::hub::direct_messages::fetch_history(&self.hub, conversation_id, user_id, limit as i64, None).await?; // Envoi de la réponse info!( user_id = %user_id, with_user = %with_user, message_count = %messages.len(), "💬 Historique DM récupéré" ); Ok(messages) } /// Vérifie si un utilisateur peut lire l'historique d'un salon async fn can_read_room_history(&self, user_id: Uuid, user_role: &Role, room: &str) -> Result { // Logique simple : les admins et modérateurs peuvent tout lire match user_role { Role::Admin | Role::Moderator | Role::SuperAdmin => Ok(true), Role::User => { // Les utilisateurs normaux peuvent lire les salons dont ils sont membres let room_id = self.get_room_id_by_name(room).await?; self.permission_service .can_read_conversation(user_id, room_id) .await .map(|_| true) .or_else(|e| { warn!( user_id = %user_id, room = %room, error = %e, "Permission refusée pour la lecture de l'historique" ); Ok(false) }) } _ => Ok(false), } } /// Vérifie si un utilisateur peut lire une conversation DM async fn can_read_dm_conversation(&self, user_id: Uuid, with_user: Uuid) -> Result { // Un utilisateur peut lire ses propres conversations if user_id == with_user { return Ok(true); } // Récupérer ou créer la conversation entre les deux utilisateurs let conversation_id = self.get_or_create_conversation(user_id, with_user).await?; // Vérifier les permissions self.permission_service .can_read_conversation(user_id, conversation_id) .await .map(|_| true) .or_else(|e| { warn!( user_id = %user_id, with_user = %with_user, conversation_id = %conversation_id, error = %e, "Permission refusée pour la lecture de la conversation DM" ); Ok(false) }) } /// Récupère ou crée une conversation entre deux utilisateurs async fn get_or_create_conversation(&self, user1_id: Uuid, user2_id: Uuid) -> Result { let conversation = crate::hub::direct_messages::get_or_create_dm_conversation(&self.hub, user1_id, user2_id).await?; Ok(conversation.id) } /// Récupère l'ID d'un salon par son nom async fn get_room_id_by_name(&self, _room_name: &str) -> Result { // Pour l'instant, retourne un UUID fictif // TODO: Implémenter la recherche d'ID de salon par nom Ok(Uuid::nil()) // UUID nul temporaire - à remplacer par une vraie recherche } }