From 61a273548bb2e13173405df2e63bf5c274295121 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 10 Aug 2026 10:32:18 +0200 Subject: [PATCH] =?UTF-8?q?Lever=20la=20barri=C3=A8re=20d'enr=C3=B4lement?= =?UTF-8?q?=20une=20fois=20les=20codes=20not=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le layout applicatif calcule l'écran d'enrôlement à son propre rendu, et un layout n'est pas rejoué par la navigation client : une fois le second facteur activé, l'écran restait posé sur toutes les routes, quel que soit le menu choisi. L'action d'enrôlement ne peut pas réactualiser la route elle-même sans emporter les codes de secours, qui ne sont affichés qu'une fois. Le rafraîchissement est donc déclenché par l'écran des codes, sur un bouton : c'est leur destinataire qui décide du moment où il les a notés. Co-Authored-By: Claude Opus 5 --- src/components/settings/MfaSettings.tsx | 36 ++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/components/settings/MfaSettings.tsx b/src/components/settings/MfaSettings.tsx index 47129a7..b106b47 100644 --- a/src/components/settings/MfaSettings.tsx +++ b/src/components/settings/MfaSettings.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useRouter } from 'next/navigation'; import { useActionState, useState, useTransition } from 'react'; import { Button } from '@/components/ui/Button'; @@ -28,8 +29,12 @@ export function MfaSettings({ enrolled: boolean; required: boolean; }) { + const router = useRouter(); const [offer, setOffer] = useState(null); const [starting, startTransition] = useTransition(); + // Les codes restent à l'écran tant que personne n'a dit les avoir notés : ce + // sont eux, et non l'horloge, qui décident du moment où l'on passe à la suite. + const [codesNoted, setCodesNoted] = useState(false); const [confirmState, confirm, confirming] = useActionState( confirmEnrolmentAction, @@ -43,8 +48,22 @@ export function MfaSettings({ const codes = confirmState.recoveryCodes; const active = (enrolled || confirmState.ok) && !disableState.ok; - if (codes && codes.length > 0) { - return ; + if (codes && codes.length > 0 && !codesNoted) { + return ( + { + setCodesNoted(true); + // Le layout applicatif décide de l'écran d'enrôlement au rendu, et un + // layout n'est pas rejoué par la navigation client : sans ce + // rafraîchissement, l'écran resterait posé sur toutes les pages, quel + // que soit le menu choisi. Il est déclenché ici, et non par l'action + // d'enrôlement, pour que les codes ne s'effacent pas avant d'avoir été + // notés. + router.refresh(); + }} + /> + ); } if (active) { @@ -193,7 +212,13 @@ export function MfaSettings({ * Ils sont conservés hachés : ni le support ni un administrateur ne peuvent les * relire. La seule issue, ensuite, est d'en régénérer une série. */ -function RecoveryCodes({ codes }: { codes: string[] }) { +function RecoveryCodes({ + codes, + onNoted, +}: { + codes: string[]; + onNoted: () => void; +}) { return (

@@ -214,6 +239,11 @@ function RecoveryCodes({ codes }: { codes: string[] }) { Affichés une seule fois : ils ne sont pas conservés en clair. Chacun ne sert qu’une fois.

+
+ +
); }