Un jeton unique par session (core/csrf.py, exposé côté Jinja via csrf_token()) est vérifié sur toute requête non-GET par un before_request (core/csrf_guard.py), dans le même esprit que core/auth_guard.py : une seule garde globale plutôt que de toucher aux ~90 routes existantes une par une. L'app entière fait déjà transiter ses formulaires par fetch() : pjax.js intercepte chaque <form> interne et le transforme lui-même en requête fetch (aucun usage de l'attribut d'échappement data-no-pjax nulle part dans le repo, confirmé par grep). Il suffit donc de patcher window.fetch UNE SEULE FOIS (static/csrf_fetch.js) pour y ajouter automatiquement l'en-tête X-CSRFToken sur toute requête non-GET, formulaires pjax comme fetch() écrits à la main dans screen_edit.html/game_dashboard.html/ play.html — sans modifier un seul appel existant. La vérification est désactivée quand app.config["TESTING"] est actif (même convention que Flask-WTF/WTF_CSRF_ENABLED), pour ne pas avoir à ajouter le jeton aux ~170 tests existants qui appellent les routes directement via le client de test Flask. tests/test_csrf.py réactive volontairement la garde pour la mettre à l'épreuve pour de vrai (GET jamais bloqué, POST sans jeton/avec mauvais jeton -> 400, POST avec le bon jeton via l'en-tête ou le champ de formulaire -> succès). templates/play.html reçoit les mêmes deux balises que base.html car il est autonome (ne l'étend pas, propre <html>/<head>). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
3.3 KiB
HTML
59 lines
3.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr" data-theme="dark">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}Forge Engine{% endblock %}</title>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
<script>window.CSRF_TOKEN = {{ csrf_token()|tojson }};</script>
|
|
<script src="{{ url_for('static', filename='csrf_fetch.js') }}"></script>
|
|
{% block extra_head %}{% endblock %}
|
|
</head>
|
|
<body class="{% block body_class %}{% endblock %}">
|
|
<!-- #pageChrome : tout ce qui est en dehors de <main> mais doit quand même
|
|
changer d'une page à l'autre (barre du jeu comprise) — un seul bloc
|
|
stable pour que pjax.js puisse le remplacer d'un coup à chaque
|
|
navigation (voir swapDocument() dans pjax.js). Avant l'ajout de ce
|
|
conteneur, pjax.js cherchait un <header class="topbar"> qui n'a jamais
|
|
existé (la barre est un <nav>, pas un <header>) : la barre de
|
|
navigation du jeu (ajoutée après pjax.js) ne changeait donc jamais
|
|
pendant la navigation pjax — absente en arrivant sur un jeu tant qu'on
|
|
ne rechargeait pas la page en entier (Ctrl+F5), et inversement
|
|
laissée en place en revenant sur l'accueil (liste des jeux) où elle
|
|
n'a pourtant rien à faire. -->
|
|
<div id="pageChrome">
|
|
<nav class="navbar topbar" role="navigation" aria-label="main navigation">
|
|
<div class="navbar-brand">
|
|
<a href="{{ url_for('index') }}" class="navbar-item brand"><strong>🛠️ Forge Engine</strong></a>
|
|
</div>
|
|
{% if g.get('current_user') %}
|
|
<div class="navbar-end" style="margin-left:auto; display:flex; align-items:center; gap:10px; padding-right:12px;">
|
|
<span class="hint">{{ g.current_user.prenom }} {{ g.current_user.nom }}{{ ' (admin)' if g.current_user.role == 'admin' else '' }}</span>
|
|
<form method="post" action="{{ url_for('logout') }}"><button type="submit" class="button is-small">Déconnexion</button></form>
|
|
</div>
|
|
{% endif %}
|
|
</nav>
|
|
{% if game is defined and game %}
|
|
<!-- Barre toujours visible entre les différentes pages d'UN MÊME jeu.
|
|
Écrans/Éléments de jeu/Variables/Objets n'ont plus leur propre page —
|
|
tout ça vit désormais dans les onglets du tableau de bord (voir
|
|
game_dashboard.html) — donc un seul lien y mène. Remplace le fil
|
|
d'Ariane (retiré, devenu redondant) : n'apparaît que si la route a mis
|
|
un `game` dans le contexte du template (déjà fait par toutes les
|
|
pages d'un jeu) — absente sur l'accueil (liste des jeux). "Jouer" est
|
|
délibérément le DERNIER lien — une action à part (ouvre l'aperçu
|
|
jouable dans un nouvel onglet), pas une page de gestion de plus. -->
|
|
<nav class="gameNavBar" aria-label="Navigation du jeu">
|
|
<a href="{{ url_for('game_dashboard', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path == '/game/' ~ game.slug else '' }}" title="Écrants, objets, templates, variables">📊 Tableau de bord</a>
|
|
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Aperçu jouable du jeu">▶️ Jouer</a>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
<main class="content {% block content_class %}{% endblock %}">
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
<script src="{{ url_for('static', filename='pjax.js') }}"></script>
|
|
</body>
|
|
</html>
|