veza/veza-chat-server/src/message_handler.rs
okinrev b7955a680c P0: stabilisation backend/chat/stream + nouvelle base migrations v1
Backend Go:
- Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN.
- Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError).
- Sécurisation de config.go, CORS, statuts de santé et monitoring.
- Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles).
- Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés.
- Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*.

Chat server (Rust):
- Refonte du pipeline JWT + sécurité, audit et rate limiting avancé.
- Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing).
- Nettoyage des panics, gestion d’erreurs robuste, logs structurés.
- Migrations chat alignées sur le schéma UUID et nouvelles features.

Stream server (Rust):
- Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core.
- Transactions P0 pour les jobs et segments, garanties d’atomicité.
- Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION).

Documentation & audits:
- TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services.
- Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3).
- Scripts de reset et de cleanup pour la lab DB et la V1.

Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 11:14:38 +01:00

330 lines
No EOL
11 KiB
Rust

//! 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<ChatHub>,
permission_service: Arc<PermissionService>,
}
impl MessageHandler {
pub fn new(hub: Arc<ChatHub>, permission_service: Arc<PermissionService>) -> Result<Self> {
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<Uuid>,
) -> 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<Uuid>,
) -> 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<i32>,
session_token: &str,
user_ip: &str,
) -> Result<Vec<crate::hub::channels::RoomMessage>> {
// 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<i32>,
session_token: &str,
user_ip: &str,
) -> Result<Vec<crate::hub::direct_messages::DmMessage>> {
// 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<bool> {
// 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<bool> {
// 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<Uuid> {
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<Uuid> {
// 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
}
}