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()