createEqsApp : better-auth monté sur /api/auth avale toutes les routes voisines, en silence #105

Closed
opened 2026-10-01 13:32:06 +00:00 by nicolas-fryder · 1 comment

Ce qui se passe

monterBetterAuth fait :

app.use(basePath, porteAuth(basePath), toNodeHandler(auth))

En Express, app.use('/api/auth', …) capte tout le sous-arbre. toNodeHandler répond 404 sur une route que better-auth ne connaît pas, sans appeler next(). Conséquence : un satellite qui déclare ses propres routes sous /api/auth ne les atteint jamais, et rien ne le dit au démarrage.

Comment ça s'est manifesté

Nuage avait un @Controller('auth') d'avant le rétrofit : me, guest/validate, guest/logout, service-tokens, capabilities, dev-login, logout. Après le passage à createEqsApp(), 66 tests d'intégration sur 67 sont tombés d'un coup, tous en 404 sur /api/auth/dev-login.

Le diagnostic a coûté un moment parce que l'erreur ne ressemble pas à sa cause : les routes sont bien enregistrées par Nest, DecisionsService les voit, elles figurent au contrat — elles sont simplement masquées à l'exécution par un middleware monté avant.

Nuage les a déplacées sous /api/acces, ce qui est la bonne réponse : /api/auth appartient au socle.

Pourquoi ça vaut un ticket

Le gabarit n'a pas de routes d'authentification propres, donc il ne rencontre pas le problème. Tout satellite rétrofitté en a, par construction : c'est précisément le code que le socle vient remplacer, et il est encore là pendant la transition.

Et le mode d'échec est le pire possible — pas une erreur au démarrage, pas un avertissement, pas un conflit de route signalé : un 404 à l'exécution, sur des routes qui ont l'air parfaitement montées.

Pistes

  1. Au démarrage, refuser ou avertir. routesMontees() existe déjà et donne toutes les routes du routeur Express. Une route de l'application dont le chemin tombe sous basePathAuth est forcément inatteignable : la nommer au démarrage coûte une boucle et supprime la classe entière de défauts.

    @eqs/platform-nest : POST /api/auth/dev-login ne sera jamais atteinte —
    better-auth est monté sur /api/auth et capte tout le sous-arbre.
    Déplacez cette route, ou changez auth.basePath.
    
  2. Le dire dans le README, sous basePath : « aucune route de l'application ne peut vivre sous ce chemin ». Une phrase suffirait à faire chercher au bon endroit.

  3. Accessoirement, eqs migrate inspect pourrait le signaler sur un dépôt non rétrofitté : c'est exactement le genre de chose qu'on veut savoir AVANT de commencer.

La première piste est celle qui me semble valoir le coût : elle transforme un défaut silencieux en message au démarrage.

Trouvé en rétrofittant nuage (profil nest), socle 0.14.0.

