Repository navigation
feat(admin): confirmer la connexion avant une action sensible - #550
Conversation
88f6c98 to
8dc8ef7
Compare
f4bb245 to
256a22d
Compare
|
Branche remise sur la base de la PR #398 corrigée (test instable de |
|
📋 MANAGER, 2026-10-21 — relecture de la pile admin pour Paul (tête Verdict : A, fusionnable telle quelle. Niveau 3, cohérent (session et authentification de l'admin). La fusion attend une demande explicite de Paul. Vérifié :
Choix à valider par Paul : #528 demande de « redemander le code TOTP ». La PR renvoie plutôt à une reconnexion complète (Google puis le code), parce que le SDK web de Firebase ne sait pas redemander le second facteur seul. Ce choix a été pris par Marco comme opérateur de sa session. Il est conforme à la sécurité visée, mais le critère est rempli au sens large. Les réglages, que le wiki range aussi parmi les actions sensibles, sont dans #535. Labels |
8dc8ef7 to
751ab59
Compare
…a deletion A sensitive action of the admin on an account is refused with 403 recent_sign_in_required when the last sign-in, with its second factor, is older than ADMIN_RECENT_SIGN_IN_MINUTES (10 by default). Reading a card and lifting a suspension stay free. The local Auth emulator, which has no second factor, is left alone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…for an old sign-in When the API answers 403 recent_sign_in_required to a role, a suspension or a deletion, the card of the account opens a window instead of the toast « Votre rôle ne permet pas cette action »: it explains that the action needs a recent sign-in with the authenticator code, and offers to sign in again, back to the same page. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
256a22d to
aa4e44a
Compare
Résultat et problème
Une session d'admin restait valable 12 h pour changer un rôle, suspendre ou supprimer un compte : un poste laissé ouvert suffisait. Après 10 minutes sans nouvelle connexion avec le code de l'application d'authentification, ces trois actions demandent maintenant de se reconnecter.
requireRecentSignIncompareauth_timedu jeton (la dernière connexion, avec son second facteur) à un délai, et répond 403recent_sign_in_requiredau-delà. Elle protègePATCH /admin/users/:id/role,POST /admin/users/:id/suspendetDELETE /admin/users/:id. Lire une fiche et réactiver un compte restent libres. Le délai vient deADMIN_RECENT_SIGN_IN_MINUTES: 10 par défaut (la règle du wiki), entre 1 et 720 ; plus court seulement pour essayer en local. Avec l'émulateur Firebase local, qui n'a pas de second facteur, la règle est ignorée, comme pourmfa_required.recent_sign_in_required, la fiche du compte ouvre une fenêtre « Reconnexion nécessaire » au lieu du message « Votre rôle ne permet pas cette action » (qui était faux : le rôle est bon). Elle explique que l'action demande une connexion récente avec le code de l'application d'authentification, et propose « Se reconnecter » ou « Annuler ». « Se reconnecter » ferme la session ; la page de connexion garde l'adresse, la personne revient sur la même page et recommence l'action.Choix à relire : une fenêtre qui renvoie à la connexion, pas une saisie du code seul. Le ticket demande de « redemander le code TOTP ». Le SDK Firebase du navigateur ne sait pas redemander le second facteur seul : rafraîchir
auth_timedemande de se ré-authentifier en entier (fenêtre Google, puis le code). La fenêtre de reconnexion est donc le moyen le plus proche du ticket que l'API permet. Décision de l'opérateur (Marco, 2026-10-20) ; une saisie du code dans une fenêtre, sans repasser par Google, demanderait un autre mécanisme d'authentification côté API.Limites :
Fixes #528
Écrans : A-06 (fiche d'un compte)
Dépendance : PR empilée sur #501 (ticket #344), elle-même sur #500, #499, #479, #398, #365 et #363 (base
issue/344-affiliation).Comment vérifier
Montage local de la pile, base locale migrée, compte
adminconnecté avec un second facteur (Firebase de staging), et dansapps/api/.env:ADMIN_RECENT_SIGN_IN_MINUTES=5(pour ne pas attendre 10 minutes).Tests : 3 tests d'API (
admin-users.test.ts) : les trois actions refusées après le délai, sans rien écrire (journal et ligne du compte inchangés) ; la même action acceptée avec une connexion récente ; la lecture d'une fiche et la réactivation toujours libres avec une connexion ancienne. Un test de configuration (délai : défaut 10, valeur 5, refus de 0). Un test admin pour reconnaître ce refus-là et aucun autre 403.Testé par Marco le 2026-10-20, en local, sur le commit
256a22d(Firebase de staging, base locale, délai à 5 minutes) : le changement de rôle avant le délai, puis, après le délai, la fenêtre « Reconnexion nécessaire » à la place du message sur le rôle, « Annuler », et « Se reconnecter » jusqu'au retour sur la page des utilisateurs avec l'action qui passe. Rien n'a été testé sur un environnement déployé, ni avec un compte connecté par lien e-mail (la reconnexion passe par la même page de connexion).pnpm lint && pnpm typecheck && pnpm testverts en localFormatage mécanique : non
Risque
Niveau : 3 — authentification et session de l'admin : une garde de plus sur le changement de rôle, la suspension et la suppression d'un compte.
Taille : 95 lignes ajoutées côté API (tests compris) et une soixantaine côté admin ; aucun commit au-dessus de 400 lignes. Aucun fichier généré.
Revue OWASP (Top 10, édition en cours)
auth_timedu jeton vérifié par Firebase (jamais d'une valeur envoyée par le client). Une session ancienne ne peut plus, seule, changer un rôle, suspendre ou supprimer un compte.requireAdminSection(rôle, session de 12 h, second facteur) ; les règles deallowedUserActionset les verrous de chaque écriture sont inchangés. Un refus pour connexion ancienne n'écrit rien (testé : journal et compte inchangés).ADMIN_RECENT_SIGN_IN_MINUTESest borné (1 à 720) et validé au démarrage ; sa valeur par défaut est celle du wiki. Il n'y a pas de moyen de désactiver la garde, sauf l'émulateur Firebase local, déjà refusé en production parconfig.ts.admin_actions; les actions faites après reconnexion s'y écrivent comme avant.Nouvelle dépendance : aucune.
Retour arrière
Le revert de la PR suffit : aucune migration, aucune donnée créée. La variable
ADMIN_RECENT_SIGN_IN_MINUTESest facultative ; sans le code qui la lit, elle est ignorée.🤖 Generated with Claude Code