Un limiteur de débit qui comptait la mauvaise chose
NestJS ThrottlerGuard keys on req.ip by default, so every user behind one office NAT shared a single rate-limit budget on authenticated routes.
Ça ne pouvait pas marcher
Une course, un ordre, une colonne pour deux choses. La forme l'excluait avant même l'exécution.
C'est venu en réfléchissant à si le back-end était sûr à clusteriser. Le ThrottlerGuard par défaut de NestJS, sans aucun tracker personnalisé nulle part dans la base de code, clé tout sur req.ip.
Raisonnable pour les endpoints anonymes pré-connexion, où il n'y a pas d'autre identité. Mais c'était aussi la seule clé pour les routes authentifiées, donc en production chaque client sur le WiFi d'accueil de la même organisation et chaque membre du personnel derrière le même NAT de bureau partageaient un budget. Rien dans le code ne distinguait un utilisateur abusif de cinquante personnes qui partagent un routeur.
Un guard personnalisé se clé maintenant sur req.user?.id avec l'IP comme repli pré-authentification, ce qui a impliqué de réordonner la chaîne de guards globale pour que l'authentification remplisse req.user avant que le throttler ne tourne. Vérifié en me connectant en tant que deux comptes depuis une IP, martelant un endpoint protégé avec les deux, et vérifiant les vraies clés Redis : deux compteurs, pas un. Connexion et inscription limitent encore par IP, puisque les routes @Public() ne remplissent jamais d'utilisateur.
« Qui compte comme un seul appelant » est une décision qu'un limiteur de débit prend silencieusement par défaut, et le défaut suppose que tous ceux derrière une connexion partagée méritent d'être traités comme complices.