Corrige une faille d'isolation entre comptes et retire l'email des chemins de projet
Build and deploy / test-python (push) Successful in 9m39s
Build and deploy / test-js (push) Successful in 52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Le rôle "admin" contournait entièrement l'isolation par projet
(core/auth_guard.py) : il pouvait ouvrir/modifier/supprimer le jeu de
n'importe quel autre compte en connaissant son slug, et la page d'accueil
listait sans filtrage tous les projets de tous les comptes.

- La propriété d'un projet se vérifie désormais sur le segment
  "propriétaire" du slug (id du compte), pour tous les rôles y compris
  admin — un slug "à plat" (sans compte associé) reste réservé à
  l'admin, comportement historique conservé pour ce cas précis.
- routes/games/index.py ne liste plus que les projets du compte connecté.
- Le dossier propriétaire d'un projet est maintenant l'id numérique du
  compte, plus jamais son email slugifié (visible en clair dans chaque
  URL auparavant) — script de migration fourni et déjà exécuté sur les
  données existantes.
- Changer d'email ne renomme plus aucun dossier (n'en dépend plus).
- Deux nouveaux tests de régression, fixtures corrigées en conséquence.
- README réécrit pour refléter l'état actuel du produit (jeu 2D
  uniquement, plus de traces de l'ancien éditeur "document").
This commit is contained in:
william
2026-09-04 22:52:05 +02:00
parent 50835a18e2
commit 7ebc9b143f
16 changed files with 382 additions and 833 deletions
-41
View File
@@ -1,41 +0,0 @@
"""Script à usage unique (PAS exécuté au runtime du moteur) : pose la clé
_meta['onboarding_type'] sur tout projet existant qui ne l'a pas encore
(créé avant l'existence de l'onboarding guidé — voir routes/onboarding/
onboarding_new.py et db/games/game_type_catalog.py). Pas strictement
nécessaire : get_onboarding_type() retombe déjà sur DEFAULT_ONBOARDING_TYPE
("custom") pour tout projet sans cette clé — ce script rend juste ce choix
EXPLICITE dans les données plutôt qu'implicite, sans rien changer au
comportement observable.
Marque "custom" tout projet trouvé sans onboarding_type (comportement
neutre : tableau de bord complet garanti visible, comme aujourd'hui) —
aucune distinction "jeu_2d"/RPG à faire ici, cette notion n'existe plus au
niveau projet depuis la fusion des moteurs (écran par écran, voir
screens/screens_repo/ensure_schema.py) : un projet historiquement "jeu_2d"
reste "custom" comme n'importe quel autre, ses écrans gardent
individuellement leur kind="jeu_2d".
python scripts/backfill_onboarding_type.py
"""
import os
import sys
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _BASE_DIR)
import db # noqa: E402
def backfill():
games = db.list_games()
updated = 0
for g in games:
if db.get_onboarding_type_raw(g["slug"]) is None:
db.set_onboarding_type(g["slug"], "custom")
updated += 1
print(f"{g['slug']!r} -> 'custom'")
print(f"{updated}/{len(games)} projet(s) mis à jour.")
if __name__ == "__main__":
backfill()
+103
View File
@@ -0,0 +1,103 @@
"""Script à usage unique (PAS exécuté au runtime du moteur) : renomme le
dossier PROPRIÉTAIRE de chaque compte (projects/<owner_folder>/, voir
db/games/project_slug.py) de l'ancien schéma slugify(email) — visible en
clair dans chaque slug/URL de l'app, ex. projects/vandal-william-gmail-com/ —
vers l'id numérique du compte (opaque), ex. projects/4/.
core/auth_guard.py vérifie désormais que le segment "propriétaire" d'un
slug correspond à l'id du compte connecté (pour TOUS les rôles, y compris
"admin" — faille corrigée) : cette migration doit avoir tourné avant de
déployer ce correctif, sinon aucun compte existant ne pourrait plus
accéder à ses propres projets (leur dossier reste sous l'ancien nom tant
que ce script n'a pas tourné).
Pour chaque compte : le dossier propriétaire ATTENDU est déduit de
`project_slug` (compte "user", déjà à jour si son email a changé entre
temps) ou, à défaut (compte "admin", qui n'utilise pas cette colonne — ou
tout compte sans projet), de `slugify(email)` (l'ancienne convention,
utilisée pour TOUS les rôles jusqu'à ce correctif). S'il existe sur
disque, il est renommé en `str(user_id)` via db.move_game (déplace TOUS
les projets de ce compte en un coup), puis project_slug est mis à jour
pour les comptes "user".
Mode simulation par défaut (n'écrit rien, affiche ce qui serait fait) —
`--apply` requis pour exécuter réellement :
python scripts/migrate_owner_folders_to_user_id.py # aperçu
python scripts/migrate_owner_folders_to_user_id.py --apply # exécute
"""
import os
import sys
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _BASE_DIR)
import db # noqa: E402
import auth # noqa: E402
from db.constants import PROJECTS_DIR # noqa: E402
from db.games.project_slug import build_slug, split_slug # noqa: E402
from auth.connection import connect as connect_auth_db # noqa: E402
def _expected_old_owner_folder(user):
if user["role"] != "admin" and user["project_slug"]:
owner_folder, project_part = split_slug(user["project_slug"])
if project_part is not None:
return owner_folder
# Repli : compte "admin" (pas de project_slug dédié) ou compte "user"
# sans projet_slug ("user" fraîchement inscrit, pas encore passé par
# l'onboarding — n'a alors aucun dossier à migrer de toute façon) —
# l'ancienne convention posait TOUJOURS owner_folder = slugify(email),
# quel que soit le rôle (voir routes/games/games_new.py avant ce
# correctif).
return db.slugify(user["email"])
def migrate(apply=False):
conn = connect_auth_db()
users = [
dict(r) for r in
conn.execute("SELECT id, email, role, project_slug FROM _users").fetchall()
]
conn.close()
migrated = 0
for user in users:
new_owner_folder = str(user["id"])
old_owner_folder = _expected_old_owner_folder(user)
if old_owner_folder == new_owner_folder:
continue # déjà migré (ou coïncidence — rien à faire non plus)
old_dir = os.path.join(PROJECTS_DIR, old_owner_folder)
if not os.path.isdir(old_dir):
continue # aucun projet pour ce compte — rien à déplacer
project_parts = sorted(
entry for entry in os.listdir(old_dir)
if os.path.isfile(os.path.join(old_dir, entry, "game.db"))
)
if not project_parts:
print(f"{user['email']} ({user['role']}) : dossier {old_owner_folder!r} sans jeu — ignoré.")
continue
sample_slug = build_slug(old_owner_folder, project_parts[0])
print(
f"{user['email']} ({user['role']}) : {old_owner_folder!r} -> {new_owner_folder!r}"
f" ({len(project_parts)} projet(s) : {', '.join(project_parts)})"
)
if not apply:
continue
final_slug = db.move_game(sample_slug, new_owner_folder)
if user["role"] != "admin":
# Un compte "user" n'a qu'UN SEUL projet — le slug renvoyé
# pour celui-ci est donc directement le nouveau project_slug.
auth.set_project_slug(user["id"], final_slug)
migrated += 1
if apply:
print(f"{migrated} compte(s) migré(s).")
else:
print("Aperçu seulement — relance avec --apply pour exécuter.")
if __name__ == "__main__":
migrate(apply="--apply" in sys.argv)