Repository navigation
🐛 [BUG] - Décompte des membres des rôles admin en échec 500 (NaN) quand des rôles OIDC sont portés #2781
Description
Description
L'ouverture de la page Administration > Rôles affiche une notification d'erreur et le décompte des membres ne s'affiche plus pour aucun rôle. L'endpoint GET /api/v1/admin/roles/member-counts renvoie une 500 : le corps produit par le handler actuellement servi (fallback legacy apps/server) échoue sa propre validation de contrat ts-rest côté serveur.
Déclencheur : au moins un utilisateur porteur d'un rôle admin adossé à un groupe OIDC (oidcGroup non vide). Le décompte est calculé en mémoire à partir des adminRoleIds des utilisateurs ; un id OIDC absent de l'index pré-initialisé passe par undefined++ et produit NaN, que le contrat z.record(z.number()) rejette pour la réponse entière.
Impact : perte du décompte des membres sur toute la page Rôles pour tous les administrateurs, tant que la route reste servie par le legacy — lequel est gelé pour les contributions (cf. CONTRIBUTING.md).
Etapes de reproduction
- Se placer sur une instance où
/api/v1/admin/rolesest servie parapps/server(défaut actuel du routage nginx-strangler) - Garantir qu'au moins un utilisateur porte un rôle admin avec
oidcGroupnon vide - Ouvrir Administration > Rôles
- Observer la notification
[ts-rest] Response validation failed for GET /api/v1/admin/roles/member-counts: …et l'absence des décomptes
Captures d'écran
Logs
[ts-rest] Response validation failed for GET /api/v1/admin/roles/member-counts: [
{
"code": "invalid_type",
"expected": "number",
"received": "nan",
"path": [
"6bebe7b2-0f0a-456e-ab7f-b3d7640a7cbf"
],
"message": "Expected number, received nan"
},
{
"code": "invalid_type",
"expected": "number",
"received": "nan",
"path": [
"76229c96-4716-45bc-99da-00498ec9018c"
],
"message": "Expected number, received nan"
}
]Navigateurs
OS
Version de la console impactée
Définition du fini
- La route
/api/v1/admin/rolesest servie parserver-nestjs(bascule nginx-strangler, prérequis feat(server-nestjs): consume adminRole events in keycloak and gitlab modules #2749 fusionné) - Des specs verrouillent le décompte : exclusion des ids OIDC, aucune valeur non finie
- Le correctif est terminé
- La communication avec les autres équipes impliquées par ce correctif a été faite
Références
- Handler fautif :
apps/server/src/resources/admin-role/business.ts—countRolesMembers(rolesCounts[roleId]++) - Contrat :
packages/shared/src/contracts/admin-role.ts— réponsemember-counts:z.record(z.number().min(0)) - Validation en cause :
apps/server/src/resources/index.ts(responseValidation: true) etapps/server/src/app.ts(setErrorHandler) - Implémentation NestJS déjà corrigée mais non routée :
apps/server-nestjs/src/modules/admin-role/admin-role-queries.utils.ts - Bloquant de bascule : 🐛 [BUG] server-nestjs: adminRole.upsert/delete events have no consumer (Keycloak/GitLab admin sync dead) — cutover blocker #2723 — PR feat(server-nestjs): consume adminRole events in keycloak and gitlab modules #2749
Activity
shikanime commentedon Sep 28, 2026
Cause racine (analyse)
La route est encore servie par le legacy : aucune
location /api/v1/admin/rolesn'existe dansapps/nginx-strangler/conf.d/routing.conf, le fallbacklocation /api/route versapps/server(gelé).countRolesMembers()(apps/server/src/resources/admin-role/business.ts:72-83) n'indexe que les rôlesoidcGroup = ''(ligne 73), puis incrémenterolesCounts[roleId]++(ligne 82) pour tous les ids portés par les utilisateurs. Pour un id de rôle OIDC — absent de l'index —undefined++produitNaN.La réponse est validée côté serveur (
responseValidation: true,apps/server/src/resources/index.ts:26) contrez.record(z.number().min(0))(packages/shared/src/contracts/admin-role.ts:50) : une seule valeurNaNinvalide la réponse entière, transformée en 500 parsetErrorHandler(apps/server/src/app.ts:39-45).Reproduit localement avec le vrai plugin
@ts-rest/fastify(responseValidation: true, données portant deux ids OIDC) :HTTP 500, message[ts-rest] Response validation failed for GET /api/v1/admin/roles/member-counts: …identique à celui de la capture.Contre-vérifications : l'implémentation
server-nestjs(apps/server-nestjs/src/modules/admin-role/admin-role-queries.utils.ts:58) ignore déjà les ids OIDC (if (typeof counts[roleId] === 'number')) ; le défaut ne vient donc pas du portage, mais du serveur legacy que la route continue d'utiliser. Le client n'est pas en cause non plus (validation de réponse désactivée côté client ; la 500 est émise par le serveur).Chemin de correction
apps/serverétant gelé (CONTRIBUTING.md: « ne reçoit plus de contribution, y compris pour une correction de bug »), le correctif passe par la bascule de/api/v1/admin/rolesversserver-nestjs:adminRole.upsert/delete→ consommateurs Keycloak/GitLab, cf. 🐛 [BUG] server-nestjs: adminRole.upsert/delete events have no consumer (Keycloak/GitLab admin sync dead) — cutover blocker #2723) ;shikanime commentedon Sep 28, 2026
Correctif
PR #2782 —
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs— est prête à la revue (CI verte, 32/32 checks) :/api/v1/admin/rolesversserver-nestjs(décompte sansNaN) ;À fusionner après #2749 (prérequis, cf. #2723).
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
shikanime commentedon Sep 28, 2026
Correctifs
/api/v1/admin/roles: pile #2751, PR feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs #2782 (couche au-dessus de refactor(user): strangler offload routes to server-nestjs #2739) — routage uniquement.main.Le socle de la pile (21/08) précède le module
admin-role, d'où cette séparation.test(admin-role): lock member counts against OIDC role ids
test(admin-role): lock member counts against OIDC role ids
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
7 remaining items
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
feat(nginx-strangler): route /api/v1/admin/roles to server-nestjs
test(admin-role): lock member counts against OIDC role ids