Ajoute des codes de récupération 2FA (perte du téléphone)
10 codes à usage unique (format "xxxx-xxxx-xxxx") sont générés à
l'instant même où la 2FA est confirmée (auth/recovery_codes.py, table
_recovery_codes séparée pour marquer/consommer chaque code un par un) —
seul leur hash (werkzeug, comme les mots de passe) est stocké, ils ne
sont visibles en clair qu'à cet instant précis.
Plutôt que d'interrompre la redirection habituelle après confirmation de
la 2FA, les codes sont posés en session ("recovery_codes_to_show") et
affichés une seule fois, en modal, dès le premier rendu de base.html qui
suit (core/recovery_codes_flash.py, session.pop) — préserve tel quel le
comportement de redirection déjà couvert par les tests existants.
Sur /login/2fa, un code de récupération est accepté à la place du code
TOTP habituel (routes/auth/login_2fa.py) : verify_totp est essayé en
premier (verify_recovery_code consomme le code dès qu'il correspond, on
ne veut pas en griller un pour rien sur une saisie qui aurait en fait
été un TOTP valide). Compte toujours vers le même compteur anti-bruteforce
que le code TOTP (déjà en place, voir auth/rate_limit.py).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a52b244f27
commit
9efe119936
@@ -4,7 +4,7 @@
|
||||
<section class="section">
|
||||
<div class="container" style="max-width:420px;">
|
||||
<h1 class="title">Code de vérification</h1>
|
||||
<p class="hint mb-4">Ouvre ton application d'authentification et saisis le code à 6 chiffres affiché.</p>
|
||||
<p class="hint mb-4">Ouvre ton application d'authentification et saisis le code à 6 chiffres affiché, ou un de tes codes de récupération si tu n'y as plus accès.</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="notification is-danger">{{ error }}</div>
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
<form method="post" action="{{ url_for('login_2fa') }}">
|
||||
<div class="field">
|
||||
<label class="label">Code à 6 chiffres</label>
|
||||
<div class="control"><input class="input" type="text" name="code" inputmode="numeric" pattern="[0-9]*" maxlength="6" autocomplete="one-time-code" required autofocus></div>
|
||||
<label class="label">Code à 6 chiffres ou code de récupération</label>
|
||||
<div class="control"><input class="input" type="text" name="code" autocomplete="one-time-code" required autofocus></div>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<button type="submit" class="button primary is-fullwidth">Se connecter</button>
|
||||
|
||||
@@ -22,6 +22,24 @@
|
||||
ne rechargeait pas la page en entier (Ctrl+F5), et inversement
|
||||
laissée en place en revenant sur l'accueil (liste des jeux) où elle
|
||||
n'a pourtant rien à faire. -->
|
||||
{% set recovery_codes = pop_recovery_codes() %}
|
||||
{% if recovery_codes %}
|
||||
<div class="modal is-active" id="recoveryCodesModal">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box">
|
||||
<h2 class="title is-4">Tes codes de récupération</h2>
|
||||
<p class="hint mb-4">Note ces 10 codes en sécurité (gestionnaire de mots de passe, papier...) : chacun ne fonctionne qu'une seule fois et permet de te connecter si tu perds l'accès à ton application d'authentification. Ils ne seront plus jamais affichés.</p>
|
||||
<div class="recoveryCodesGrid">
|
||||
{% for code in recovery_codes %}
|
||||
<code class="recoveryCode">{{ code }}</code>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="button" class="button primary is-fullwidth mt-4" onclick="document.getElementById('recoveryCodesModal').remove()">J'ai noté mes codes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="pageChrome">
|
||||
<nav class="navbar topbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
|
||||
Reference in New Issue
Block a user