Repository navigation
[NestJS] Porter la route GET /api/v1/auth depuis le serveur Fastify vers server-nestjs #2225
Description
Description
La route GET /api/v1/auth (login / synchronisation de session OIDC) est la
dernière route du module user servie uniquement par le legacy
(apps/server/src/resources/user/router.ts, handler auth). La tapissant dans
le location /api/ catch-all, elle maintient apps/server déployé pour le seul
flux de connexion et empêche de solder la vague « user » du suivi
#1889.
Le module UserModule cible existe déjà dans
apps/server-nestjs/src/modules/user/ et sert GET /users,
GET /users/matching, PATCH /users ; seul ce endpoint manque. La
qualification du 2026-10-06 (voir commentaire) a arrêté l'approche : réutiliser
le chemin d'authentification Bearer déjà émis par le client plutôt que répliquer
la pile de session Fastify du legacy.
-
req.session.user(rempli parfastify-keycloak-adaptervia
userPayloadMapper:sub → id,given_name → firstName,
family_name → lastName,groups), 401 si absent. -
logViaSession: find-or-create du user (création avectype: 'human',
adminRoleIds: []), fusion des rôles admin (groupes OIDC ∪ rôles persistés ∪
type: 'global'), mise à jourlastLogin, réponseUserSchema(200). -
@Get('auth')dans leUserControllerexistant, sans garde admin. -
Authentification via
AuthService/KeycloakJwtService(header
Authorization: Bearerdéjà envoyé parapps/client/src/api/xhr-client.ts
pour toute route hors liste blanche). -
KeycloakJwtService.validatePayloadcouvre déjà find-or-update, fusion des
adminRoleIdset calcul du bitmask ; l'écart à combler est la création à la
volée du user absent (legacy : upserttype: 'human'; NestJS actuel : 401). -
Réponse :
UserSchemaviatoContractUser(contrat déjà défini dans
packages/shared/src/contracts/user.ts,method: 'GET', réponses
200: UserSchema,307,500). -
Legacy handler :
apps/server/src/resources/user/router.ts(auth) et
logViaSessiondansapps/server/src/resources/user/business.ts -
Contrat :
packages/shared/src/contracts/user.ts -
Consommateur client :
apps/client/src/stores/user.ts
(apiClient.Users.auth()), token Bearer :apps/client/src/api/xhr-client.ts -
Auth NestJS existante :
apps/server-nestjs/src/modules/infrastructure/auth/
(keycloak-jwt/keycloak-jwt.service.ts) -
Module cible :
apps/server-nestjs/src/modules/user/ -
Suivi global : [NestJS] Modularisation de server #1889
Définition du fini
-
GET /api/v1/authservi parUserControlleravec parité de réponse
(même session Keycloak ⇒ mêmeUserSchemaque le legacy) - Premier login d'un user absent : création en base (
type: 'human'),
pas de 401 - Bitmask admin et
adminRoleIdsidentiques au legacy pour les trois
sources de rôles (OIDC, persistés, global) - Tests unitaires controller + service (Vitest) sans régression
- Bascule
routing.conf:location = /api/v1/authvers
server-nestjs, marquée vague + procédure de rollback - [NestJS] Modularisation de server #1889 mis à jour (case
usersoldée) et
MODULARISATION-STATUT.mdactualisé
Activity
[-][NestJS] Port POST /api/v1/auth route from Fastify server to server-nestjs[/-][+][NestJS] Porter la route POST /api/v1/auth depuis le serveur Fastify vers server-nestjs[/+][-][NestJS] Porter la route POST /api/v1/auth depuis le serveur Fastify vers server-nestjs[/-][+][NestJS] Porter la route GET /api/v1/auth depuis le serveur Fastify vers server-nestjs[/+]shikanime commentedon Oct 6, 2026
Convergence — qualification contre le code réel (2026-10-06)
Trois résolutions issues de la comparaison systématique du ticket avec
origin/main: le périmètre initial a été écrit avant que le moduleusern'existe ; deux des trois prérequis qu'il listait sont désormais portés par
l'existant, et le verbe de la route était faux.
1. Le verbe est
GET, pasPOST. Le contrat partagé fait foi :Le client consomme ce contrat (
apiClient.Users.auth()dansapps/client/src/stores/user.ts). Le corps du ticket disaitPOST: corrigé.2.
UserModuleexiste déjà.user.controller.tssert déjàGET /api/v1/users,GET /api/v1/users/matchingetPATCH /api/v1/userset laroute
location /api/v1/usersderouting.confpointe vers NestJS. Le ticketdemandait de créer le module : seul le endpoint
authmanque. Le serviceexpose déjà
PrismaService+EventEmitter2;toContractUserest réutilisablepour la réponse.
3. Le vrai travail est la session, pas la route. Côté legacy,
req.session.userest alimenté par la pilefastify-cookie+@fastify/session(
sessionConf: cookiesessionId, httpOnly, secure, expires 30 min) rempliepar
fastify-keycloak-adapterviauserPayloadMapper(sub → id,given_name → firstName,family_name → lastName,groups). Côté NestJS :aucun plugin de session enregistré dans
main.ts, etAuthServicene couvre quedso-tokenetkeycloak-jwt(headerAuthorization: Bearer). Or le client envoiedéjà le token Bearer sur toutes les routes hors liste blanche
(
apps/client/src/api/xhr-client.ts). La qualification a donc tranché l'approche :KeycloakJwtService.validatePayloadimplémente déjà la logique métier delogViaSession(findUnique user, upsert desadminRoleIdsfusionnant groupesOIDC + rôles persistés +
type: 'global', bitmask OR des permissions,lastLogin). Le handler devient un@Get('auth')dans leUserControllerexistant qui délègue à
AuthService.authenticate(ou directement àKeycloakJwtService), complète la création du user manquant(
logViaSessioncrée avectype: 'human',validatePayloadrenvoie 401) etretourne le user complet via
toContractUser. Aucune session Fastify àrépliquer, aucun secret partagé legacy à porter.
@fastify/session: le cookie de sessionlegacy et le Bearer du client coexistent dans la même requête ; aligner NestJS
sur le mécanisme déjà émis par le client évite un troisième chemin
d'authentification et la synchronisation de
sessionSecretentre les deuxbackends.
Divergence comportementale à décider pendant l'implémentation : la création à la
volée d'un user absent (legacy : upsert
type: 'human'; NestJS actuel : 401).Le respect du contrat (200 avec
UserSchema+307documenté) impose de porterla création, sinon tout premier login sur une route NestJS échoue.
Le corps du ticket a été réécrit avec ce périmètre qualifié ; l'estimation
~2 j tient, l'essentiel étant l'écart create-vs-401 et la parité de réponse.
feat(user): router la connexion GET /api/v1/auth vers server-nestjs