Appearance
Architecture Overview
Vue d'ensemble
Le projet suit actuellement une architecture en trois couches:
frontpour l'interface et la session utilisateurauthpour le portail SSO standalone et les sessions multi-comptesbackpour l'API, la validation d'acces et les regles metier- Supabase pour l'auth, le stockage des donnees et les politiques RLS
Frontend
Le frontend Nuxt:
- ouvre une session utilisateur via Supabase Auth
- gere aussi la MFA TOTP Supabase pour les acces console par mot de passe
- recupere le profil utilisateur dans
profiles - conserve le contexte d'entreprise courant
- appelle soit Supabase directement, soit le backend selon le niveau de migration du flux
- n'autorise l'entree console qu'aux roles
super-adminetadministrateur - force la deconnexion des profils
utilisateur - affiche un message de connexion non revelateur en cas de refus ou d'echec:
Identifiant ou mot de passe incorrect. - purge l'etat local lors de la deconnexion, avec fallback si la revocation Supabase echoue
- autorise le
magic linkcomme alternative d'authentification a la place du fluxmot de passe + TOTP
Le portail auth:
- ouvre une session Supabase
- appelle
POST /auth-app/authorizesur le backend - recupere un
app_access_token - redirige vers l'application cliente cible
- expose aussi
/session-statuspour permettre aux applications clientes de verifier si la sessionAUTHexiste encore
Le backend console-admin/back:
- expose
POST /auth-app/introspectpour verifier unapp_access_tokencote serveur-a-serveur - exige le header
x-auth-introspection-secret
Tests E2E frontend:
- Playwright est utilise pour les parcours utilisateurs critiques
- la premiere suite mocke Supabase et le backend pour stabiliser les scenarios
- la CI execute cette suite mockee sur PR et sur push
- une suite live dediee au staging complete la validation reelle
- les rapports sont recuperes via artifacts GitHub Actions, pas via GitHub Pages par defaut
Fichiers importants:
console/composables/useAuth.tsconsole/pages/login.vueconsole/composables/useEntreprises.tsconsole/composables/useEntrepriseContext.tsconsole/playwright.config.tsconsole/playwright.staging.config.tsconsole/e2e/api-keys.spec.tsconsole/e2e/auth-guards.spec.tsconsole/e2e-live/staging-api-keys.spec.tsconsole/e2e-live/staging-auth.spec.ts
Auth et portail SSO
La surface auth/ est une application Vue/Vite separee de la console admin. La meme codebase peut etre servie par deux domaines publics :
auth.*porte la connexion, l'inscription, la deconnexion et les callbacks SSOportail.*porte le portail utilisateur et le profil
Elle sert aux applications Heriade externes:
- memoriser plusieurs sessions Supabase par
accountId - rafraichir silencieusement un compte demande via son
refresh_token - appeler
POST /auth-app/authorize - renvoyer l'utilisateur vers l'application avec un
app_access_token - faire une deconnexion selective via
/logout?accountId=...
Regles de routage:
- un utilisateur non connecte qui arrive sur
portail.*est redirige versauth.*/login - apres connexion portail, la session est transferee vers
portail.*, puis le hash de transfert est nettoye - une deconnexion depuis
portail.*nettoie aussi la session de l'origineauth.* - un flux applicatif avec
returnToreste porte parauth.*, puis revient vers l'application cliente
Fichiers importants:
auth/src/lib/account-sessions.jsauth/src/lib/domains.jsauth/src/lib/session.jsauth/src/pages/LoginPage.vueauth/src/pages/LogoutPage.vueauth/src/pages/PortalPage.vue
| Type de test | Outil | Emplacement | Usage |
|---|---|---|---|
| Backend unitaire/integration | Jest | back/src/**/*.spec.ts | Logique metier et guards |
| Backend e2e HTTP | Jest + Supertest | back/test | Routes NestJS |
| Frontend e2e UI | Playwright | console/e2e | Parcours utilisateurs et permissions |
| Frontend e2e live | Playwright | console/e2e-live | Validation reelle sur staging |
| Suite Playwright | Cible | Mocks | Quand l'executer |
|---|---|---|---|
console/e2e | Front local | Oui | PR, push, debug dev |
console/e2e-live | Staging reel | Non | Avant release, validation staging, workflow manuel |
| Restitution des resultats | Emplacement | Usage |
|---|---|---|
| Rapport HTML local | playwright-report ou playwright-report-live | Analyse locale apres execution |
| Artifact GitHub Actions | Workflow run | Partage equipe et investigation CI |
| Videos / screenshots / traces | test-results | Diagnostic des echecs |
Choix de publication:
- les rapports ne sont pas pushes sur GitHub Pages par defaut
- la raison principale est la prudence sur un projet prive avec donnees, captures et URLs de staging potentiellement sensibles
Backend
Le backend Nest:
- charge
back/.envvia@nestjs/config - verifie le bearer token Supabase
- ou verifie une API key
x-api-keypour certains endpoints - charge le
profileet lerolede l'utilisateur - expose des routes metier protegees
- utilise Supabase cote serveur avec une cle
publishableousecret - expose le domaine
auth-apppour les applications clientes
Fichiers importants:
back/src/supabase/supabase.service.tsback/src/auth-app/auth-app.service.tsback/src/auth-app/auth-app-token.service.tsback/src/auth/guards/supabase-auth.guard.tsback/src/auth/guards/roles.guard.tsback/src/api-keys/api-keys.service.tsback/src/api-keys/guards/composite-auth.guard.tsback/src/api-keys/guards/scopes.guard.tsback/src/api-keys/interceptors/api-key-usage-logging.interceptor.tsback/src/enterprises/enterprises.controller.tsback/src/enterprises/enterprises.service.ts
Authentification et autorisation
Le backend distingue trois notions:
- identite applicative: cle Supabase
publishableousecret - identite utilisateur: token Supabase de session
- identite machine: API key geree par l'application
- autorisation metier:
profiles.role.slugetprofiles.enterprise_id - autorisation technique:
scopesportes par l'API key - autorisation inter-applications:
app_access_tokensigne parauth-app
Regle importante:
- la cle serveur ne remplace jamais le token utilisateur
- le token utilisateur ne remplace jamais la cle applicative
- une API key ne remplace pas un utilisateur console
- une API key ne porte jamais la cle brute en base, seulement un hash HMAC
| Notion | Portee | Exemple |
|---|---|---|
| Identite applicative | Infrastructure | SUPABASE_SECRET_KEY |
| Identite utilisateur | Session console | Bearer Supabase |
| Identite machine | Integration technique | x-api-key |
| Identite application externe | Session courte inter-app | app_access_token |
| Autorisation metier | Role + entreprise | super-admin, administrateur |
| Autorisation technique | Scopes | enterprises:read |
| Autorisation inter-applications | Jeton signe | app_access_token |
Protocole AUTH -> application cliente
Pour une application cliente comme bdd :
- l'application redirige vers le portail
auth - le portail envoie
applicationetenvironmentaPOST /auth-app/authorize - le backend valide l'acces reel cote serveur
- le backend emet un
app_access_token - l'application cliente verifie ensuite ce jeton localement
Dans le cas de bdd, ce jeton applicatif est maintenant la seule preuve d'authentification et d'autorisation attendue cote application.
Le token transporte notamment :
subappaudenventerprise_idaccount_typerole- informations minimales de profil
Le token est signe avec APP_ACCESS_TOKEN_SECRET.
Cohérence de session inter-apps
Le systeme ne repose pas sur un cookie SSO parent-domaine .heriade.fr.
Il repose sur :
- la session Supabase du portail
AUTH(source de vérité) - un
app_access_tokencourt (TTL 300 s) pour chaque application cliente - un refresh silencieux via l'iframe
/session-status(sans rechargement de page) - une révocation persistée en base (
user_token_revocations) avec cache 30 s - une notification proactive
heriade-auth-session:signed-outdiffusée aux apps en cas de déconnexion
Révocation des tokens
La table user_token_revocations(user_id, revoked_after, updated_at) stocke le timestamp de révocation par utilisateur. L'endpoint POST /auth-app/introspect consulte cette table (avec cache 30 s) pour invalider les tokens émis avant la révocation.
La déconnexion Supabase (scope global) invalide le refresh_token Supabase côté serveur. La prochaine tentative de refresh depuis /session-status échoue, retourne authenticated: false, et déclenche la notification SIGNED_OUT aux apps.
Flux complet
mermaid
sequenceDiagram
participant User as Utilisateur
participant App as App cliente
participant Auth as auth/session-status (iframe)
participant Back as back/
participant Sb as Supabase
User->>App: charge l'app
App->>Auth: iframe postMessage check
Auth->>Sb: getSession / refreshSession
Auth-->>App: authenticated=true|false
alt Session active (SSO)
App->>Auth: postMessage refresh + application
Auth->>Back: POST /auth-app/authorize
Back-->>Auth: app_access_token (TTL 300s)
Auth-->>App: appAccessToken + expiresAt
App->>App: token en cache, timer T+240s
else Pas de session
App->>App: redirect auth/login?returnTo=...
end
Note over App,Auth: À T+240s (60s avant expiration)
App->>Auth: postMessage refresh (timer proactif)
Auth-->>App: nouveau appAccessToken
Note over Auth,App: En cas de déconnexion
Sb-->>Auth: onAuthStateChange SIGNED_OUT
Auth-->>App: postMessage signed-out
App->>App: clear session → redirect /loginSDK client (packages/heriade-auth-sdk)
Intégration dans une application consommatrice :
js
import {
buildAppCallbackUrl,
createHeriadeAuthClient,
} from '@heriade-app/auth-sdk'
const auth = createHeriadeAuthClient({
authUrl: 'https://auth.heriade.fr',
apiUrl: 'https://api.console.heriade.fr',
application: 'contacts',
environment: 'default',
storage: 'localStorage',
returnTo: () => buildAppCallbackUrl(location.origin, location.pathname),
onSignedOut: () => window.location.assign('/login'),
})
const ok = await auth.init()
if (!ok) auth.login({ redirectTo: location.pathname })
await auth.authFetch('/api/data')Configuration des apps consommatrices
Chaque app configure AUTH_APP_URL, AUTH_API_URL, APPLICATION et ENVIRONMENT. Le portail valide l'accès applicatif côté backend via /auth-app/authorize, puis le SDK renouvelle le token via /auth-app/refresh.
La documentation d'integration detaillee est dans SDK Auth Heriade.
Limite
Cette solution est propre pour l'architecture actuelle, mais reste une étape intermédiaire.
La cible à terme serait :
- une vraie session maître de domaine
- ou un BFF
AUTHposant un cookie parent-domaine.heriade.fr
Systeme d'API keys
Architecture retenue:
CompositeAuthGuardaccepte soit un bearer Supabase, soit un headerx-api-keyRolesGuardcontinue de raisonner sur unroleSlugderiveScopesGuards'applique uniquement aux principals de typeapi_key- les utilisateurs console gardent un acces complet a leurs routes protegees par role
Stockage:
- table
public.api_keys - table
public.api_key_usage_logs key_hashstocke sous formeHMAC-SHA256- secret serveur dans
API_KEY_SECRET key_prefixstocke pour affichage partiel- un log detaille par appel API key avec
used_at,method,path,status_code,ip
| Composant | Role |
|---|---|
CompositeAuthGuard | Choisit bearer Supabase ou x-api-key |
RolesGuard | Applique les roles derives |
ScopesGuard | Applique les scopes aux principals api_key |
ApiKeyUsageLoggingInterceptor | Journalise les appels authentifies par cle |
Consequence d'exploitation:
- si
API_KEY_SECRETchange, toutes les API keys existantes deviennent invalides - une fuite de base seule ne suffit pas a verifier des cles candidates sans le secret applicatif
Visibilite admin:
- la page super-admin des cles API affiche les metadonnees de la cle
- un panneau "Voir logs" charge les 100 derniers appels pour une cle donnee
- la consultation passe par
GET /api-keys/:id/usage-logs
| Ecran super-admin | Donnees visibles |
|---|---|
| Liste des cles | libelle, prefixe, portee, scopes, expiration, dernier usage, statut |
| Detail des logs | date, methode, route, code HTTP, IP |
Flux technique:
text
Requete entrante
-> CompositeAuthGuard
-> bearer Supabase ? SupabaseAuthGuard
-> sinon x-api-key ? ApiKeyAuthGuard
-> RolesGuard
-> ScopesGuard
-> Controller/Service
-> ApiKeyUsageLoggingInterceptor
-> insertion dans api_key_usage_logsStrategie de migration
Strategie actuelle:
- migrer d'abord les lectures metier a fort impact
- conserver le frontend fonctionnel pendant la transition
- centraliser progressivement les acces sensibles dans
back
Etat du domaine enterprises:
fetchAll()passe parGET /enterprisessurbackGET /enterprisesexclut explicitement les entreprises de typepersonalGET /enterprisesretourne maintenant{ items, page, pageSize, total }fetchById()et les mutations restent cotefrontvers Supabase
Etat du domaine profiles:
- le backend gere maintenant le CRUD metier
auth.users + profiles - les regles de creation, archivage, desarchivage et suppression ont ete rapatriees depuis les fonctions Supabase
- le front utilise deja ces routes pour le CRUD principal utilisateurs
- le front consomme aussi la pagination backend sur les listes actifs/archives via
page,pageSizeetarchived - le changement d'entreprise courant du
super-adminpasse maintenant parPOST /profiles/me/enterprise
Etat du domaine personal-accounts:
- le backend gere maintenant la creation publique des comptes personnels
- la creation publique des comptes personnels passe par
auth.signUppour declencher la validation email Supabase - le backend gere aussi la liste, la mise a jour et la suppression des comptes personnels
- la page
console/pages/comptes-personnels.vueconsomme desormais ces endpoints backend - le role
personnelreste explicitement exclu de l'entree console admin
Etat du domaine organisations/services:
- le front liste maintenant les etablissements et services via les endpoints backend pagines
GET /enterprises/:enterpriseId/organisationsalimente la liste des etablissementsGET /enterprises/:enterpriseId/servicesalimente la liste globale des servicesGET /organisations/:organisationId/servicesreste utilise pour le detail par etablissement
Endpoint profiles | Resource | Roles |
|---|---|---|
GET /profiles/me | Profil courant | super-admin, administrateur, utilisateur |
PATCH /profiles/me | Profil courant | super-admin, administrateur, utilisateur |
POST /profiles/me/email | Changement d'email avec audit | super-admin, administrateur, utilisateur |
POST /profiles/me/password | Changement de mot de passe avec audit | super-admin, administrateur, utilisateur |
GET /enterprises/:enterpriseId/profiles | Liste paginee | super-admin, administrateur |
POST /enterprises/:enterpriseId/profiles | Creation auth.users + profiles | super-admin, administrateur |
GET /profiles/:id | Profil cible | super-admin, administrateur, utilisateur limite a lui-meme |
PATCH /profiles/:id | Profil cible | super-admin, administrateur, utilisateur limite a lui-meme |
POST /profiles/:id/email | Changement d'email avec audit | super-admin, administrateur, utilisateur limite a lui-meme et API keys profiles:write |
POST /profiles/:id/password | Changement de mot de passe avec audit | super-admin, administrateur, utilisateur limite a lui-meme et API keys profiles:write |
POST /profiles/:id/role | Changement de role avec audit | super-admin, administrateur et API keys profiles:write |
DELETE /profiles/:id | Suppression definitive | super-admin, administrateur |
POST /profiles/:id/archive | Archivage via banned_until | super-admin, administrateur |
POST /profiles/:id/unarchive | Desarchivage via banned_until | super-admin, administrateur |
| Contrainte | Regle |
|---|---|
| Changement d'email | Endpoints dedies POST /profiles/me/email et POST /profiles/:id/email avec audit persistant |
| Changement de mot de passe | Endpoints dedies POST /profiles/me/password et POST /profiles/:id/password avec audit persistant |
| Changement de role | Endpoint dedie POST /profiles/:id/role avec audit persistant |
| Reassignment d'entreprise | Exclu des updates pour eviter un transfert de tenancy |
| Auto-suppression / auto-archivage | Interdit |
Quota maxUsers | Controle a la creation et au desarchivage |
Tests
Deux niveaux de tests existent maintenant sur le backend pour enterprises et profiles:
- tests unitaires sur la logique metier du service
- tests d'integration backend avec guards reels et
SupabaseServicemocke
Fichiers:
back/src/enterprises/enterprises.service.spec.tsback/src/enterprises/enterprises.integration.spec.tsback/src/profiles/profiles.service.spec.tsback/src/profiles/profiles.integration.spec.ts
Flux profiles:
text
Bearer Supabase
-> SupabaseAuthGuard
-> RolesGuard
-> ProfilesController
-> ProfilesService
-> lecture/ecriture profiles
-> lecture/ecriture auth.users admin
-> controle de perimetre entreprise
-> controle quota maxUsers si creation ou desarchivage