Aller au contenu

Corrections — 4 bugs identifiés sur le module Enfants et l'authentification

Date : 2026-06-25
Session : Investigation suite à un signalement de disparition d'un enfant après reconnexion
Statut : Tous les 4 bugs corrigés et déployés en production


Contexte du signalement

Bernard signale qu'après avoir saisi un enfant pour la commune de Kandi, la fermeture et réouverture de session faisait disparaître l'enfant de la liste. L'investigation a montré que le problème initial était transitoire (auto-résolu), mais a révélé 4 bugs structurels sous-jacents.


Bug 1 — Audit connexion planté silencieusement

Symptôme

À chaque connexion, une erreur PrismaClientValidationError était loguée :

Unknown argument `ressourceId`. Did you mean `ressource`?
at AuditLogRepositoryPrisma.creer()
at ConnexionUseCase.auditer()
L'opération continuait grâce au try/catch non-bloquant, mais l'audit était perdu à chaque connexion.

Cause

Dans AuditLogRepositoryPrisma.creer(), le code passait ressourceId: donnees.ressourceId directement dans le data du prisma.auditLog.create(). Or dans le client Prisma compilé dans le container Docker, ressourceId est une FK relationnelle vers la table RessourceAudit — elle n'est pas accessible comme champ scalaire direct dans create. Le champ prévu pour stocker l'ID de la ressource ciblée est cibleId.

Fichier modifié

BackEnd/src/Modules/Auth/infrastructure/repositories/prisma/AuditLogRepositoryPrisma.ts

Correction

// Avant (ligne ~44)
ressourceId: donnees.ressourceId,

// Après
cibleId: donnees.ressourceId,

Validation

Connexion avec test.agent@projetesm.test → log [AUDIT] Connexion réussie sans erreur Prisma.


Bug 2 — communeCode absent du profil après connexion

Symptôme

Après connexion, le profil utilisateur stocké dans le localStorage (Zustand persist) ne contenait pas communeCode. Résultat : les composants frontend qui devaient filtrer par commune de l'utilisateur travaillaient avec undefined.

Cause

La chaîne de propagation était cassée en 4 endroits : 1. ConnexionHandler.formatSuccessResponse() construisait manuellement l'objet utilisateur sans inclure communeCode 2. ConnexionUseCase.construireResultat() idem pour les 3 cas (connexion directe, 2FA requis configuré, 2FA requis non configuré) 3. L'interface IConnexionUseCase.ResultatConnexion.utilisateur ne déclarait pas communeCode 4. L'entité frontend User : constructeur, fromLoginResult(), toProfile() ignoraient communeCode

Fichiers modifiés

  • BackEnd/src/Modules/Auth/interface-adapters/handlers/auth/ConnexionHandler.ts
  • BackEnd/src/Modules/Auth/application/usecases/ConnexionUseCase.ts
  • BackEnd/src/Modules/Auth/application/interfaces/IConnexionUseCase.ts
  • FrontEnd/src/Modules/auth/domain/entities/User.ts
  • FrontEnd/src/Modules/auth/application/dto/dtos.ts (LoginResult)

Corrections clés

Backend — ConnexionHandler :

responseData.utilisateur = {
  idUtilisateur: connexionResult.utilisateur.id,
  // ...
  deuxFacteursMethode: connexionResult.utilisateur.deuxFacteursMethode ?? null,
  communeCode: connexionResult.utilisateur.communeCode ?? null  // ← ajouté
};

Frontend — User.ts :

// Constructeur : nouveau paramètre optionnel en fin de liste
public readonly communeCode: string | undefined = undefined

// fromLoginResult() : accepte et propage communeCode
static fromLoginResult(data: { ...; communeCode?: string | null }) {
  return new User(..., data.communeCode ?? undefined);
}

// toProfile() : exporte communeCode
toProfile(): UserProfile {
  return { ..., communeCode: this.communeCode };
}

