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.).
328 lines
8.8 KiB
Markdown
328 lines
8.8 KiB
Markdown
# 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 :
|
|
|
|
```rust
|
|
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`
|
|
|
|
```rust
|
|
// Vérifier les permissions avant d'envoyer le message
|
|
state
|
|
.permission_service
|
|
.can_send_message(sender_uuid, conversation_id)
|
|
.await?;
|
|
```
|
|
|
|
#### `JoinConversation`
|
|
|
|
```rust
|
|
// Vérifier les permissions avant de rejoindre
|
|
state
|
|
.permission_service
|
|
.can_join_conversation(user_uuid, conversation_id)
|
|
.await?;
|
|
```
|
|
|
|
#### `MarkAsRead`
|
|
|
|
```rust
|
|
// 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`
|
|
|
|
```sql
|
|
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`
|
|
|
|
```sql
|
|
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!` :
|
|
|
|
```rust
|
|
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 :
|
|
|
|
```json
|
|
{
|
|
"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 :
|
|
|
|
```rust
|
|
// 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
|
|
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
// 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
|
|
|
|
```rust
|
|
// 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
|
|
|