Première des deux grandes fonctionnalités demandées (authentification d'abord, export HTML/CSS/JS autonome ensuite) : - Inscription (nom, prénom, email UNIQUE, mot de passe) avec schéma visuel du mot de passe (jauge + liste de critères qui passent au vert en direct — auth/password_strength.py, mêmes règles vérifiées côté serveur qu'affichées côté client). - Double authentification (TOTP, compatible Google Authenticator/Authy) OBLIGATOIRE dès l'inscription : QR code (SVG, sans dépendance Pillow) à scanner puis code à confirmer avant que le compte soit utilisable — voir auth/create_user.py (totp_confirmed) et routes/auth/register_2fa.py. - Connexion en 2 temps (mot de passe puis code TOTP), déconnexion. - Isolation par utilisateur : un compte "user" est limité à un SEUL projet, dont le dossier est nommé d'après son adresse email (slugifiée) et créé automatiquement dès la 2FA confirmée — aucune page de gestion multi-jeux pour lui (redirigé directement vers son propre tableau de bord). Le rôle "admin" reste illimité, comme le moteur l'a toujours été (le TOUT PREMIER compte jamais créé sur une base de comptes vide devient automatiquement admin — voir auth/is_first_user.py — pas de mot de passe par défaut à faire circuler : s'inscrire en premier suffit). Un compte "user" ne peut pas non plus supprimer son unique projet (aucune façon d'en recréer un ensuite). - Garde d'accès globale (core/auth_guard.py, un seul before_request) : toute page exige une connexion, sans avoir touché individuellement aux ~80 routes déjà existantes du moteur. tests/conftest.py isole complètement les tests de la vraie base de comptes (FORGE_USERS_DB_PATH/FORGE_SECRET_KEY_PATH vers un dossier temporaire propre à la session de tests) et authentifie automatiquement la fixture `client` partagée en tant que compte admin de test — les 155 tests déjà existants continuent de passer SANS AUCUNE modification de leur côté, exactement comme avant l'authentification. 10 nouveaux tests dédiés (tests/test_auth.py) : inscription/mots de passe/2FA/connexion/ isolation par projet/blocage de suppression, vérifiés en conditions réelles (vrai client de test Flask, vraie base SQLite, vrais codes TOTP calculés avec pyotp). 165 tests au total, tous au vert. Nouvelles dépendances : pyotp, qrcode (requirements.txt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
import os
|
|
|
|
from ..connection import connect
|
|
from ..game_dir import game_dir
|
|
from ..slugify import slugify
|
|
|
|
|
|
def create_game(name, slug_override=None):
|
|
"""Feature 1 : crée le dossier du jeu, ses fichiers index.html/css/js
|
|
reliés entre eux, et sa base de données dédiée (nom du jeu en méta).
|
|
|
|
slug_override : impose le nom du dossier au lieu de le dériver de
|
|
`name` — utilisé par l'inscription (auth/) pour que le dossier d'un
|
|
compte porte son adresse email (slugifiée), indépendamment du nom
|
|
donné à son jeu. Toujours rendu unique de la même façon (suffixe
|
|
-2/-3...) si, par un hasard extrême, le dossier existe déjà."""
|
|
slug = slugify(slug_override) if slug_override else slugify(name)
|
|
base = slug
|
|
n = 2
|
|
while os.path.isdir(game_dir(base)):
|
|
base = f"{slug}-{n}"
|
|
n += 1
|
|
slug = base
|
|
|
|
os.makedirs(game_dir(slug), exist_ok=True)
|
|
|
|
index_html = f"""<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{name}</title>
|
|
<link rel="stylesheet" href="index.css">
|
|
</head>
|
|
<body>
|
|
<h1>{name}</h1>
|
|
<p>Squelette généré par le moteur Forge — à remplir au fur et à mesure
|
|
des fonctionnalités du jeu.</p>
|
|
<script src="index.js"></script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
index_css = """body {
|
|
font-family: system-ui, sans-serif;
|
|
margin: 40px;
|
|
color: #222;
|
|
}
|
|
"""
|
|
index_js = """// index.js — point d'entrée du jeu, généré par le moteur Forge.
|
|
console.log("Jeu chargé.");
|
|
"""
|
|
with open(os.path.join(game_dir(slug), "index.html"), "w", encoding="utf-8") as f:
|
|
f.write(index_html)
|
|
with open(os.path.join(game_dir(slug), "index.css"), "w", encoding="utf-8") as f:
|
|
f.write(index_css)
|
|
with open(os.path.join(game_dir(slug), "index.js"), "w", encoding="utf-8") as f:
|
|
f.write(index_js)
|
|
|
|
conn = connect(slug)
|
|
conn.executescript(
|
|
"""
|
|
CREATE TABLE _meta (key TEXT PRIMARY KEY, value TEXT);
|
|
CREATE TABLE _definitions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL UNIQUE,
|
|
table_name TEXT NOT NULL UNIQUE,
|
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE TABLE _fields (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
definition_id INTEGER NOT NULL REFERENCES _definitions(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
relation_definition_id INTEGER REFERENCES _definitions(id),
|
|
required INTEGER NOT NULL DEFAULT 0,
|
|
position INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
"""
|
|
)
|
|
conn.execute("INSERT INTO _meta (key, value) VALUES ('name', ?)", (name,))
|
|
conn.commit()
|
|
conn.close()
|
|
return slug
|