Corrige une faille d'isolation entre comptes et retire l'email des chemins de projet
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:
@@ -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()
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user