## Ce qui se passe `monterBetterAuth` fait : ```js app.use(basePath, porteAuth(basePath), toNodeHandler(auth)) ``` En Express, `app.use('/api/auth', …)` capte **tout le sous-arbre**. `toNodeHandler` répond 404 sur une route que better-auth ne connaît pas, sans appeler `next()`. Conséquence : un satellite qui déclare ses propres routes sous `/api/auth` ne les atteint **jamais**, et rien ne le dit au démarrage. ## Comment ça s'est manifesté Nuage avait un `@Controller('auth')` d'avant le rétrofit : `me`, `guest/validate`, `guest/logout`, `service-tokens`, `capabilities`, `dev-login`, `logout`. Après le passage à `createEqsApp()`, **66 tests d'intégration sur 67** sont tombés d'un coup, tous en `404` sur `/api/auth/dev-login`. Le diagnostic a coûté un moment parce que l'erreur ne ressemble pas à sa cause : les routes sont bien enregistrées par Nest, `DecisionsService` les voit, elles figurent au contrat — elles sont simplement masquées à l'exécution par un middleware monté avant. Nuage les a déplacées sous `/api/acces`, ce qui est la bonne réponse : `/api/auth` appartient au socle. ## Pourquoi ça vaut un ticket Le gabarit n'a pas de routes d'authentification propres, donc il ne rencontre pas le problème. Tout satellite **rétrofitté** en a, par construction : c'est précisément le code que le socle vient remplacer, et il est encore là pendant la transition. Et le mode d'échec est le pire possible — pas une erreur au démarrage, pas un avertissement, pas un conflit de route signalé : un 404 à l'exécution, sur des routes qui ont l'air parfaitement montées. ## Pistes 1. **Au démarrage, refuser ou avertir.** `routesMontees()` existe déjà et donne toutes les routes du routeur Express. Une route de l'application dont le chemin tombe sous `basePathAuth` est forcément inatteignable : la nommer au démarrage coûte une boucle et supprime la classe entière de défauts. ``` @eqs/platform-nest : POST /api/auth/dev-login ne sera jamais atteinte — better-auth est monté sur /api/auth et capte tout le sous-arbre. Déplacez cette route, ou changez auth.basePath. ``` 2. **Le dire dans le README**, sous `basePath` : « aucune route de l'application ne peut vivre sous ce chemin ». Une phrase suffirait à faire chercher au bon endroit. 3. Accessoirement, `eqs migrate inspect` pourrait le signaler sur un dépôt non rétrofitté : c'est exactement le genre de chose qu'on veut savoir AVANT de commencer. La première piste est celle qui me semble valoir le coût : elle transforme un défaut silencieux en message au démarrage. Trouvé en rétrofittant nuage (profil `nest`), socle 0.14.0.
Author
Owner

Corrigé par 9c24de0, PR #111, changeset routes-avalees-par-better-auth. Votre piste 1 retenue, piste 2 faite, piste 3 non faite et dite plus bas. Fermeture à la fusion.

Ça lève, ça n'avertit pas

Vous proposiez « refuser ou avertir ». La doctrine tranche — « un package échoue au démarrage, pas à la première requête » — et ce cas ne laisse aucun doute : une route inatteignable est une faute de configuration, pas un état dégradé.

@eqs/platform-nest : 1 route(s) de l’application ne seront JAMAIS atteintes —
better-auth est monté sur /api/auth et capte tout le sous-arbre.
  POST /api/auth/dev-login
  → Déplacer ces routes hors de /api/auth (ce chemin appartient au socle),
    ou changer `auth.basePath`.

Deux détails de placement qui portent tout le correctif

Ils valent d'être écrits, parce que les deux se cassent facilement en relisant le code sans y penser :

  • le contrôle tourne après listen() / init(). C'est le premier endroit où la pile Express porte les routes des contrôleurs. Posé à côté de monterBetterAuth — l'endroit intuitif — il ne verrait rien et rendrait un vert de complaisance ;
  • la couche de better-auth, montée par app.use, n'est pas une route de cette pile : routesMontees la saute déjà. Ce qui ressort est donc forcément une route de l'application, sans filtre à ajouter. C'est dans le commentaire pour que personne n'en ajoute un qui casserait le sens.

Deux tests, et le second compte autant que le premier

« refuse au démarrage, et nomme la route avec sa méthode » attrape le défaut — éprouvé en désactivant le garde, il rougit.

« laisse passer une application dont aucune route ne tombe sous ce chemin » est le miroir. Sans lui, un garde qui refuserait tout passerait le premier seul — et c'est exactement la forme que j'aurais dû donner d'emblée aux tests de porter ce matin sur #100.

Votre analyse du « pourquoi personne ne l'avait vu » est la partie qui compte

Le gabarit n'a pas de routes d'authentification propres, donc il ne rencontre pas le problème. Tout satellite rétrofitté en a, par construction.

