Files
Forge-Engine/auth/ensure_schema.py
T
williamandClaude Sonnet 5 9efe119936 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>
2026-08-28 20:42:39 +02:00

49 lines
1.9 KiB
Python

from .connection import connect
def ensure_users_schema():
conn = connect()
conn.executescript(
"""
CREATE TABLE IF NOT EXISTS _users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
nom TEXT NOT NULL,
prenom TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user',
totp_secret TEXT NOT NULL,
totp_confirmed INTEGER NOT NULL DEFAULT 0,
project_slug TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
"""
)
# Anti-bruteforce (voir rate_limit.py) : ajoutées en ALTER TABLE pour
# ne rien casser sur une base de comptes déjà créée avant cette
# fonctionnalité (le compte admin déjà inscrit sur ce poste, par
# exemple) — CREATE TABLE IF NOT EXISTS ci-dessus n'aurait sinon
# aucun effet sur une table qui existe déjà sans ces colonnes.
cols = {r["name"] for r in conn.execute("PRAGMA table_info(_users)").fetchall()}
if "failed_attempts" not in cols:
conn.execute("ALTER TABLE _users ADD COLUMN failed_attempts INTEGER NOT NULL DEFAULT 0")
if "locked_until" not in cols:
conn.execute("ALTER TABLE _users ADD COLUMN locked_until TEXT")
# Codes de récupération 2FA (voir recovery_codes.py) : table séparée
# plutôt qu'une colonne JSON sur _users, pour pouvoir marquer/consommer
# chaque code UN PAR UN (used_at) sans avoir à relire/réécrire toute
# la liste à chaque connexion.
conn.execute(
"""
CREATE TABLE IF NOT EXISTS _recovery_codes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
code_hash TEXT NOT NULL,
used_at TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
"""
)
conn.commit()
conn.close()