Validation

communeCode: 'KANDI'  ← présent dans la réponse JSON de connexion

Bug 3 — Mode hors-ligne : liste des enfants vide

Symptôme

En mode offline (navigator.onLine = false), la page enfants affichait toujours une liste vide même avec des données en cache Dexie.

Cause

Dans EnfantsRepository.lister(), deux erreurs dans les callbacks passés à listerAvecCache() :

Erreur 1 — extraire : la fonction qui hydrate le cache Dexie depuis la réponse API lisait data.items alors que le backend retourne data.donnees.enfants.

// Avant — ne trouve jamais rien
(data: ListeEnfantsResult) => data.items ?? []

// Après — lit la vraie structure de la réponse
(data: any) => data.donnees?.enfants ?? []

Erreur 2 — construire : la fonction qui fabrique la réponse offline retournait { items, total } alors que le hook useEnfants attend { donnees: { enfants, total, page, ... } } (même structure que le backend online).

// Avant — structure incompatible avec useEnfants
(items) => ({ items, total: items.length }) as ListeEnfantsResult

// Après — structure identique à la réponse backend
(items) => ({ donnees: { enfants: items, total: items.length, page: 1, limite: items.length, totalPages: 1 } }) as any

Fichier modifié

FrontEnd/src/Modules/enfants/infrastructure/repositories/EnfantsRepository.ts

Note technique

Le type TypeScript ListeEnfantsResult dans dtos.ts est défini comme { items, total } mais la réponse réelle du backend a la forme { donnees: { enfants, total, page, limite, totalPages } }. Le hook useEnfants gère cette divergence via select: (res) => res.data?.donnees ?? res.data. La correction aligne le mode offline sur cette même structure sans toucher au type TypeScript (qui est utilisé ailleurs).


Bug 4 — Listing enfants filtré par créateur au lieu de par commune

Symptôme

Un agent PSYCHOLOGUE affecté à la commune de Kandi ne voyait que les enfants qu'il avait lui-même créés, pas ceux saisis par d'autres agents de la même commune. Pire, un ADMIN ne voyait que les enfants qu'il avait créés avec son propre compte.

Cause

EnfantRepositoryPrisma.lister() initialisait le filtre WHERE avec { idUtilisateur } (le créateur) au lieu d'un scope par commune :

// Avant — filtre multi-tenant trop restrictif
const where: Record<string, any> = { idUtilisateur };

De plus, ListerEnfantsHandler ne passait pas le scope commune au use case, et EnfantsModuleController.createContext() ne définissait pas getCommuneCode() (méthode pourtant déclarée dans IHttpContext).

Logique de scope correcte

  • Agent terrain (ex: PSYCHOLOGUE, ASSISTANT_SOCIAL) : getCommuneCode()"KANDI"WHERE communeCode = 'KANDI'
  • Admin / accès global : getCommuneCode()null → pas de filtre commune, accès à tous les enfants

Fichiers modifiés

IEnfantRepository.ts — signature de lister() :

// Avant
lister(idUtilisateur: string, filtres: FiltresEnfant): Promise<ListeEnfantsResult>
// Après
lister(communeCodeScope: string | null, filtres: FiltresEnfant): Promise<ListeEnfantsResult>

EnfantRepositoryPrisma.ts — logique WHERE :

async lister(communeCodeScope: string | null, filtres: FiltresEnfant) {
  const where: Record<string, any> = {};          // plus de filtre idUtilisateur

  if (communeCodeScope) {
    where.communeCode = communeCodeScope;         // scope commune automatique
  }
  if (filtres.communeCode) {
    where.communeCode = filtres.communeCode;      // filtre explicite (prioritaire)
  }
  // ... reste des filtres
}

ListerEnfantsUseCase.ts — passe le scope au repository :

const communeCodeScope = donnees.communeCodeScope !== undefined ? donnees.communeCodeScope : null;
const resultat = await this.enfantRepository.lister(communeCodeScope, { ... });

