veza/veza-chat-server/docs/CHAT_PERMISSIONS.md
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

8.8 KiB

Système de Permissions du Chat Server

Vue d'ensemble

Le système de permissions du chat server Veza fournit un contrôle d'accès granulaire pour les conversations, avec support des rôles (admin, moderator, member) et vérifications centralisées.

Architecture

Module security/permission.rs

Le module PermissionService centralise toutes les vérifications de permissions :

pub struct PermissionService {
    pool: PgPool,
}

Fonctions principales

user_in_conversation(user_id, conversation_id) -> Result<bool>

Vérifie si un utilisateur est membre d'une conversation.

Retourne : true si membre, false sinon.

user_role_in_conversation(user_id, conversation_id) -> Result<Role>

Récupère le rôle d'un utilisateur dans une conversation spécifique.

Retourne : Le rôle (Admin, Moderator, User, SuperAdmin) ou une erreur si non membre.

user_global_role(user_id) -> Result<Role>

Récupère le rôle global d'un utilisateur depuis la table users.

Retourne : Le rôle global, ou User par défaut.

can_send_message(user_id, conversation_id) -> Result<()>

Vérifie si un utilisateur peut envoyer un message dans une conversation.

Règles :

  • Les membres peuvent envoyer des messages
  • Les admins globaux peuvent envoyer des messages même sans être membres
  • Les non-membres (non-admin) sont refusés

can_read_conversation(user_id, conversation_id) -> Result<()>

Vérifie si un utilisateur peut lire une conversation.

Règles :

  • Les membres peuvent lire
  • Les admins globaux peuvent lire même sans être membres
  • Les non-membres (non-admin) sont refusés

can_mark_read(user_id, conversation_id) -> Result<()>

Vérifie si un utilisateur peut marquer un message comme lu.

Règles : Identiques à can_read_conversation.

can_join_conversation(user_id, conversation_id) -> Result<()>

Vérifie si un utilisateur peut rejoindre une conversation.

Règles :

  • Les conversations publiques peuvent être rejointes par tous
  • Les conversations privées nécessitent d'être membre ou admin global

Rôles et Permissions

Rôles disponibles

Rôle Description
User Utilisateur standard
Moderator Modérateur avec permissions étendues
Admin Administrateur avec tous les pouvoirs
SuperAdmin Super administrateur

Matrice des permissions

Action User Moderator Admin SuperAdmin
Envoyer message (membre)
Envoyer message (non-membre)
Lire conversation (membre)
Lire conversation (non-membre)
Marquer comme lu
Rejoindre conversation publique
Rejoindre conversation privée * *

* Nécessite d'être membre de la conversation

Intégration dans les Handlers

WebSocket Handler (websocket/handler.rs)

Tous les handlers WebSocket vérifient les permissions avant d'exécuter les actions :

SendMessage

// Vérifier les permissions avant d'envoyer le message
state
    .permission_service
    .can_send_message(sender_uuid, conversation_id)
    .await?;

JoinConversation

// Vérifier les permissions avant de rejoindre
state
    .permission_service
    .can_join_conversation(user_uuid, conversation_id)
    .await?;

MarkAsRead

// Vérifier les permissions pour marquer comme lu
state
    .permission_service
    .can_mark_read(user_uuid, conversation_id)
    .await?;

Message Handler (message_handler.rs)

Les handlers de messages vérifient également les permissions :

handle_room_message

Vérifie can_send_message avant d'envoyer un message dans un salon.

handle_direct_message

Vérifie can_send_message avant d'envoyer un message direct.

handle_room_history

Vérifie can_read_conversation via can_read_room_history.

handle_dm_history

Vérifie can_read_conversation via can_read_dm_conversation.

Schéma de Base de Données

Table conversation_members

CREATE TABLE conversation_members (
    conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE,
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    role VARCHAR(50) NOT NULL DEFAULT 'user',
    joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    PRIMARY KEY (conversation_id, user_id)
);

Colonne role : Peut être 'user', 'moderator', 'admin', ou 'superadmin'.

Table users

