9.4 KiB
Oui. Si tu gardes les bitflags, je simplifierais tes modèles autour de cette règle :
Les permissions appartiennent aux groupes ou sont accordées directement à un membre, et chaque attribution possède un périmètre.
Je ne mettrais plus les permissions par défaut directement sur server et channel.
1. user
Je garderais quasiment ton modèle actuel :
user
----
id
username
password
pub_key
created_at
updated_at
is_superuser
is_superuser reste un bypass global, indépendant des permissions d’un serveur.
Je ne mettrais pas de permissions directement dans user, car les permissions doivent être contextuelles à un serveur.
2. server
Je supprimerais :
default_server_permissions
default_channel_permissions
default_voice_permissions
Le serveur ne devrait pas porter directement les permissions. Il possède plutôt des groupes.
server
------
id
name
password
created_at
updated_at
is_default
Les permissions par défaut seraient celles du groupe système Everyone.
3. server_user
Je garderais cette table pour l’appartenance d’un utilisateur à un serveur, mais je supprimerais probablement :
is_admin
is_owner
server_permissions
channel_permissions
voice_permissions
Je remplacerais les statuts par des groupes :
server_user
-----------
id
server_id
user_id
username
joined_at
updated_at
Puis :
group_member
------------
group_id
user_id
Par exemple :
Everyone
Membre
Modérateur
Administrateur
L’utilisateur propriétaire pourrait rester une exception structurelle :
server.owner_id
ou être représenté par un groupe système très privilégié. Personnellement, je conserverais owner_id pour éviter qu’un
propriétaire perde accidentellement ses droits.
4. group
Ta table actuelle est déjà proche de ce qu’il faut :
group
-----
id
server_id
name
is_default
created_at
updated_at
Je garderais les trois bitmasks :
server_permissions
channel_permissions
voice_permissions
Donc :
pub struct Model {
pub id: Uuid,
pub server_id: Uuid,
pub name: String,
pub is_default: bool,
pub server_permissions: i64,
pub channel_permissions: i64,
pub voice_permissions: i64,
pub created_at: DateTimeUtc,
}
Chaque groupe est alors un rôle contenant un ensemble de permissions.
Exemples :
Everyone :
READ_CHANNEL
SEND_MESSAGE
JOIN_CHANNEL
SPEAK
Modérateur :
DELETE_OTHERS_MESSAGES
MANAGE_MESSAGES
MUTE_OTHERS
5. group_member
C’est ici que j’ajouterais le périmètre.
Actuellement, un groupe est uniquement attribué à un utilisateur de manière globale au serveur :
group_id
user_id
Pour permettre à un modérateur d’agir seulement dans une catégorie ou un canal, il faut ajouter un scope.
Option que je recommande
group_member
------------
group_id
user_id
scope_type
scope_id
Exemples :
Modérateur -> Alice -> server -> serveur A
Modérateur -> Bob -> category -> catégorie Support
Modérateur -> Claire -> channel -> canal Général
Avec un enum :
pub enum PermissionScopeType {
Server,
Category,
Channel,
}
Le problème est que scope_id peut pointer vers plusieurs tables. Il faudra donc valider la cohérence côté application,
ou utiliser trois colonnes nullable.
Variante plus propre SQL
group_member
------------
group_id
user_id
server_id
category_id
channel_id
Avec la règle :
exactement une portée est définie
Mais cette variante est plus lourde à manipuler.
Pour un projet avec SeaORM, je choisirais probablement scope_type + scope_id, avec validation dans le service de
permissions.
6. Permissions individuelles
Tu as deux possibilités.
Option simple : les stocker dans server_user
Pour une permission individuelle valable sur tout le serveur :
server_user
-----------
server_permissions
channel_permissions
voice_permissions
Cela permet :
Alice possède individuellement SEND_MESSAGE sur le serveur
Mais cette solution ne permet pas facilement une permission individuelle limitée à une catégorie ou un canal.
Option plus flexible : créer user_permission_scope
Je recommande cette table :
user_permission
---------------
id
user_id
server_id
category_id nullable
channel_id nullable
server_permissions
channel_permissions
voice_permissions
created_at
Exemples :
Alice -> SEND_MESSAGE -> serveur A
Bob -> DELETE_OTHERS_MESSAGES -> catégorie Support
Claire -> SEND_MESSAGE -> canal Général
La colonne server_id est utile pour garantir que toutes les ressources appartiennent au bon serveur.
7. Que faire de channel_user ?
Ta table actuelle contient :
channel_user
------------
channel_id
user_id
role
permissions
Je ne garderais pas role et permissions sous cette forme si tu as déjà group et group_member.
Je l’utiliserais uniquement pour représenter une relation particulière au canal, par exemple :
channel_user
------------
id
channel_id
user_id
joined_at
Ou je la supprimerais complètement si l’accès au canal est déduit des permissions et de l’appartenance au serveur.
Pour les permissions, je préfère user_permission, qui couvre déjà :
- serveur ;
- catégorie ;
- canal.
Sinon tu risques d’avoir trois systèmes concurrents :
server_user.permissions
channel_user.permissions
group.permissions
et le calcul deviendra difficile à maintenir.
8. Que faire des permissions de channel ?
Je supprimerais :
default_channel_permissions
default_voice_permissions
Le rôle par défaut Everyone du serveur les remplace.
Si tu veux qu’un canal accorde des permissions supplémentaires, tu peux utiliser une table dédiée :
channel_permission_grant
------------------------
channel_id
group_id nullable
user_id nullable
server_permissions
channel_permissions
voice_permissions
Mais je ne l’ajouterais pas immédiatement. Commence avec :
permissions serveur
permissions de groupe
permissions individuelles
Puis ajoute les overrides de canal uniquement si le besoin apparaît réellement.
Structure que je choisirais maintenant
user
server
category
channel
server_user
group
group_member
user_permission
server_user
Appartenance au serveur :
user_id
server_id
joined_at
group
Permissions d’un rôle :
id
server_id
name
is_default
server_permissions
channel_permissions
voice_permissions
group_member
Attribution d’un rôle avec périmètre :
group_id
user_id
scope_type
scope_id
user_permission
Permission individuelle avec périmètre :
id
user_id
server_id
scope_type
scope_id
server_permissions
channel_permissions
voice_permissions
Calcul des permissions
Pour un utilisateur et un canal donné :
permissions effectives =
permissions des groupes applicables
| permissions individuelles applicables
Les groupes applicables sont ceux dont le scope est :
serveur parent
catégorie parente
canal courant
Exemple :
Alice
Everyone sur le serveur
Modérateur sur la catégorie Support
permission individuelle sur le canal Général
Dans un canal de Support, elle reçoit :
permissions Everyone
| permissions Modérateur
| permissions individuelles du canal
Problème important avec is_admin
Je supprimerais is_admin de server_user.
Un administrateur devient simplement un groupe :
Administrateur
avec les permissions correspondantes.
En revanche, je garderais probablement :
server.owner_id
car le propriétaire est une relation métier particulière, pas seulement un ensemble de permissions.
À propos de i64
Puisque tu stockes des valeurs u64 dans des colonnes i64, je ferais au minimum une abstraction claire côté Rust :
fn permissions_from_db(value: i64) -> u64 {
value as u64
}
Et j’éviterais de disperser les conversions dans les modèles.
Si tu ne comptes pas utiliser le bit de poids fort, tu peux aussi décider officiellement que seuls les 63 premiers bits sont valides.
Recommandation finale
Je modifierais ton système comme ceci :
Supprimer :
permissions de server
permissions de channel
is_admin de server_user
permissions de channel_user
role texte de channel_user
Garder :
is_superuser sur user
owner_id sur server
trois bitflags sur group
Ajouter :
scope aux attributions de groupes
table user_permission pour les permissions individuelles
Le modèle deviendrait :
User
├── appartient à des serveurs
├── reçoit des groupes
└── peut recevoir des permissions individuelles
Group
└── possède trois bitflags
GroupMember
└── associe un groupe à un utilisateur dans un scope
UserPermission
└── associe directement des bitflags à un utilisateur dans un scope
C’est le compromis que je choisirais : bitflags conservés, groupes réutilisables, permissions individuelles possibles, et modération limitée par scope.