ListerEnfantsHandler.ts — lit le scope depuis le contexte HTTP :

return this.listerEnfantsUseCase.executer({
  idUtilisateur: context.getUserId(),
  communeCodeScope: context.getCommuneCode(),  // ← ajouté
  ...
});

EnfantsModuleController.ts — ajout de getCommuneCode() dans createContext() :

getCommuneCode: () => utilisateur?.communeCode ?? null,
getRole: () => utilisateur?.role ?? '',
Ces méthodes manquaient dans l'objet contexte créé manuellement par ce contrôleur (contrairement à ExpressHttpContext qui les définissait déjà).

Validation

Agent KANDI (test.agent)  → total: 2 enfants  (commune KANDI uniquement)
Admin       (test.admin)  → total: 6 enfants  (toutes communes)

Déploiements effectués

Composant Déploiement Résultat
Backend deploy.sh --target backend × 3 Health check OK, 203 routes
Frontend deploy.sh --target frontend × 1 Build Vite OK, PWA v1.3.0

Script utilisé :

bash /home/kanmaber/scripts/deploys/deploy.sh \
  --config /home/kanmaber/scripts/deploys/projetesm.env \
  --target backend|frontend


Fichiers modifiés (récapitulatif)

Backend

Fichier Raison
Modules/Auth/infrastructure/repositories/prisma/AuditLogRepositoryPrisma.ts Bug 1 : ressourceIdcibleId
Modules/Auth/interface-adapters/handlers/auth/ConnexionHandler.ts Bug 2 : ajouter communeCode dans la réponse
Modules/Auth/application/usecases/ConnexionUseCase.ts Bug 2 : propager communeCode dans les 3 cas
Modules/Auth/application/interfaces/IConnexionUseCase.ts Bug 2 : déclarer communeCode dans l'interface
Modules/Enfants/domain/interfaces/repositories/IEnfantRepository.ts Bug 4 : signature lister()
Modules/Enfants/application/interfaces/IListerEnfantsUseCase.ts Bug 4 : ajouter communeCodeScope
Modules/Enfants/application/usecases/ListerEnfantsUseCase.ts Bug 4 : passer le scope au repository
Modules/Enfants/interface-adapters/handlers/ListerEnfantsHandler.ts Bug 4 : lire getCommuneCode()
Modules/Enfants/interface-adapters/controllers/EnfantsModuleController.ts Bug 4 : ajouter getCommuneCode() dans createContext()
Modules/Enfants/infrastructure/repositories/prisma/EnfantRepositoryPrisma.ts Bug 4 : filtre par commune

Frontend

Fichier Raison
Modules/auth/domain/entities/User.ts Bug 2 : propagation communeCode
Modules/auth/application/dto/dtos.ts Bug 2 : LoginResult.utilisateur.communeCode
Modules/enfants/infrastructure/repositories/EnfantsRepository.ts Bug 3 : correction mode offline

Points d'attention pour la suite

  1. Autres contrôleurs module : EnfantsModuleController avait un createContext() maison sans getCommuneCode(). D'autres modules pourraient avoir le même problème si des méthodes de IHttpContext y sont appelées sans être implémentées. À vérifier si d'autres handlers utilisent getCommuneCode() ou getRole().

  2. Type ListeEnfantsResult : le type TypeScript dans dtos.ts ({ items, total }) est en décalage avec la vraie réponse backend ({ donnees: { enfants, total, ... } }). Ce décalage est géré par des as any et le select du hook. Un nettoyage du type serait souhaitable dans une prochaine itération.

  3. Scope commune pour obtenirParId() : la méthode obtenirParId() filtre toujours par idUtilisateur. Elle devrait aussi utiliser communeCodeScope pour la même cohérence (un admin devrait pouvoir voir la fiche d'un enfant qu'il n'a pas créé).