Files
oxspeak_server/.junie/perm.md
T
2026-07-11 03:11:41 +02:00

9.4 KiB
Raw Blame History

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 dun 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 lappartenance dun 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

Lutilisateur 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 quun propriétaire perde accidentellement ses droits.

4. group

Ta table actuelle est déjà proche de ce quil 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

Cest ici que jajouterais 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 dagir 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 lutiliserais 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 laccès au canal est déduit des permissions et de lappartenance au serveur.

Pour les permissions, je préfère user_permission, qui couvre déjà :

  • serveur ;
  • catégorie ;
  • canal.

Sinon tu risques davoir 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 quun 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 lajouterais 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 dun rôle :

id
server_id
name
is_default
server_permissions
channel_permissions
voice_permissions

group_member

Attribution dun 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

Cest le compromis que je choisirais : bitflags conservés, groupes réutilisables, permissions individuelles possibles, et modération limitée par scope.