CREATE TABLE users (
    id UUID PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    role VARCHAR(20) DEFAULT 'user',  -- Rôle global
    ...
);

Colonne role : Rôle global de l'utilisateur dans le système.

Gestion des Erreurs

Types d'erreurs

PermissionError::NotMember

L'utilisateur n'est pas membre de la conversation.

Code HTTP : 403 Forbidden

PermissionError::InsufficientPermissions

L'utilisateur n'a pas les permissions suffisantes pour l'action.

Code HTTP : 403 Forbidden

PermissionError::InvalidRole

Le rôle spécifié est invalide.

Code HTTP : 500 Internal Server Error

Logging

Toutes les violations de permissions sont loggées avec tracing::warn! :

warn!(
    user_id = %user_id,
    conversation_id = %conversation_id,
    error = %e,
    "Permission refusée pour l'envoi de message"
);

Messages WebSocket d'Erreur

Lorsqu'une permission est refusée, le client reçoit un message d'erreur :

{
  "type": "error",
  "message": "Permission refusée: Utilisateur non membre de la conversation",
  "code": "permission_denied"
}

JWT Manager

Le JwtManager a été mis à jour pour récupérer les informations utilisateur depuis la base de données lors du refresh token :

// Récupérer username et role depuis la DB
let user_info: Option<(String, Option<String>)> = sqlx::query_as(
    r#"
    SELECT username, role FROM users
    WHERE id = $1
    "#,
)
.bind(user_uuid)
.fetch_optional(pool)
.await?;

Fallback : Si l'utilisateur n'est pas trouvé ou si le pool DB n'est pas disponible, utilise "user" / "user" par défaut (avec warning).

Tests

Les tests sont disponibles dans tests/test_permissions.rs :

  • test_can_send_message_non_member : Vérifie qu'un non-membre ne peut pas envoyer
  • test_can_send_message_member : Vérifie qu'un membre peut envoyer
  • test_can_send_message_admin_global : Vérifie qu'un admin global peut envoyer sans être membre
  • test_can_read_conversation_non_member : Vérifie qu'un non-membre ne peut pas lire
  • test_can_read_conversation_member : Vérifie qu'un membre peut lire
  • test_user_in_conversation : Vérifie la fonction user_in_conversation
  • test_user_role_in_conversation : Vérifie la fonction user_role_in_conversation
  • test_integration_send_message_with_permissions : Test d'intégration complet

Note : Les tests nécessitent une base de données de test et sont marqués avec #[ignore].

Exemples d'utilisation

Vérifier les permissions avant d'envoyer un message

use chat_server::security::permission::PermissionService;

let permission_service = PermissionService::new(pool);

// Vérifier avant d'envoyer
permission_service
    .can_send_message(user_id, conversation_id)
    .await?;

// Envoyer le message...

Vérifier les permissions avant de lire

// Vérifier avant de lire
permission_service
    .can_read_conversation(user_id, conversation_id)
    .await?;

// Récupérer les messages...

Récupérer le rôle d'un utilisateur

// Rôle dans une conversation spécifique
let role = permission_service
    .user_role_in_conversation(user_id, conversation_id)
    .await?;

// Rôle global
let global_role = permission_service
    .user_global_role(user_id)
    .await?;

Sécurité

Bonnes pratiques

  1. Toujours vérifier les permissions avant d'exécuter une action
  2. Logger les violations pour audit et monitoring
  3. Ne jamais faire confiance au client : toutes les vérifications sont côté serveur
  4. Utiliser le service centralisé : ne pas dupliquer la logique de vérification
  5. Gérer les erreurs gracieusement : envoyer des messages d'erreur clairs au client

Points d'attention

  • Les admins globaux peuvent contourner certaines restrictions (par design)
  • Les conversations privées nécessitent une vérification explicite d'appartenance
  • Le rôle dans conversation_members peut différer du rôle global dans users

Évolution future

  • Support de permissions custom par conversation
  • Permissions granulaires (edit, delete, pin, etc.)
  • Système de rôles hiérarchiques
  • Permissions temporaires (time-based)
  • Audit trail des changements de permissions