Ajoute la protection CSRF sur tous les formulaires et requêtes AJAX

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>
This commit is contained in:
william
2026-08-28 20:35:32 +02:00
co-authored by Claude Sonnet 5
parent 989e899a96
commit a52b244f27
8 changed files with 198 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
/*
* csrf_fetch.js — pose automatiquement le jeton CSRF (voir core/csrf.py/
* core/csrf_guard.py) sur CHAQUE fetch() non-GET de l'appli, sans avoir à
* modifier individuellement les nombreux appels fetch() déjà écrits à la
* main (screen_edit.html, game_dashboard.html, play.html...) ni les
* formulaires classiques (pjax.js les transforme lui-même en fetch(), voir
* son commentaire d'en-tête — cette rustine s'applique donc aussi à eux).
*
* window.CSRF_TOKEN est posé une fois par templates/base.html (valeur
* injectée par Jinja, voir core/csrf.py::get_csrf_token) — chargé AVANT ce
* script pour qu'il soit déjà disponible ici.
*/
(function () {
"use strict";
if (window.fetch.__forgeCsrfPatched) return; // rejoué par pjax.js (runScriptsIn) sans effet une 2e fois
var originalFetch = window.fetch;
function patchedFetch(input, init) {
init = init || {};
var method = (init.method || "GET").toUpperCase();
if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
var headers = new Headers(init.headers || {});
if (!headers.has("X-CSRFToken") && window.CSRF_TOKEN) {
headers.set("X-CSRFToken", window.CSRF_TOKEN);
}
init = Object.assign({}, init, { headers: headers });
}
return originalFetch(input, init);
}
patchedFetch.__forgeCsrfPatched = true;
window.fetch = patchedFetch;
})();