Gestion des utilisateurs
La gestion des utilisateurs est au coeur de l'administration de BeePass. C'est ici que vous pouvez verifier l'etat d'un compte, investiguer un probleme de connexion, ajuster les permissions d'un eleveur ou reagir a un comportement abusif.
Cette section couvre quatre volets : la liste avec recherche et filtres, la fiche detaillee d'un utilisateur, la validation des demandes de roles proteges et la gestion des sessions.
Liste des utilisateurs
Le tableau principal (/backoffice/users) offre une vue d'ensemble de tous les comptes inscrits sur la plateforme. Il est concu pour repondre rapidement a des questions comme "Quel plan utilise cet eleveur ?", "Quand s'est-il connecte pour la derniere fois ?" ou "Son compte est-il actif ?".
Colonnes affichees :
| Colonne | Description |
|---|---|
| Avatar | Photo de profil ou initiales |
| Nom | profiles.firstname + profiles.lastname |
Adresse email (depuis auth.users via getUserById(), pas depuis profiles) | |
| Code eleveur | Identifiant unique (profiles.breeder_code, ex : FR-44-001) |
| Roles | Badges colores pour chaque role attribue (JSONB profiles.roles) |
| Plan | Abonnement actif : profiles.subscription_plan (discovery, breeder, selector, program) |
| Statut abonnement | profiles.subscription_status (active, trialing, past_due, canceled, unpaid, incomplete) |
| Inscription | Date de creation du compte (profiles.created_at) |
| Derniere connexion | Date du dernier login reussi |
| Statut | Actif, banni (profiles.is_banned) ou en attente de validation |
Recherche et filtres
La barre de recherche permet de filtrer par nom, email ou code eleveur avec un debounce (leger delai avant d'executer la recherche, pour eviter de surcharger le serveur a chaque frappe). Les filtres avances sont combinables :
| Filtre | Valeurs |
|---|---|
| Role | eleveur, testeur, groupe_selection, research_center, admin, super_admin |
| Plan | Discovery, Breeder, Selector, Selection Group |
| Statut | Actif, Banni, En attente |
Actions
| Action | Description | Restriction |
|---|---|---|
| Voir le detail | Ouvre la fiche complete /backoffice/users/[id] | admin |
| Modifier | Edition du profil et des roles | admin |
| Bannir / Debannir | Suspend ou reactive l'acces au compte | admin |
| Supprimer | Suppression definitive avec protocole FK safety | super_admin uniquement |
| Creer un utilisateur | Creation avec mot de passe aleatoire crypto.randomUUID() | super_admin pour roles admin |
| Export CSV | Telechargement du tableau filtre au format CSV | admin |
API Routes
| Route | Methode | Description |
|---|---|---|
/api/admin/users | GET | Tous les profils + emails auth.users, pagination, filtres, recherche |
/api/admin/users | POST | Creation utilisateur (super_admin requis pour roles admin) |
/api/admin/users/[id] | GET | Detail utilisateur enrichi |
/api/admin/users/[id] | PUT | Modification profil et roles |
/api/admin/users/[id] | DELETE | Suppression FK safety (super_admin requis) |
/api/admin/users/[id]/ban | POST | Bannir/debannir (action: ban ou unban) |
Supprimer un utilisateur n'est pas un simple DELETE en base de donnees. Un eleveur peut avoir des reines, des evaluations, des messages de chat, des tickets de support, des fichiers stockes... Tout est lie par des cles etrangeres (FK). Supprimer brutalement le compte casserait ces liens et provoquerait des erreurs en cascade.
C'est pourquoi la suppression suit un protocole strict ou chaque etape est executee dans un ordre precis :
1. Audit log -> event_type = 'account_deletion'
2. Anonymiser les donnees genetiques :
- queens_f0.owner_id + claimed_by -> NULL
- queens_f1.owner_id -> NULL
- queen_evaluations.owner_id -> NULL
- queen_lifecycle_events -> DELETE
- site_settings.updated_by -> NULL
3. Re-claim orphaned queens via claim_unclaimed_queens()
4. Supprimer donnees personnelles :
- notifications, known_devices, verification_codes, profiles -> DELETE
5. Detacher storage : cleanup_storage_owner(user_id) via RPC SECURITY DEFINER
6. deleteUser() -> DELETE FROM auth.users
-> FK CASCADE : chat_*, calendar_*, ticket_attachments
-> FK SET NULL : queens, evaluations (deja nettoyees)
Ne jamais inserer ou supprimer directement dans auth.users via SQL, sous peine de casser listUsers().
Detail utilisateur
La fiche detaillee (/backoffice/users/[id]) rassemble toutes les informations d'un compte en un seul endroit. Elle est organisee en sections avec un header card affichant l'avatar, les badges de roles et les dates cles. C'est la page de reference pour investiguer un probleme lie a un utilisateur specifique.
Informations du profil
| Champ | Colonne DB | Notes |
|---|---|---|
| Nom complet | profiles.firstname + profiles.lastname | -- |
auth.users.email via getUserById() | Jamais depuis profiles | |
| Telephone | profiles.phone | -- |
| Adresse | profiles.address, profiles.city, profiles.country | -- |
| Code postal | profiles.zip_code | -- |
| Code eleveur | profiles.breeder_code | Identifiant unique par eleveur |
| Races travaillees | profiles.races | -- |
| Site web | profiles.website | -- |
| Reseaux sociaux | profiles.facebook, profiles.instagram, profiles.youtube, profiles.tiktok, profiles.x_twitter, profiles.linkedin | Icones Simple Icons colorees |
| Avatar | profiles.avatar | Stocke dans bucket Supabase Storage |
Le champ email n'existe pas dans la table profiles. Il est toujours recupere separement via supabase.auth.admin.getUserById(). Pourquoi cette separation ? Parce que Supabase gere les emails dans sa propre table auth.users, distincte de la table profiles qui contient les informations metier. Ne jamais tenter un SELECT email FROM profiles -- cela casserait silencieusement la requete PostgREST (la requete retourne null au lieu de generer une erreur explicite).
Schema DB profiles
| Colonne | Type | Description |
|---|---|---|
id | UUID PK | FK vers auth.users(id) ON DELETE CASCADE |
firstname | TEXT | Prenom |
lastname | TEXT | Nom de famille |
phone | TEXT | Telephone |
address | TEXT | Adresse postale |
city | TEXT | Ville |
country | TEXT | Pays |
zip_code | TEXT | Code postal |
breeder_code | TEXT | Code eleveur unique |
races | TEXT | Races d'abeilles travaillees |
roles | JSONB | Tableau de roles (ex : ["eleveur", "testeur"]) |
requested_roles | JSONB | Roles en attente de validation admin |
is_banned | BOOLEAN | Compte banni (defaut false) |
avatar | TEXT | Chemin avatar Storage |
subscription_plan | TEXT NOT NULL | Plan actif : discovery, breeder, selector, program (defaut discovery) |
subscription_status | TEXT NOT NULL | Statut Stripe : active, trialing, past_due, canceled, unpaid, incomplete |
stripe_customer_id | TEXT UNIQUE | Stripe Customer ID (cus_...) |
subscription_stripe_id | TEXT | Stripe Subscription ID (sub_...) |
subscription_current_period_end | TIMESTAMPTZ | Fin de la periode de facturation |
subscription_trial_end | TIMESTAMPTZ | Fin de la periode d'essai |
totp_secret | TEXT | Secret TOTP Base32 (NULL si non configure) |
totp_enabled | BOOLEAN NOT NULL | TOTP 2FA active (defaut false) |
totp_enabled_at | TIMESTAMPTZ | Date d'activation TOTP |
notification_prefs | JSONB | Preferences de notifications admin (toggles par type) |
created_at | TIMESTAMPTZ | Date de creation du compte |
updated_at | TIMESTAMPTZ | Derniere modification (trigger auto) |
Gestion des roles
Les roles definissent ce qu'un utilisateur peut faire sur la plateforme. Certains roles sont attribues automatiquement, d'autres necessitent une validation manuelle -- ce systeme a deux niveaux existe pour proteger les fonctionnalites sensibles (acces aux donnees de groupe, outils de recherche) tout en simplifiant l'inscription pour les utilisateurs standards.
L'administrateur peut ajouter ou retirer des roles depuis cette section. Les roles sont stockes dans le champ JSONB profiles.roles.
| Type | Roles | Attribution | Validation |
|---|---|---|---|
| Public | eleveur, testeur | Automatique a l'inscription ou par l'admin | Aucune |
| Protege | groupe_selection, research_center | Demande par l'utilisateur | Validation admin requise |
| Admin | admin, super_admin | Attribution manuelle | Super-admin uniquement |
Chaque changement de role :
- Declenche un evenement
role_changedans le journal d'audit - Est valide cote base de donnees par le trigger
validate_user_roles() - Est notifie a l'utilisateur par email (Brevo) + notification in-app
Le trigger validate_user_roles() empeche les combinaisons invalides et protege les roles admin contre les attributions non autorisees. La fonction get_protected_roles() renvoie la liste des roles proteges incluant admin/super_admin.
Abonnement
Cette section affiche l'etat actuel de l'abonnement Stripe de l'utilisateur. Elle est particulierement utile pour diagnostiquer les problemes de facturation ("Mon plan n'est pas actif" = verifier subscription_status).
| Information | Colonne | Description |
|---|---|---|
| Plan actif | subscription_plan | Nom du plan Stripe en cours |
| Statut | subscription_status | active, trialing, past_due, canceled... |
| Periode fin | subscription_current_period_end | Prochaine date de facturation |
| Fin essai | subscription_trial_end | Date de fin d'essai gratuit |
| Stripe Customer ID | stripe_customer_id | Lien direct vers le client dans le Stripe Dashboard |
Sessions actives
Pouvoir voir et revoquer les sessions d'un utilisateur est essentiel en cas de compromission de compte. Si un eleveur signale un acces suspect, l'admin peut immediatement revoquer toutes les sessions pour forcer une deconnexion globale.
Liste des sessions en cours pour cet utilisateur, geree par les fonctions SQL SECURITY DEFINER (fonctions qui s'executent avec des privileges eleves, permettant d'acceder a la table auth.sessions normalement protegee) :
| Fonction RPC | Description |
|---|---|
get_user_sessions(user_id) | Liste les sessions actives depuis auth.sessions |
revoke_user_session(session_id) | Supprime une session (invalide le refresh token) |
revoke_all_user_sessions(user_id) | Revoque toutes les sessions de l'utilisateur |
Pour chaque session :
- Adresse IP source
- User agent (navigateur / OS)
- Date de derniere activite (heartbeat toutes les 5 minutes via
getUser()dans AuthContext) - Bouton Revoquer pour forcer la deconnexion d'un appareil specifique
La revocation genere un evenement session_revoked dans le journal d'audit.
MFA (Authentification multi-facteur)
L'authentification multi-facteur ajoute une couche de securite au-dela du mot de passe. Meme si le mot de passe d'un utilisateur est compromis, l'attaquant aurait encore besoin du deuxieme facteur (code email ou code TOTP) pour acceder au compte.
BeePass supporte deux niveaux de MFA :
| Type | Cible | Obligatoire | Desactivation |
|---|---|---|---|
| MFA email | Tous les utilisateurs | Sur nouvel appareil | -- |
| TOTP 2FA | Eleveurs (optionnel) | Non | Autorisee |
| TOTP 2FA | Admins | Oui | Interdite (403) |
MFA Email : Lors d'une connexion depuis un appareil inconnu (identifie par un fingerprint base sur le Canvas et le User Agent), un code a 6 chiffres est envoye par email (Brevo). Le code est genere via crypto.randomInt() (generateur cryptographiquement sur), expire en 10 minutes, avec 3 tentatives maximum. La verification est atomique via la RPC increment_verification_attempts (evite les conditions de concurrence si plusieurs requetes arrivent simultanement).
TOTP Eleveur : Activation volontaire depuis SecurityTab. 4 routes API /api/auth/totp/{setup,verify,validate,disable}. Un cookie beepass-totp-verified est emis apres validation.
TOTP Admin : Obligatoire. Pre-auth cookie (10 min TTL) puis validation TOTP avant emission du cookie HMAC admin. 8 backup codes SHA-256 avec salt (table admin_backup_codes). Rate limit : 5 tentatives / 15 min.
Journal d'audit utilisateur
Historique des dernieres actions de securite pour ce compte : connexions, changements de mot de passe, activations MFA, etc. Ce journal est un extrait filtre du journal d'audit global, centre sur un seul utilisateur. Il permet de reconstituer rapidement la chronologie d'un incident.
Validation des roles proteges
Certains roles donnent acces a des fonctionnalites avancees (donnees de groupe, outils de recherche) qui necessitent une verification humaine. Un eleveur qui demande le role groupe_selection doit etre un vrai selectionneur participant a un programme -- pas simplement un curieux. C'est pourquoi ces roles passent par une validation manuelle.
Les roles groupe_selection et research_center necessitent une approbation manuelle de l'administrateur. La page /backoffice/role-validation liste les demandes en attente.
Flux de validation
1. L'utilisateur demande un role protege depuis son profil
|
2. La demande est stockee dans profiles.requested_roles (JSONB)
|
3. Une notification synthetique apparait dans la cloche admin
|
4. L'admin consulte le profil du demandeur (eleveur, code, historique)
|
5. Approbation ou rejet (avec message optionnel)
|
6. L'utilisateur est notifie par email Brevo + notification in-app
Actions disponibles
| Action | Effet | API |
|---|---|---|
| Approuver | Le role est ajoute au JSONB profiles.roles + evenement audit role_change | POST /api/admin/roles |
| Rejeter | La demande est archivee avec le motif de rejet | POST /api/admin/roles |
Les roles eleveur et testeur ne necessitent aucune validation. Ils sont attribues automatiquement a l'inscription ou peuvent etre ajoutes par un admin depuis la fiche utilisateur.
Rate limiting
Pour se proteger contre les attaques par force brute (essayer des milliers de mots de passe) et les abus, les operations d'authentification sont protegees par des limites de debit via Upstash Redis :
| Operation | Limite | Fenetre |
|---|---|---|
| Login eleveur | 10 tentatives | 15 min |
| Login admin | 5 tentatives | 15 min |
| TOTP admin | 5 tentatives | 15 min |
| TOTP eleveur | 5 tentatives | 15 min |
| Inscription | 3 inscriptions | 1 heure |
| Reset mot de passe | 3 demandes | 1 heure |
| Envoi code MFA | 5 envois | 10 min |
| Verification code | 10 verifications | 1 min |
| Creation cle API | 10 creations | 1 heure |
| Renvoi confirmation | 3 renvois | 1 heure |
| Changement mot de passe admin | 3 tentatives | 15 min |
| Suppression utilisateur admin | 5 suppressions | 1 heure |
| Bannissement admin | 10 actions | 15 min |
| Approbation role admin | 10 actions | 1 heure |
| Modification utilisateur admin | 30 modifications | 15 min |
| Modification plan admin | 10 modifications | 1 heure |
Password reset personnalise
Plutot que d'utiliser le flux de reinitialisation par defaut de Supabase (qui offre peu de controle), BeePass utilise un systeme personnalise base sur HMAC-SHA256 (une methode cryptographique qui garantit l'integrite et l'authenticite du token). Ce flux permet un controle total sur les emails envoyes, le rate limiting et la revocation des sessions apres changement.
1. POST /api/auth/password-reset (rate limit 3/h)
|
2. Generation token v2 : userId:expiry:nonce:signature (HMAC-SHA256)
|
3. Email Brevo avec lien contenant le token
|
4. PUT /api/auth/update-password (timingSafeEqual + backend regex validation)
|
5. Revocation de toutes les sessions (revoke_all_user_sessions)
|
6. Nettoyage token de l'URL
Le timing de reponse est normalise (anti-enumeration) : qu'un email existe ou non, le temps de reponse est identique. Cela empeche un attaquant de determiner si un email est inscrit sur la plateforme en mesurant le temps de reponse.
Confirmation email
Depuis AUTH v3.0.0, l'inscription par email utilise un flow de confirmation HMAC personnalise (meme pattern que le password reset) au lieu du flow natif Supabase :
Flux
1. POST /api/auth/register (rate limit 3/h)
|
2. Generation token HMAC : userId:expiry:nonce:signature
|
3. Email Brevo de confirmation avec lien
|
4. GET /api/auth/confirm-email (verification HMAC + activation compte)
|
5. Redirect vers /auth/login avec message de succes
Page confirm-pending
Apres l'inscription, l'utilisateur est redirige vers /auth/confirm-pending qui affiche un message "Verifiez votre email". Le middleware bloque l'acces au dashboard tant que email_confirmed_at est null.
Renvoi
L'endpoint POST /api/auth/resend-confirm permet de renvoyer l'email de confirmation (rate limit 3/h). Le nonce est regenere a chaque renvoi.
Voir aussi : Journal d'audit | Finances & Abonnements | Vue d'ensemble