"""Garde d'accès globale — connexion obligatoire pour tout le moteur, et isolation par utilisateur d'un SEUL projet (sauf le rôle "admin", illimité comme avant l'authentification). Un seul before_request plutôt qu'un décorateur à poser sur chacune des ~80 routes existantes : moins de risque d'en oublier une, et aucun fichier de routes existant n'a besoin d'être modifié. Cet import doit avoir lieu APRÈS `import routes` (voir app.py/conftest.py) pour que `app.url_map` connaisse déjà toutes les routes au moment où ce module tente de résoudre request.endpoint — en pratique sans importance ici (la résolution se fait à la requête, pas à l'import), mais gardé pour rester cohérent avec l'ordre d'import du reste du moteur.""" from flask import g, redirect, request, session, url_for, abort import auth from .flask_app import app # Endpoints accessibles SANS être connecté — tout le reste exige une # session valide. "static" (CSS/JS/images) doit rester public : la page # de connexion elle-même en a besoin pour s'afficher. # # game_play_public/runtime_payload_public/flow_node_run_data_public/ # flow_node_run_variable_public (routes/public_play/) : la route PUBLIQUE # /jouer/, pour un vrai visiteur anonyme (état par joueur, Phase 1) # — chaque vue vérifie ELLE-MÊME db.is_public_played(slug) (404 sinon), # cette garde générique ne fait qu'autoriser l'accès sans connexion, elle # ne dispense d'aucune autre vérification. _PUBLIC_ENDPOINTS = { "static", "login", "login_2fa", "register", "register_2fa", "logout", "forgot_password", "reset_password", "game_play_public", "runtime_payload_public", "flow_node_run_data_public", "flow_node_run_variable_public", "flow_node_run_add_row_public", } @app.before_request def _require_login_and_enforce_project_isolation(): endpoint = request.endpoint if endpoint is None or endpoint in _PUBLIC_ENDPOINTS: return None user_id = session.get("user_id") if not user_id: return redirect(url_for("login", next=request.path)) user = auth.get_user_by_id(user_id) if not user: # Compte supprimé/introuvable depuis la dernière requête : la # session ne doit jamais rester "connectée" dans le vide. session.clear() return redirect(url_for("login")) g.current_user = user # Un compte "user" n'a accès qu'à SON SEUL projet (project_slug) — un # "admin" reste illimité, exactement comme avant l'authentification. # Les routes de gestion multi-jeux (page d'accueil, "+ Nouveau jeu") # n'ont pas leur place pour un compte à projet unique : redirigées # directement vers son propre tableau de bord plutôt qu'un 403 sec. if user["role"] != "admin": if endpoint in ("index", "games_new") and user.get("project_slug"): return redirect(url_for("game_dashboard", slug=user["project_slug"])) slug = request.view_args.get("slug") if request.view_args else None if slug is not None and slug != user.get("project_slug"): abort(403) if endpoint == "game_delete": # Un compte "user" n'a qu'UN SEUL projet, et aucun moyen d'en # recréer un une fois supprimé (games_new le renvoie toujours # vers project_slug, même si le dossier n'existe plus) — # bloqué plutôt que de risquer de le laisser sans projet du # tout. Un admin, lui, peut toujours supprimer ses jeux comme # avant (aucune restriction ci-dessus pour ce rôle). abort(403) return None