Skip to content

Architecture Overview

Vue d'ensemble

Le projet suit actuellement une architecture en trois couches:

  • front pour l'interface et la session utilisateur
  • auth pour le portail SSO standalone et les sessions multi-comptes
  • back pour 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-admin et administrateur
  • 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 link comme alternative d'authentification a la place du flux mot de passe + TOTP

Le portail auth:

  • ouvre une session Supabase
  • appelle POST /auth-app/authorize sur le backend
  • recupere un app_access_token
  • redirige vers l'application cliente cible
  • expose aussi /session-status pour permettre aux applications clientes de verifier si la session AUTH existe encore

Le backend console-admin/back:

  • expose POST /auth-app/introspect pour verifier un app_access_token cote 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.ts
  • console/pages/login.vue
  • console/composables/useEntreprises.ts
  • console/composables/useEntrepriseContext.ts
  • console/playwright.config.ts
  • console/playwright.staging.config.ts
  • console/e2e/api-keys.spec.ts
  • console/e2e/auth-guards.spec.ts
  • console/e2e-live/staging-api-keys.spec.ts
  • console/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 SSO
  • portail.* 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 vers auth.*/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'origine auth.*
  • un flux applicatif avec returnTo reste porte par auth.*, puis revient vers l'application cliente

Fichiers importants:

  • auth/src/lib/account-sessions.js
  • auth/src/lib/domains.js
  • auth/src/lib/session.js
  • auth/src/pages/LoginPage.vue
  • auth/src/pages/LogoutPage.vue
  • auth/src/pages/PortalPage.vue
Type de testOutilEmplacementUsage
Backend unitaire/integrationJestback/src/**/*.spec.tsLogique metier et guards
Backend e2e HTTPJest + Supertestback/testRoutes NestJS
Frontend e2e UIPlaywrightconsole/e2eParcours utilisateurs et permissions
Frontend e2e livePlaywrightconsole/e2e-liveValidation reelle sur staging
Suite PlaywrightCibleMocksQuand l'executer
console/e2eFront localOuiPR, push, debug dev
console/e2e-liveStaging reelNonAvant release, validation staging, workflow manuel
Restitution des resultatsEmplacementUsage
Rapport HTML localplaywright-report ou playwright-report-liveAnalyse locale apres execution
Artifact GitHub ActionsWorkflow runPartage equipe et investigation CI
Videos / screenshots / tracestest-resultsDiagnostic 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/.env via @nestjs/config
  • verifie le bearer token Supabase
  • ou verifie une API key x-api-key pour certains endpoints
  • charge le profile et le role de l'utilisateur
  • expose des routes metier protegees
  • utilise Supabase cote serveur avec une cle publishable ou secret
  • expose le domaine auth-app pour les applications clientes

Fichiers importants:

  • back/src/supabase/supabase.service.ts
  • back/src/auth-app/auth-app.service.ts
  • back/src/auth-app/auth-app-token.service.ts
  • back/src/auth/guards/supabase-auth.guard.ts
  • back/src/auth/guards/roles.guard.ts
  • back/src/api-keys/api-keys.service.ts
  • back/src/api-keys/guards/composite-auth.guard.ts
  • back/src/api-keys/guards/scopes.guard.ts
  • back/src/api-keys/interceptors/api-key-usage-logging.interceptor.ts
  • back/src/enterprises/enterprises.controller.ts
  • back/src/enterprises/enterprises.service.ts

Authentification et autorisation

Le backend distingue trois notions:

  • identite applicative: cle Supabase publishable ou secret
  • identite utilisateur: token Supabase de session
  • identite machine: API key geree par l'application
  • autorisation metier: profiles.role.slug et profiles.enterprise_id
  • autorisation technique: scopes portes par l'API key
  • autorisation inter-applications: app_access_token signe par auth-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
NotionPorteeExemple
Identite applicativeInfrastructureSUPABASE_SECRET_KEY
Identite utilisateurSession consoleBearer Supabase
Identite machineIntegration techniquex-api-key
Identite application externeSession courte inter-appapp_access_token
Autorisation metierRole + entreprisesuper-admin, administrateur
Autorisation techniqueScopesenterprises:read
Autorisation inter-applicationsJeton signeapp_access_token

Protocole AUTH -> application cliente

