Aller au contenu

Authentification & gardes

JWT access + refresh

L'authentification repose sur des JWT (jsonwebtoken) : un access token de courte durée transmis dans l'en-tête Authorization: Bearer <token>, et un refresh token pour obtenir un nouvel access token. Les routes d'auth (/api/auth/*) sont soumises à un rate limiting plus strict (THROTTLER_AUTH_LIMIT).

Un échec d'authentification lève AuthenticationError401, avec un code machine qui pilote la réaction du client (refresh, sélection de workspace, activation…). Voir le contrat complet dans Réponses API.

Inscription puis onboarding

POST /api/auth/register ne demande pas de devise et ne crée aucun workspace. Le backend géolocalise l'IP de l'appelant pour remplir country et en dérive une devise provisoire ; la réponse porte workspaces: [], activeWorkspaceId: null et permissions: [].

Le choix explicite arrive ensuite, sur POST /api/users/me/onboarding : { "intent": "entrepreneur" | "closer" | "both", "currency": "XOF" }. La devise devient celle du portefeuille dans tous les cas ; entrepreneur et both créent en plus le premier workspace, dont l'appelant est propriétaire.

L'appel ne se joue qu'une fois — un rejeu renvoie 409. Le frontend sait s'il doit afficher la page en lisant parameters.marketing.intent dans GET /api/users/me : null veut dire « pas encore passé ». Un invité qui rejoint par POST /api/invitations/accept-as-new-user arrive avec l'intention closer déjà posée, et ne voit donc jamais la page.

Préfixe et workspace actif

  • Toutes les routes sont préfixées par /api (sauf le service de fichiers /_storage/...).
  • Le workspace actif est transmis par l'en-tête X-Workspace-Id et calculé à la requête, jamais persisté. Voir Multi-tenant.

Trois gardes d'authentification

Les gardes vivent dans web/guards/. Elles forment une échelle : aucun contexte de workspace, au mieux, ou obligatoire.

RequireWorkspaceGuard (le défaut)

Authentifie et résout un workspace actif. Derrière elle :

  • user.activeWorkspaceId est positionné ;
  • user.hasPerm(...) est significatif.

C'est la garde par défaut pour la quasi-totalité des routes.

AuthenticatedGuard (les routes personnelles)

Authentifie uniquement. Elle couvre deux familles : les routes qu'un utilisateur doit atteindre avant d'appartenir à un workspace (POST /workspaces, POST /invitations/accept), et celles dont la donnée n'appartient à aucun workspace — argent personnel, identité, moyens de paiement, référentiels globaux. Derrière elle :

  • il n'y a pas de workspace actif (user.activeWorkspaceId non défini) ;
  • user.permissions ne contient que la baseline staff (vide pour un non-staff).

Les routes concernées (#510) :

Route Portée réelle
GET /wallets/me le USER_WALLET de l'appelant
GET /wallets/:id/transactions (déprécié) propriété du wallet, inter-workspaces
GET /transactions tous les wallets lisibles, inter-workspaces
GET /kyc/me, POST /kyc/submissions l'identité de l'appelant, une soumission par user
/withdrawal-methods (les 4 routes) userId
/withdrawals (les 4 routes) propriété du wallet source
GET /workspace-memberships/me tous les workspaces de l'appelant
POST /invitations/accept égalité des e-mails, avant toute appartenance
GET /permissions, GET /workflows référentiels statiques, identiques pour tous
/notifications (les 5 routes) la cloche est par personne

Une garde de classe ne se relâche pas par route

Nest compose les gardes — globale, puis contrôleur, puis route — et exige que toutes passent. Une @UseGuards(AuthenticatedGuard) posée sur une méthode n'annule donc pas le @UseGuards(RequireWorkspaceGuard) de la classe. Sur un contrôleur mixte (wallets, workspace-memberships, invitations), la garde se pose par route, jamais sur la classe.

Ne supposez pas un workspace derrière AuthenticatedGuard

Un service derrière AuthenticatedGuard ne doit pas supposer que l'appelant a un workspace ou une permission scopée — ce serait un bug. En cas de doute, utilisez RequireWorkspaceGuard.

OptionalWorkspaceGuard (le cas mixte)

Authentifie, résout le workspace actif quand elle le peut, et ne refuse jamais son absence. Réservée aux routes qui doivent rapporter le contexte de workspace tout en restant joignables sans — aujourd'hui /users/me seul, dont les quatre routes ne touchent que la ligne users de l'appelant. Derrière elle :

  • user.activeWorkspaceId n'est digne de confiance que si user.hasActiveWorkspace le dit ;
  • user.permissions ne contient que la baseline staff tant qu'aucun workspace n'est actif ;
  • le verrou de mutation des workspaces suspendus ne s'applique pas : une donnée personnelle n'appartient pas au workspace.

Autres gardes

Garde Rôle
StaffGuard Vérifie le statut superutilisateur / staff.
AppThrottlerGuard Rate limiting global (@nestjs/throttler), renvoie 429.

Injection de l'utilisateur courant

Dans les contrôleurs, on récupère l'utilisateur via le décorateur @CurrentUser() et on le passe directement au service — jamais de re-fetch via un repository. Côté service, user: User est le dernier paramètre positionnel (voir Style de code).

@Post()
create(@Body() body: CreateFormDto, @CurrentUser() user: User) {
  return this.forms.create(body, user);
}

Autorisation

Une fois authentifié et le workspace résolu, l'autorisation se fait dans le service via user.hasPerm(codename). Un refus lève AuthorizationError403. Voir Permissions (RBAC).