Skip to content

[NestJS] Porter la route GET /api/v1/auth depuis le serveur Fastify vers server-nestjs #2225

Description

@shikanime

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 par fastify-keycloak-adapter via
    userPayloadMapper : sub → id, given_name → firstName,
    family_name → lastName, groups), 401 si absent.

  • logViaSession : find-or-create du user (création avec type: 'human',
    adminRoleIds: []), fusion des rôles admin (groupes OIDC ∪ rôles persistés ∪
    type: 'global'), mise à jour lastLogin, réponse UserSchema (200).

  • @Get('auth') dans le UserController existant, sans garde admin.

  • Authentification via AuthService / KeycloakJwtService (header
    Authorization: Bearer déjà envoyé par apps/client/src/api/xhr-client.ts
    pour toute route hors liste blanche).

  • KeycloakJwtService.validatePayload couvre déjà find-or-update, fusion des
    adminRoleIds et calcul du bitmask ; l'écart à combler est la création à la
    volée du user absent (legacy : upsert type: 'human' ; NestJS actuel : 401).

  • Réponse : UserSchema via toContractUser (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
    logViaSession dans apps/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/auth servi par UserController avec parité de réponse
    (même session Keycloak ⇒ même UserSchema que le legacy)
  • Premier login d'un user absent : création en base (type: 'human'),
    pas de 401
  • Bitmask admin et adminRoleIds identiques 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/auth vers
    server-nestjs, marquée vague + procédure de rollback
  • [NestJS] Modularisation de server #1889 mis à jour (case user soldée) et
    MODULARISATION-STATUT.md actualisé

Activity

  1. changed the title [-][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[/+] on Jun 17, 2026
  2. self-assigned this
    on Aug 26, 2026
  3. added theissue type on Sep 29, 2026
  4. added this to the 9.27.0 milestone on Sep 29, 2026
  5. changed the title [-][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[/+] on Oct 6, 2026
  6. shikanime commented on Oct 6, 2026

    @shikanime
    MemberAuthor

    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 module user
    n'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, pas POST. Le contrat partagé fait foi :

    // packages/shared/src/contracts/user.ts @ origin/main
    auth: {
      method: 'GET',
      path: `${apiPrefix}/auth`,
      summary: 'Login',
      description: 'OIDC callback to signin or signup',

    Le client consomme ce contrat (apiClient.Users.auth() dans
    apps/client/src/stores/user.ts). Le corps du ticket disait POST : corrigé.

    2. UserModule existe déjà. user.controller.ts sert déjà
    GET /api/v1/users, GET /api/v1/users/matching et PATCH /api/v1/users et la
    route location /api/v1/users de routing.conf pointe vers NestJS. Le ticket
    demandait de créer le module : seul le endpoint auth manque. Le service
    expose déjà PrismaService + EventEmitter2 ; toContractUser est réutilisable
    pour la réponse.

    3. Le vrai travail est la session, pas la route. Côté legacy,
    req.session.user est alimenté par la pile fastify-cookie + @fastify/session
    (sessionConf : cookie sessionId, httpOnly, secure, expires 30 min) remplie
    par fastify-keycloak-adapter via userPayloadMapper (sub → id,
    given_name → firstName, family_name → lastName, groups). Côté NestJS :
    aucun plugin de session enregistré dans main.ts, et AuthService ne couvre que
    dso-token et keycloak-jwt (header Authorization: Bearer). Or le client envoie
    dé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 :

    • Option retenue — port du handler via le token Bearer existant :
      KeycloakJwtService.validatePayload implémente déjà la logique métier de
      logViaSession (findUnique user, upsert des adminRoleIds fusionnant groupes
      OIDC + rôles persistés + type: 'global', bitmask OR des permissions,
      lastLogin). Le handler devient un @Get('auth') dans le UserController
      existant qui délègue à AuthService.authenticate (ou directement à
      KeycloakJwtService), complète la création du user manquant
      (logViaSession crée avec type: 'human', validatePayload renvoie 401) et
      retourne le user complet via toContractUser. Aucune session Fastify à
      répliquer, aucun secret partagé legacy à porter.
    • Option écartée — répliquer @fastify/session : le cookie de session
      legacy 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 sessionSecret entre les deux
      backends.

    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 + 307 documenté) impose de porter
    la 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions