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.).
259 lines
No EOL
7.4 KiB
Rust
259 lines
No EOL
7.4 KiB
Rust
//! Module d'authentification pour le serveur de chat
|
|
//!
|
|
//! Gère l'authentification des utilisateurs, les sessions et les rôles
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use chrono::{DateTime, Utc};
|
|
use std::collections::HashMap;
|
|
use crate::error::{ChatError, Result};
|
|
|
|
/// Rôles des utilisateurs dans le système de chat
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum Role {
|
|
/// Utilisateur standard
|
|
User,
|
|
/// Modérateur - peut modérer les messages et gérer les salons
|
|
Moderator,
|
|
/// Administrateur - accès complet au système
|
|
Admin,
|
|
/// Utilisateur banni - accès restreint
|
|
Banned,
|
|
}
|
|
|
|
impl Role {
|
|
/// Vérifie si le rôle a les permissions d'administrateur
|
|
pub fn is_admin(&self) -> bool {
|
|
matches!(self, Role::Admin)
|
|
}
|
|
|
|
/// Vérifie si le rôle a les permissions de modérateur ou plus
|
|
pub fn is_moderator_or_above(&self) -> bool {
|
|
matches!(self, Role::Admin | Role::Moderator)
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur est banni
|
|
pub fn is_banned(&self) -> bool {
|
|
matches!(self, Role::Banned)
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur peut envoyer des messages
|
|
pub fn can_send_messages(&self) -> bool {
|
|
!self.is_banned()
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur peut créer des salons
|
|
pub fn can_create_rooms(&self) -> bool {
|
|
matches!(self, Role::Admin | Role::Moderator | Role::User)
|
|
}
|
|
}
|
|
|
|
/// Session utilisateur active
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UserSession {
|
|
/// ID unique de l'utilisateur
|
|
pub user_id: i32,
|
|
/// Nom d'utilisateur
|
|
pub username: String,
|
|
/// Rôle de l'utilisateur
|
|
pub role: Role,
|
|
/// Timestamp de connexion
|
|
pub connected_at: DateTime<Utc>,
|
|
/// Dernière activité
|
|
pub last_activity: DateTime<Utc>,
|
|
/// Adresse IP de connexion
|
|
pub ip_address: String,
|
|
/// User agent du client
|
|
pub user_agent: Option<String>,
|
|
/// Salons auxquels l'utilisateur est connecté
|
|
pub active_rooms: Vec<String>,
|
|
/// Statut de présence
|
|
pub presence_status: PresenceStatus,
|
|
}
|
|
|
|
/// Statut de présence de l'utilisateur
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum PresenceStatus {
|
|
Online,
|
|
Away,
|
|
Busy,
|
|
Offline,
|
|
}
|
|
|
|
impl UserSession {
|
|
/// Crée une nouvelle session utilisateur
|
|
pub fn new(
|
|
user_id: i32,
|
|
username: String,
|
|
role: Role,
|
|
ip_address: String,
|
|
user_agent: Option<String>,
|
|
) -> Self {
|
|
let now = Utc::now();
|
|
Self {
|
|
user_id,
|
|
username,
|
|
role,
|
|
connected_at: now,
|
|
last_activity: now,
|
|
ip_address,
|
|
user_agent,
|
|
active_rooms: Vec::new(),
|
|
presence_status: PresenceStatus::Online,
|
|
}
|
|
}
|
|
|
|
/// Met à jour la dernière activité
|
|
pub fn update_activity(&mut self) {
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
/// Ajoute l'utilisateur à un salon
|
|
pub fn join_room(&mut self, room_id: String) {
|
|
if !self.active_rooms.contains(&room_id) {
|
|
self.active_rooms.push(room_id);
|
|
}
|
|
self.update_activity();
|
|
}
|
|
|
|
/// Retire l'utilisateur d'un salon
|
|
pub fn leave_room(&mut self, room_id: &str) {
|
|
self.active_rooms.retain(|r| r != room_id);
|
|
self.update_activity();
|
|
}
|
|
|
|
/// Change le statut de présence
|
|
pub fn set_presence(&mut self, status: PresenceStatus) {
|
|
self.presence_status = status;
|
|
self.update_activity();
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur est dans un salon spécifique
|
|
pub fn is_in_room(&self, room_id: &str) -> bool {
|
|
self.active_rooms.contains(&room_id.to_string())
|
|
}
|
|
|
|
/// Vérifie si la session est expirée (inactivité > 1 heure)
|
|
pub fn is_expired(&self) -> bool {
|
|
let now = Utc::now();
|
|
let duration = now.signed_duration_since(self.last_activity);
|
|
duration.num_hours() > 1
|
|
}
|
|
}
|
|
|
|
/// Gestionnaire d'authentification
|
|
pub struct AuthManager {
|
|
/// Sessions actives (user_id -> session)
|
|
sessions: HashMap<i32, UserSession>,
|
|
/// Connexions WebSocket (connection_id -> user_id)
|
|
connections: HashMap<String, i32>,
|
|
}
|
|
|
|
impl AuthManager {
|
|
/// Crée un nouveau gestionnaire d'authentification
|
|
pub fn new() -> Self {
|
|
Self {
|
|
sessions: HashMap::new(),
|
|
connections: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Authentifie un utilisateur et crée une session
|
|
pub fn authenticate_user(
|
|
&mut self,
|
|
user_id: i32,
|
|
username: String,
|
|
role: Role,
|
|
connection_id: String,
|
|
ip_address: String,
|
|
user_agent: Option<String>,
|
|
) -> Result<&UserSession> {
|
|
// Créer ou mettre à jour la session
|
|
let session = UserSession::new(user_id, username, role, ip_address, user_agent);
|
|
|
|
// Stocker la session
|
|
self.sessions.insert(user_id, session.clone());
|
|
self.connections.insert(connection_id, user_id);
|
|
|
|
// Récupérer la session insérée (ne peut pas échouer car on vient de l'insérer)
|
|
Ok(self.sessions.get(&user_id).ok_or_else(|| {
|
|
ChatError::internal_error(format!(
|
|
"Session not found after insertion for user_id: {}",
|
|
user_id
|
|
))
|
|
})?)
|
|
}
|
|
|
|
/// Récupère une session par ID utilisateur
|
|
pub fn get_session(&self, user_id: i32) -> Option<&UserSession> {
|
|
self.sessions.get(&user_id)
|
|
}
|
|
|
|
/// Récupère une session par ID de connexion
|
|
pub fn get_session_by_connection(&self, connection_id: &str) -> Option<&UserSession> {
|
|
let user_id = self.connections.get(connection_id)?;
|
|
self.sessions.get(user_id)
|
|
}
|
|
|
|
/// Met à jour l'activité d'une session
|
|
pub fn update_activity(&mut self, user_id: i32) -> Result<()> {
|
|
if let Some(session) = self.sessions.get_mut(&user_id) {
|
|
session.update_activity();
|
|
Ok(())
|
|
} else {
|
|
Err(ChatError::unauthorized("Session non trouvée"))
|
|
}
|
|
}
|
|
|
|
/// Déconnecte un utilisateur
|
|
pub fn disconnect_user(&mut self, connection_id: &str) -> Option<UserSession> {
|
|
if let Some(user_id) = self.connections.remove(connection_id) {
|
|
// Retirer de tous les salons
|
|
if let Some(mut session) = self.sessions.remove(&user_id) {
|
|
session.active_rooms.clear();
|
|
session.presence_status = PresenceStatus::Offline;
|
|
Some(session)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Nettoie les sessions expirées
|
|
pub fn cleanup_expired_sessions(&mut self) -> Vec<i32> {
|
|
let mut expired_users = Vec::new();
|
|
|
|
self.sessions.retain(|&user_id, session| {
|
|
if session.is_expired() {
|
|
expired_users.push(user_id);
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
|
|
// Nettoyer aussi les connexions
|
|
self.connections.retain(|_, &mut user_id| {
|
|
!expired_users.contains(&user_id)
|
|
});
|
|
|
|
expired_users
|
|
}
|
|
|
|
/// Récupère toutes les sessions actives
|
|
pub fn get_active_sessions(&self) -> Vec<&UserSession> {
|
|
self.sessions.values().collect()
|
|
}
|
|
|
|
/// Récupère le nombre de sessions actives
|
|
pub fn active_session_count(&self) -> usize {
|
|
self.sessions.len()
|
|
}
|
|
}
|
|
|
|
impl Default for AuthManager {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|