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>
37 lines
1.7 KiB
Python
37 lines
1.7 KiB
Python
"""
|
|
app.py — moteur Forge, point d'entrée.
|
|
|
|
Lance un serveur web local. Fonctionnalités construites pour l'instant
|
|
(le reste viendra au fur et à mesure) :
|
|
1. Créer un jeu (dossier + index.html/css/js + base de données dédiée).
|
|
2. Définir des "objets" (comme des tables de base de données, avec des
|
|
champs typés, et des relations vers d'autres objets déjà définis).
|
|
3. Le moteur lit ces définitions et génère lui-même le formulaire adapté
|
|
pour saisir des données et les enregistrer dans la vraie base SQLite.
|
|
4. CRUD complet sur les trois niveaux (jeu / objet / donnée) : renommer
|
|
et supprimer un jeu, modifier une définition d'objet (ajouter/retirer
|
|
des champs) et la supprimer, modifier et supprimer une ligne de
|
|
données — avec blocage/avertissement quand une suppression casserait
|
|
une relation existante.
|
|
"""
|
|
|
|
import threading
|
|
import webbrowser
|
|
|
|
from core.flask_app import app
|
|
from core import jinja_filters # noqa: F401 - enregistre les filtres Jinja
|
|
import routes # noqa: F401 - enregistre toutes les routes sur `app`
|
|
from core import auth_guard # noqa: F401 - enregistre la garde de connexion (après les routes)
|
|
from core import csrf # noqa: F401 - enregistre csrf_token() comme variable globale Jinja
|
|
from core import csrf_guard # noqa: F401 - enregistre la vérification du jeton CSRF
|
|
from core import recovery_codes_flash # noqa: F401 - enregistre pop_recovery_codes() comme variable globale Jinja
|
|
|
|
|
|
def _open_browser():
|
|
webbrowser.open("http://127.0.0.1:5050/")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
threading.Timer(1.0, _open_browser).start()
|
|
app.run(host="127.0.0.1", port=5050, debug=True, use_reloader=False)
|