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.).
330 lines
No EOL
11 KiB
Rust
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
|
|
}
|
|
}
|