Pour une application cliente comme bdd :

  1. l'application redirige vers le portail auth
  2. le portail envoie application et environment a POST /auth-app/authorize
  3. le backend valide l'acces reel cote serveur
  4. le backend emet un app_access_token
  5. 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 :

  • sub
  • app
  • aud
  • env
  • enterprise_id
  • account_type
  • role
  • 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_token court (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-out diffusé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 /login

SDK 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 AUTH posant un cookie parent-domaine .heriade.fr

Systeme d'API keys

Architecture retenue:

  • CompositeAuthGuard accepte soit un bearer Supabase, soit un header x-api-key
  • RolesGuard continue de raisonner sur un roleSlug derive
  • ScopesGuard s'applique uniquement aux principals de type api_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_hash stocke sous forme HMAC-SHA256
  • secret serveur dans API_KEY_SECRET
  • key_prefix stocke pour affichage partiel
  • un log detaille par appel API key avec used_at, method, path, status_code, ip
ComposantRole
CompositeAuthGuardChoisit bearer Supabase ou x-api-key
RolesGuardApplique les roles derives
ScopesGuardApplique les scopes aux principals api_key
ApiKeyUsageLoggingInterceptorJournalise les appels authentifies par cle

Consequence d'exploitation:

  • si API_KEY_SECRET change, 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-adminDonnees visibles
Liste des cleslibelle, prefixe, portee, scopes, expiration, dernier usage, statut
Detail des logsdate, 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_logs

Strategie 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 par GET /enterprises sur back
  • GET /enterprises exclut explicitement les entreprises de type personal
  • GET /enterprises retourne maintenant { items, page, pageSize, total }
  • fetchById() et les mutations restent cote front vers 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, pageSize et archived
  • le changement d'entreprise courant du super-admin passe maintenant par POST /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.signUp pour 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.vue consomme desormais ces endpoints backend
  • le role personnel reste 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/organisations alimente la liste des etablissements
  • GET /enterprises/:enterpriseId/services alimente la liste globale des services
  • GET /organisations/:organisationId/services reste utilise pour le detail par etablissement
Endpoint profilesResourceRoles
GET /profiles/meProfil courantsuper-admin, administrateur, utilisateur
PATCH /profiles/meProfil courantsuper-admin, administrateur, utilisateur
POST /profiles/me/emailChangement d'email avec auditsuper-admin, administrateur, utilisateur
POST /profiles/me/passwordChangement de mot de passe avec auditsuper-admin, administrateur, utilisateur
GET /enterprises/:enterpriseId/profilesListe pagineesuper-admin, administrateur
POST /enterprises/:enterpriseId/profilesCreation auth.users + profilessuper-admin, administrateur
GET /profiles/:idProfil ciblesuper-admin, administrateur, utilisateur limite a lui-meme
PATCH /profiles/:idProfil ciblesuper-admin, administrateur, utilisateur limite a lui-meme
POST /profiles/:id/emailChangement d'email avec auditsuper-admin, administrateur, utilisateur limite a lui-meme et API keys profiles:write
POST /profiles/:id/passwordChangement de mot de passe avec auditsuper-admin, administrateur, utilisateur limite a lui-meme et API keys profiles:write
POST /profiles/:id/roleChangement de role avec auditsuper-admin, administrateur et API keys profiles:write
DELETE /profiles/:idSuppression definitivesuper-admin, administrateur
POST /profiles/:id/archiveArchivage via banned_untilsuper-admin, administrateur
POST /profiles/:id/unarchiveDesarchivage via banned_untilsuper-admin, administrateur
ContrainteRegle
Changement d'emailEndpoints dedies POST /profiles/me/email et POST /profiles/:id/email avec audit persistant
Changement de mot de passeEndpoints dedies POST /profiles/me/password et POST /profiles/:id/password avec audit persistant
Changement de roleEndpoint dedie POST /profiles/:id/role avec audit persistant
Reassignment d'entrepriseExclu des updates pour eviter un transfert de tenancy
Auto-suppression / auto-archivageInterdit
Quota maxUsersControle 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 SupabaseService mocke

Fichiers:

  • back/src/enterprises/enterprises.service.spec.ts
  • back/src/enterprises/enterprises.integration.spec.ts
  • back/src/profiles/profiles.service.spec.ts
  • back/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