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()
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.tsBackEnd/src/Modules/Auth/application/usecases/ConnexionUseCase.tsBackEnd/src/Modules/Auth/application/interfaces/IConnexionUseCase.tsFrontEnd/src/Modules/auth/domain/entities/User.tsFrontEnd/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 ?? '',
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 : ressourceId → cibleId |
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¶
-
Autres contrôleurs module :
EnfantsModuleControlleravait uncreateContext()maison sansgetCommuneCode(). D'autres modules pourraient avoir le même problème si des méthodes deIHttpContexty sont appelées sans être implémentées. À vérifier si d'autres handlers utilisentgetCommuneCode()ougetRole(). -
Type
ListeEnfantsResult: le type TypeScript dansdtos.ts({ items, total }) est en décalage avec la vraie réponse backend ({ donnees: { enfants, total, ... } }). Ce décalage est géré par desas anyet leselectdu hook. Un nettoyage du type serait souhaitable dans une prochaine itération. -
Scope commune pour
obtenirParId(): la méthodeobtenirParId()filtre toujours paridUtilisateur. Elle devrait aussi utilisercommuneCodeScopepour la même cohérence (un admin devrait pouvoir voir la fiche d'un enfant qu'il n'a pas créé).