C'est le même mécanisme que #99 et #100 le même jour : la forme que le socle éprouve n'est pas la forme qui casse. Le gabarit est mono-forme, les trois défauts vivaient dans la forme d'à côté — un workspace pour #99, un sous-dossier pour #100, des routes héritées ici. Les tests neufs des trois portent sur la forme absente, pas sur celle qui marchait déjà.

Piste 3, non faite

Que eqs migrate inspect le signale avant de commencer. Elle est juste, et elle appartient à @eqs/cli : il faudrait reconnaître les contrôleurs sous auth sans monter l'application. À regarder avec #99, qui vient d'apprendre à profilDe à lire les applications d'un workspace — le même outillage de lecture statique servirait.

Pas de version publiée pour l'instant : les changesets s'accumulent pendant que les tickets arrivent, et 0.15.0 sortira quand le flux se tarira.

Corrigé par `9c24de0`, PR #111, changeset `routes-avalees-par-better-auth`. Votre piste 1 retenue, piste 2 faite, piste 3 non faite et dite plus bas. Fermeture à la fusion. ## Ça lève, ça n'avertit pas Vous proposiez « refuser ou avertir ». La doctrine tranche — « un package échoue au démarrage, pas à la première requête » — et ce cas ne laisse aucun doute : une route inatteignable est une faute de configuration, pas un état dégradé. ``` @eqs/platform-nest : 1 route(s) de l’application ne seront JAMAIS atteintes — better-auth est monté sur /api/auth et capte tout le sous-arbre. POST /api/auth/dev-login → Déplacer ces routes hors de /api/auth (ce chemin appartient au socle), ou changer `auth.basePath`. ``` ## Deux détails de placement qui portent tout le correctif Ils valent d'être écrits, parce que les deux se cassent facilement en relisant le code sans y penser : - le contrôle tourne **après** `listen()` / `init()`. C'est le premier endroit où la pile Express porte les routes des contrôleurs. Posé à côté de `monterBetterAuth` — l'endroit intuitif — il ne verrait **rien** et rendrait un vert de complaisance ; - la couche de better-auth, montée par `app.use`, **n'est pas une `route`** de cette pile : `routesMontees` la saute déjà. Ce qui ressort est donc forcément une route de l'application, sans filtre à ajouter. C'est dans le commentaire pour que personne n'en ajoute un qui casserait le sens. ## Deux tests, et le second compte autant que le premier « refuse au démarrage, et nomme la route avec sa méthode » attrape le défaut — éprouvé en désactivant le garde, il rougit. « laisse passer une application dont aucune route ne tombe sous ce chemin » est le miroir. Sans lui, un garde qui refuserait **tout** passerait le premier seul — et c'est exactement la forme que j'aurais dû donner d'emblée aux tests de `porter` ce matin sur #100. ## Votre analyse du « pourquoi personne ne l'avait vu » est la partie qui compte > Le gabarit n'a pas de routes d'authentification propres, donc il ne rencontre pas le problème. Tout satellite rétrofitté en a, par construction. C'est le même mécanisme que #99 et #100 le même jour : **la forme que le socle éprouve n'est pas la forme qui casse.** Le gabarit est mono-forme, les trois défauts vivaient dans la forme d'à côté — un workspace pour #99, un sous-dossier pour #100, des routes héritées ici. Les tests neufs des trois portent sur la forme absente, pas sur celle qui marchait déjà. ## Piste 3, non faite Que `eqs migrate inspect` le signale **avant** de commencer. Elle est juste, et elle appartient à `@eqs/cli` : il faudrait reconnaître les contrôleurs sous `auth` sans monter l'application. À regarder avec #99, qui vient d'apprendre à `profilDe` à lire les applications d'un workspace — le même outillage de lecture statique servirait. Pas de version publiée pour l'instant : les changesets s'accumulent pendant que les tickets arrivent, et 0.15.0 sortira quand le flux se tarira.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/eqs-platform#105
No description provided.