Bugs & failles actives connues
Contrairement à Dette technique connue (différable, pas urgent), tout ce qui est listé ici est un dysfonctionnement ou un risque de sécurité déjà actif en l'état du code — pas une amélioration future. À traiter en priorité, pas "quand l'occasion se présente".
Vérifié le 2026-08-13, complété le 2026-08-24, puis recorrigé le 2026-08-24 après un second git pull du backend qui a fait apparaître la route "mot de passe oublié" (section "❌ Non corrigés").
❌ Non corrigés
"Mot de passe oublié" backoffice — la route backend existe désormais, mais deux URL codées en dur cassent encore le flux
Mise à jour le 2026-08-24 (second pull) : contrairement à ce que cette page indiquait plus tôt dans la journée, le backend main a maintenant POST /api/auth/forgot-password/POST /api/auth/reset-password (cdde188, auth.controller.js) — implémentation par ailleurs propre : ne révèle jamais si l'email existe, token crypto.randomBytes(32) unique en base (users.reset_token, expirant 15 min), regex de mot de passe fort à la réinitialisation.
Reste cassé pour deux raisons indépendantes, une de chaque côté :
- Backend —
services/mailer.js::sendResetPasswordEmailconstruit le lien avec`http://localhost:5173/admin/reset-password?token=${token}`codé en dur, alors queprocess.env.FRONTEND_URLexiste déjà dans.env.exampleet est déjà utilisé pour ce cas exact ailleurs (utils/mailer.js, corrigé le 2026-08-13 — voir Dette technique connue). Un email de réinitialisation envoyé depuis un serveur en production pointera donc verslocalhost, injoignable pour le destinataire. - Frontend (non re-vérifié à cette date, seul le backend a été re-tiré) —
ForgotPasswordView.vue/ResetPasswordView.vueappellent une URL codée en dur (fetch('http://localhost:5000/api/auth/forgot-password', ...)) au lieu de l'instanceapi(axios) àbaseURLconfigurable utilisée partout ailleurs.
Voir Authentification & 2FA.
Nouveau cache sur taux de change/publications/réglages site — jamais invalidé
Le 2026-08-24, un cache Redis a été ajouté sur GET /exchange-rates (10 min), GET /publications (5 min) et GET /site-settings/:key (10 min) — voir Contenu vitrine. Contrairement au cache déjà existant sur GET /slides (qui appelle apicache.clear() à chaque écriture), aucun des trois contrôleurs concernés n'invalide le cache après une création/modification. Toute publication d'un taux de change, d'une publication ou d'un changement de réglage met donc jusqu'à 5-10 minutes à apparaître publiquement, sans aucun moyen de forcer un rafraîchissement.
/api/otp/send et /api/otp/verify sans limiteur de débit dédié
Mise à jour le 2026-08-24 (second pull) : le backend a réorganisé son rate-limiting global (eb315ff) — un strictLimiter (5 000 requêtes/15 min par IP) s'applique désormais à toutes les routes POST/PUT/DELETE de l'API, contre 10 000 req/15 min toutes méthodes confondues auparavant. C'est mieux, mais toujours pas dédié : contrairement à /api/tracking (trackingLimiter, 15 req/15 min, voir OTP, SMS & vérification SBA), un code à 6 chiffres avec une fenêtre de validité de 5 minutes reste en pratique bruteforçable par numéro de téléphone dans l'enveloppe des 5 000 requêtes, et chaque tentative sur /otp/send déclenche potentiellement un vrai envoi SMS facturé via Kannel et un vrai appel au middleware bancaire UWM. À corriger avant tout trafic public réel sur ces formulaires.
✅ Corrigés (2026-08-13)
updateLoanStatus n'avait aucune vérification d'appartenance à une agence
backend/src/controllers/loan.controller.js, fonction updateLoanStatus (PATCH /api/loan-requests/:id) — contrairement aux 3 autres endpoints du contrôleur, celle-ci ne vérifiait jamais que req.user avait le droit d'agir sur le dossier ciblé : tout utilisateur backoffice authentifié pouvait changer le statut de n'importe quelle demande de prêt, y compris celles d'une autre agence — le point le plus sensible du contrôleur puisqu'il exécute la décision (pas juste une consultation). canAccessAgency(req.user, currentLoan.id_agence) ajouté juste après le chargement du dossier.
account_requests : lecture entièrement publique, sans authentification
backend/src/routes/account_requests.routes.js : GET / et GET /:id n'avaient aucun middleware verifyToken — n'importe qui, sans être connecté, pouvait lister l'intégralité des demandes d'ouverture de compte (identité, téléphone, email, adresse, données FATCA/TIN). verifyToken ajouté sur les deux routes, et agencyScopeWhere/canAccessAgency appliqués à getAll/getById (account_requests.controller.js, account_requests.service.js::getAll accepte désormais un where) — un utilisateur authentifié ne voit maintenant que les demandes de sa propre agence (ou de sa zone pour CHEF_ZONE), cohérent avec account.controller.js et loan.controller.js.
loan_parameters modifiable sans authentification
PUT /api/loan-parameters/:id exige désormais verifyToken (GET reste public, consommé par les formulaires publics).
Alertes lisibles sans authentification
GET /api/alertes et GET /api/alertes/:id exigent désormais verifyToken.
POST /api/users accessible à tout utilisateur authentifié
POST/PUT/DELETE /api/users exigent désormais isSuperAdmin (même middleware que /api/roles).
Le bypass 000000 de la 2FA n'existe pas (mais était documenté comme existant)
Le README backend et le commentaire dans SchoolLoanView.vue ont été corrigés pour ne plus affirmer l'existence de ce bypass. Le placeholder OTP côté prêt scolaire (000000) reste en place — c'est un mécanisme distinct, volontaire, en attendant l'intégration Sopra Banking Amplitude, voir Demande de prêt scolaire.
Mise à jour de statut d'une demande de compte : route inexistante
AccountRequestDetailView.vue/AccountRequestsView.vue appellent désormais PATCH /api/account-requests/:id/status (route réellement exposée).
LoanDocumentsConfigView.vue appelait le mauvais endpoint de suppression
Corrigé : DELETE /loan-document-requirements/:id (au lieu de /loan-requirements/:id).
CNI (signataires/bureau/promoteur) jamais enregistrées comme documents
OpenAccountView.vue les inclut désormais dans additional_data.documents (mêmes clés descriptives : signataire_N_cni, bureau_{role}_cni, promoteur_cni) — elles passent maintenant par le mécanisme générique déjà utilisé pour les autres pièces, aucun changement backend nécessaire. Voir Ouverture de compte.
Sauvegarde des slides : suppression totale sans transaction
Un nouvel endpoint PUT /api/slides (slidesService.replaceAll) remplace l'ancienne séquence DELETE/POST multiples non transactionnelle par une seule transaction Prisma (delete + recreate atomiques, éléments inclus). Voir Éditeur de bannières.
URL de setup-password codée en dur
utils/mailer.js lit désormais process.env.FRONTEND_URL (repli sur http://localhost:5173 si absent) ; FRONTEND_URL ajoutée à .env.example.
loan_field_requirements réservé SUPER_ADMIN, incohérent avec loan_document_requirements
La restriction isSuperAdmin a été retirée — les deux routers de configuration de prêt exigent maintenant uniformément verifyToken seul.
CHEF_ZONE ne filtrait pas réellement par zone
Le module agencyScopeWhere/canAccessAgency (backend/src/utils/agency-scope.js) avait bien été écrit et branché sur loan.controller.js/account.controller.js/account_requests.controller.js, mais restait inopérant pour deux raisons cumulées, découvertes seulement en testant avec un vrai compte CHEF_ZONE (login + 2FA + appels API réels) :
CHEF_ZONEavait la permissionview_all_agencies(seed/seed_roles.js) — cette permission court-circuiteagencyScopeWhere/canAccessAgencyen premier (accès total, sans restriction), rendant la logique "restreindre à la zone" écrite juste en dessous inatteignable. Retirée des permissions deCHEF_ZONE.- Une fois ce point corrigé, plus aucun dossier n'était visible pour
CHEF_ZONE(régression inverse) :id_zonen'était jamais inclus dans le JWT émis parverify2fa— seulid_agencel'était. Ajouté au payload JWT et à la sélection Prisma degetMe(auth.controller.js).
Leçon : une correction non testée en conditions réelles (vrai login, pas juste une relecture de code) peut sembler correcte tout en étant totalement inopérante.
Un CHEF_ZONE est désormais réellement restreint aux agences de sa propre zone (id_zone), vérifié par un test de bout en bout (accès accepté sur une agence de sa zone, 403 sur une agence d'une autre zone). Voir Agences, zones & GAB et Authentification & 2FA.
README backend : message de démarrage NLP trompeur
"NLP Model Trained Successfully with 26 intents" a été retiré — ce message décrivait un ancien moteur NLP (@nlpjs) que le code actuel ne charge plus (le vrai moteur actif est un système RAG + Ollama, désactivé côté serveur par choix, pas un bug — voir Chatbot (NLP + RAG)). Le serveur n'affiche désormais que UTB Backend Server running on port 3000 au démarrage, ce que le README reflète maintenant.
Doc Swagger de users.routes.js obsolète
Corrigée : password retiré des champs requis/documentés de POST /api/users (jamais utilisé par create(), qui crée le compte sans mot de passe et envoie une invitation) ; two_factor_secret/two_factor_enabled retirés (destructurés mais jamais utilisés) ; id_agence/id_zone ajoutés (réellement pris en compte, absents de la doc précédente).
seed_fields.js — entrée retirée, le fichier n'existe pas
Une version antérieure de cette page affirmait qu'un ancien script seed_fields.js traînait à la racine du dépôt backend, hérité d'avant la réorganisation des scripts de seed. Vérification faite le 2026-08-13 : ce fichier n'existe pas dans le dépôt actuel — l'affirmation provenait d'un contenu migré depuis l'ancienne doc du projet sans revérification indépendante à ce moment-là. Corrigé ici pour que l'erreur ne se propage pas plus loin.