Appearance
Profiles Domain
Definition
Le domaine profiles couvre une ressource composite:
- le compte utilisateur dans
auth.users - le profil applicatif dans
public.profiles
Le backend expose cette ressource pour centraliser les regles d'acces, le controle d'entreprise et les operations sensibles de cycle de vie utilisateur.
References:
back/src/profiles/profiles.controller.tsback/src/profiles/profiles.service.ts
Donnees exposees
Partie auth.users
| Champ | Type | Usage |
|---|---|---|
id | string | Identifiant auth Supabase |
email | string | null | Email de connexion |
lastSignInAt | string | null | Derniere connexion connue |
bannedUntil | string | null | Date d'archivage technique |
isArchived | boolean | Derive de bannedUntil |
Partie profiles
| Champ | Type | Modifiable |
|---|---|---|
id | number | Non |
uuid | string | Non |
userId | string | Non |
username | string | null | Oui |
firstname | string | null | Oui |
lastname | string | null | Oui |
matricule | string | null | Oui |
address | string | null | Oui |
enterpriseId | number | Non |
photoId | string | null | Oui |
photoUrl | string | null | Non, derive par l'API |
roleId | number | null | Non dans cette V1 |
job | string | null | Oui |
createdAt | string | Non |
updatedAt | string | Non |
Champs explicitement hors perimetre des updates:
emailpasswordroleIdenterpriseId
Le changement d'email passe par des endpoints dedies avec audit:
POST /profiles/me/emailPOST /profiles/:id/email
Le changement de mot de passe passe par des endpoints dedies avec audit:
POST /profiles/me/passwordPOST /profiles/:id/password
Le changement de role passe par un endpoint dedie avec audit:
POST /profiles/:id/role
La double authentification console:
- ne passe pas par une API backend dediee
- est geree directement par Supabase Auth MFA cote frontend
- utilise un facteur
TOTPvia application d'authentification - se pilote depuis
Mon Profil - s'applique au flux de connexion
email + mot de passe - le
magic linkreste une alternative de connexion sans etape TOTP supplementaire - le nom affiche dans l'application TOTP est configurable via
NUXT_PUBLIC_MFA_ISSUERouVITE_MFA_ISSUER - le nom amical du facteur est configurable via
NUXT_PUBLIC_MFA_FRIENDLY_NAMEouVITE_MFA_FRIENDLY_NAME
Regles d'acces
| Endpoint | super-admin | administrateur | utilisateur |
|---|---|---|---|
GET /profiles/me | Oui | Oui | Oui |
PATCH /profiles/me | Oui | Oui | Oui |
POST /profiles/me/email | Oui | Oui | Oui |
POST /profiles/me/password | Oui | Oui | Oui |
POST /profiles/me/photo | Oui | Oui | Oui |
DELETE /profiles/me/photo | Oui | Oui | Oui |
POST /profiles/me/enterprise | Oui, uniquement super-admin | Non | Non |
GET /enterprises/:enterpriseId/profiles | Oui | Oui, si enterpriseId == profile.enterprise_id | Non |
POST /enterprises/:enterpriseId/profiles | Oui | Oui, si enterpriseId == profile.enterprise_id | Non |
GET /profiles/:id | Oui | Oui, si le profil cible est sur son entreprise | Oui, pour lui-meme uniquement |
PATCH /profiles/:id | Oui | Oui, si le profil cible est sur son entreprise | Oui, pour lui-meme uniquement |
POST /profiles/:id/email | Oui | Oui, si le profil cible est sur son entreprise | Oui, pour lui-meme uniquement |
POST /profiles/:id/password | Oui | Oui, si le profil cible est sur son entreprise | Oui, pour lui-meme uniquement |
POST /profiles/:id/role | Oui | Oui, si le profil cible est sur son entreprise | Non |
POST /profiles/:id/photo | Oui | Oui, si le profil cible est sur son entreprise | Oui, pour lui-meme uniquement |
DELETE /profiles/:id/photo | Oui | Oui, si le profil cible est sur son entreprise | Oui, pour lui-meme uniquement |
DELETE /profiles/:id | Oui | Oui, si le profil cible est sur son entreprise | Non |
POST /profiles/:id/archive | Oui | Oui, si le profil cible est sur son entreprise | Non |
POST /profiles/:id/unarchive | Oui | Oui, si le profil cible est sur son entreprise | Non |
POST /profiles/bulk/archive | Oui | Oui, pour des profils de son entreprise uniquement | Non |
POST /profiles/bulk/delete | Oui | Oui, pour des profils de son entreprise uniquement | Non |
Contraintes fortes:
- pas d'auto-suppression
- pas d'auto-archivage
- pas d'auto-desarchivage
- pas d'auto-changement de role
- un
administrateurne peut pas creer un comptesuper-admin - un
administrateurpeut attribuer uniquementadministrateur,valideur,utilisateur - un
administrateurne peut pas modifier un profil actuellementsuper-admin - un
administrateurne peut pas changer le mot de passe d'un profil actuellementsuper-admin - quand un profil devient
administrateurousuper-admin, ses rattachementsprofiles_organisationsetprofiles_servicessont purges - les routes bulk valident tous les profils cibles avant de commencer la mutation
- les routes bulk sont disponibles par API key avec le scope
profiles:write
Endpoints
Lire son propre profil
| Endpoint | Auth | Description |
|---|---|---|
GET /profiles/me | Bearer Supabase | Retourne la ressource composite du user connecte |
Exemple de reponse:
json
{
"user": {
"id": "f7a7d6ce-4fd8-4ca8-8f8d-5d9d4f6f9d8c",
"email": "[email protected]",
"lastSignInAt": "2026-04-04T08:30:00.000Z",
"bannedUntil": null,
"isArchived": false
},
"profile": {
"id": 10,
"uuid": "6a2e7e98-6fb4-4a5e-8a36-7f3b9899e5ec",
"userId": "f7a7d6ce-4fd8-4ca8-8f8d-5d9d4f6f9d8c",
"username": "jdoe",
"firstname": "John",
"lastname": "Doe",
"matricule": "M-001",
"address": "1 rue de Paris",
"enterpriseId": 66,
"photoId": null,
"photoUrl": null,
"roleId": 3,
"job": "Nurse",
"createdAt": "2026-04-04T08:30:00.000Z",
"updatedAt": "2026-04-04T08:30:00.000Z",
"role": {
"id": 3,
"name": "Utilisateur",
"slug": "utilisateur"
},
"enterprise": {
"id": 66,
"name": "Heriade",
"type": "enterprise"
}
}
}Mettre a jour son profil
| Endpoint | Auth | Description |
|---|---|---|
PATCH /profiles/me | Bearer Supabase | Met a jour les champs profil autorises |
POST /profiles/me/email | Bearer Supabase | Change l'email du profil courant avec audit |
POST /profiles/me/password | Bearer Supabase | Change le mot de passe du profil courant avec audit |
POST /profiles/me/photo | Bearer Supabase | Upload multipart de la photo du profil courant |
DELETE /profiles/me/photo | Bearer Supabase | Supprime la photo du profil courant |
POST /profiles/me/enterprise | Bearer Supabase super-admin uniquement | Change l'entreprise courante du super-admin et purge ses rattachements metier |
Payload autorise:
json
{
"username": "jdoe",
"firstname": "John",
"lastname": "Doe",
"matricule": "M-001",
"address": "1 rue de Paris",
"job": "Nurse",
"organisationIds": [11, 12],
"serviceIds": [21]
}Upload photo:
- champ multipart attendu:
file - formats acceptes:
JPG,PNG,WEBP - taille max:
5 Mo
Payload pour le changement d'entreprise courant:
json
{
"enterpriseId": 66
}Pour desaffecter l'entreprise courante:
json
{
"enterpriseId": null
}Contraintes:
- endpoint reserve au
super-admin - endpoint reserve a un bearer utilisateur; les API keys ne peuvent pas appeler
/profiles/me/* - les scopes API key ne s'appliquent donc pas a cette route
Payload pour le changement d'email:
json
{
"email": "[email protected]"
}Payload pour le changement de mot de passe:
json
{
"newPassword": "StrongPassword123!"
}Lister les profils d'une entreprise
| Endpoint | Auth | Description |
|---|---|---|
GET /enterprises/:enterpriseId/profiles?page=1&pageSize=20 | Bearer Supabase | Retourne une liste paginee de ressources composites |
GET /enterprises/:enterpriseId/profiles?page=1&pageSize=20&archived=false | Bearer Supabase | Retourne uniquement les profils actifs |
GET /enterprises/:enterpriseId/profiles?page=1&pageSize=20&archived=true | Bearer Supabase | Retourne uniquement les profils archives |
Reponse:
json
{
"items": [],
"page": 1,
"pageSize": 20,
"total": 0
}Creer un utilisateur et son profil
| Endpoint | Auth | Description |
|---|---|---|
POST /enterprises/:enterpriseId/profiles | Bearer Supabase | Cree auth.users puis profiles |
Payload V1:
json
{
"email": "[email protected]",
"password": "StrongPassword123!",
"username": "nuser",
"firstname": "New",
"lastname": "User",
"matricule": "M-002",
"address": "2 rue de Lyon",
"photoId": null,
"job": "Caregiver",
"roleId": 3
}Archiver ou supprimer plusieurs utilisateurs
| Endpoint | Auth | Description |
|---|---|---|
POST /profiles/bulk/archive | Bearer Supabase ou API key profiles:write | Archive plusieurs profils gerables en une seule operation |
POST /profiles/bulk/delete | Bearer Supabase ou API key profiles:write | Supprime plusieurs profils gerables en une seule operation |
Payload:
json
{
"profileIds": [101, 103]
}Reponse:
json
{
"success": true,
"count": 2
}Regles de creation:
emailetpasswordsont obligatoiresroleIdest autorise a la creation uniquement- un
administrateurne peut pas assigner le rolesuper-admin - le quota
enterprise.maxUsersest controle avant creation, hors comptessuper-admin
Lire ou modifier un profil cible
| Endpoint | Auth | Description |
|---|---|---|
GET /profiles/:id | Bearer Supabase | Retourne un profil cible selon le perimetre autorise |
PATCH /profiles/:id | Bearer Supabase | Met a jour un profil cible selon le perimetre autorise |
POST /profiles/:id/email | Bearer Supabase ou x-api-key | Change l'email d'un profil cible avec audit |
POST /profiles/:id/password | Bearer Supabase ou x-api-key | Change le mot de passe d'un profil cible avec audit |
POST /profiles/:id/role | Bearer Supabase ou x-api-key | Change le role d'un profil cible avec audit |
POST /profiles/:id/photo | Bearer Supabase | Ajoute ou remplace une photo de profil cible |
DELETE /profiles/:id/photo | Bearer Supabase | Supprime une photo de profil cible |
Payload pour le changement de mot de passe cible:
json
{
"newPassword": "StrongPassword123!"
}Payload pour le changement de role:
json
{
"roleId": 4
}Archiver, desarchiver, supprimer
| Endpoint | Effet |
|---|---|
POST /profiles/:id/archive | Met a jour auth.users.banned_until |
POST /profiles/:id/unarchive | Retire banned_until si le quota le permet |
DELETE /profiles/:id | Supprime auth.users, profiles, profiles_organisations, profiles_services et l'entreprise personnelle associee si presente |
Pagination
La pagination s'applique sur:
GET /enterprisesGET /enterprises/:enterpriseId/profiles
| Parametre | Defaut | Max |
|---|---|---|
page | 1 | n/a |
pageSize | 20 | 100 |
Filtre additionnel sur les profils:
| Parametre | Valeurs | Effet |
|---|---|---|
archived | true / false | Filtre les profils selon l'etat d'archivage (auth.users.banned_until) |
Regles metier detaillees
Changement d'entreprise du super-admin
Quand un super-admin change d'entreprise courante:
- son
profile.enterprise_idest mis a jour - ses liaisons
profiles_organisationssont supprimees - ses liaisons
profiles_servicessont supprimees - le front passe desormais par
POST /profiles/me/enterprise, plus par des writes Supabase directes
Objectif:
- eviter tout reliquat de contexte metier issu de l'entreprise precedente
- garantir qu'un
super-adminnouvellement rattache a une entreprise ne conserve aucun etablissement ou service personnel associe
Quota utilisateurs
Le backend controle enterprise.maxUsers:
- a la creation d'un utilisateur
- au desarchivage d'un utilisateur
Les comptes super-admin sont exclus du quota.
Validation des rattachements metier
Quand organisationIds et serviceIds sont fournis sur les endpoints de creation ou mise a jour profil:
- chaque etablissement doit appartenir a l'entreprise du profil
- chaque service doit appartenir a l'entreprise du profil
- chaque service doit etre lie a au moins un des etablissements selectionnes
- la synchronisation des tables
profiles_organisationsetprofiles_servicesest faite par le backend
Stockage des photos
Les photos de profil sont stockees dans Supabase Storage.
- bucket par defaut:
profile-photos - variable backend:
SUPABASE_PROFILE_PHOTOS_BUCKET - mode recommande: bucket prive
- l'API genere une
photoUrlsignee a chaque lecture profil profiles.photo_idstocke l'identifiant technique UUID de la photo- le chemin storage est derive par le backend sous la forme
<user_id>/<photo_id>
Audit de changement d'email
Le changement d'email passe par un audit persistant dans public.profile_email_change_audits.
Chaque enregistrement contient notamment:
- l'acteur (
userouapi_key) actor_user_idouactor_api_key_idactor_role_slugtarget_profile_idtarget_user_idold_emailnew_emailstatus(successoufailed)error_message
Bonne pratique appliquee ici:
- ne pas lier fortement cette table aux comptes utilisateurs pour conserver l'historique meme si le createur ou la cible sont supprimes plus tard
Audit de changement de role
Le changement de role passe par un audit persistant dans public.profile_role_change_audits.
Chaque enregistrement contient notamment:
- l'acteur (
userouapi_key) actor_user_idouactor_api_key_idactor_role_slugtarget_profile_idtarget_user_idold_role_idold_role_slugnew_role_idnew_role_slugstatus(successoufailed)error_message
Audit de changement de mot de passe
Le changement de mot de passe passe par un audit persistant dans public.profile_password_change_audits.
Chaque enregistrement contient notamment:
- l'acteur (
userouapi_key) actor_user_idouactor_api_key_idactor_role_slugtarget_profile_idtarget_user_idstatus(successoufailed)error_message
Important:
- aucun mot de passe en clair n'est journalise
- l'audit trace seulement qui a change le mot de passe de quel profil, et avec quel resultat
Archivage
L'archivage n'est pas une suppression logique dans profiles.
Le mecanisme retenu est:
- ecriture sur
auth.users.banned_until - calcul de
isArchiveddans la reponse backend
Suppression definitive
La suppression est destructive et suit cet ordre:
- suppression des relations
profiles_organisations - suppression des relations
profiles_services - suppression des lignes
profiles - suppression du compte
auth.users - suppression de l'entreprise personnelle si elle existe encore
Reaffectation d'etablissements
Quand un utilisateur est rattache a un autre ensemble d'etablissements dans le formulaire console:
- les
service_idsdu formulaire sont reinitialises - l'operateur doit reselectionner explicitement les services compatibles
Cette regle est volontairement stricte:
- elle evite de conserver des services devenus invalides apres un changement d'etablissements
- elle rend la reaffectation explicite au lieu de tenter une conservation partielle implicite
Flux technique
text
Bearer Supabase
-> SupabaseAuthGuard
-> RolesGuard
-> ProfilesController
-> ProfilesService
-> controle role + entreprise
-> lecture/ecriture profiles
-> lecture/ecriture auth.users admin
-> controle quota si creation ou desarchivageTests
| Niveau | Fichier | Couverture |
|---|---|---|
| Unitaire | back/src/profiles/profiles.service.spec.ts | pagination, perimetre entreprise, auto-archivage interdit |
| Integration | back/src/profiles/profiles.integration.spec.ts | guards reels, /profiles/me, listing entreprise, blocage utilisateur |
| E2E console | console/e2e/utilisateurs.spec.ts | purge des affectations du super-admin au changement d'entreprise, reinitialisation des services lors d'un changement d'etablissements |
Points de vigilance
- le front n'utilise pas encore ces endpoints
organisation_idsetservice_idsne sont pas exposes dans cette V1 backend- le changement de mot de passe et le changement de role restent separes des updates profil generiques
