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

533 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 :
```plain text
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 :
```plain text
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.
```plain text
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 :
```plain text
is_admin
is_owner
server_permissions
channel_permissions
voice_permissions
```
Je remplacerais les statuts par des groupes :
```plain text
server_user
-----------
id
server_id
user_id
username
joined_at
updated_at
```
Puis :
```plain text
group_member
------------
group_id
user_id
```
Par exemple :
```plain text
Everyone
Membre
Modérateur
Administrateur
```
Lutilisateur propriétaire pourrait rester une exception structurelle :
```plain text
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 :
```plain text
group
-----
id
server_id
name
is_default
created_at
updated_at
```
Je garderais les trois bitmasks :
```plain text
server_permissions
channel_permissions
voice_permissions
```
Donc :
```rust
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 :
```plain text
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 :
```plain text
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
```plain text
group_member
------------
group_id
user_id
scope_type
scope_id
```
Exemples :
```plain text
Modérateur -> Alice -> server -> serveur A
Modérateur -> Bob -> category -> catégorie Support
Modérateur -> Claire -> channel -> canal Général
```
Avec un enum :
```rust
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
```plain text
group_member
------------
group_id
user_id
server_id
category_id
channel_id
```
Avec la règle :
```plain text
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 :
```plain text
server_user
-----------
server_permissions
channel_permissions
voice_permissions
```
Cela permet :
```plain text
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 :
```plain text
user_permission
---------------
id
user_id
server_id
category_id nullable
channel_id nullable
server_permissions
channel_permissions
voice_permissions
created_at
```
Exemples :
```plain text
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 :
```plain text
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 :
```plain text
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 :
```plain text
server_user.permissions
channel_user.permissions
group.permissions
```
et le calcul deviendra difficile à maintenir.
## 8. Que faire des permissions de `channel` ?
Je supprimerais :
```plain text
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 :
```plain text
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 :
```plain text
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
```plain text
user
server
category
channel
server_user
group
group_member
user_permission
```
### `server_user`
Appartenance au serveur :
```plain text
user_id
server_id
joined_at
```
### `group`
Permissions dun rôle :
```plain text
id
server_id
name
is_default
server_permissions
channel_permissions
voice_permissions
```
### `group_member`
Attribution dun rôle avec périmètre :
```plain text
group_id
user_id
scope_type
scope_id
```
### `user_permission`
Permission individuelle avec périmètre :
```plain text
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é :
```plain text
permissions effectives =
permissions des groupes applicables
| permissions individuelles applicables
```
Les groupes applicables sont ceux dont le scope est :
```plain text
serveur parent
catégorie parente
canal courant
```
Exemple :
```plain text
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 :
```plain text
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 :
```plain text
Administrateur
```
avec les permissions correspondantes.
En revanche, je garderais probablement :
```plain text
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 :
```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 :
```plain text
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 :
```plain text
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**.