fix: A43 TERMINÉ + A38 phase 1 — CSRF rendu côté serveur, helper unique (v7.33.0)
A43 (les 4 items sont clos) :
- `__CSRF_PLACEHOLDER__` supprimé : ContextVar CSRF_TOKEN posée par
CSRFMiddleware avant call_next (même mécanique que le nonce CSP),
global `{{ csrf_token() }}` dans templating, base.html rend
`{"X-CSRF-Token":{{ csrf_token()|tojson }}}` — vide si cookie absent,
`htmx:configRequest` re-lit le cookie à chaque appel → plus jamais de
jeton factice servi.
- Palette : `fetch('/api/search…')` SANS header (GET ∈ SAFE_METHODS →
CSRF inapplicable) — le JSON.parse du body-attr par frappe disparaît.
- utcnow déprécié = 0 dans app/**.py ; health loggé (A25) + booléens db/
gitea = raison ; probe réseau = voulu (test de connectivité).
A38 phase 1 (CSRF unifié) :
- `window.getCsrf()` unique dans le <head> de base.html.
- 76 lectures brutes du cookie → getCsrf() dans 13 fichiers (47 formes
`(…||[])[1]||''`, 25 déclarations `const X = match(…)` avec conversion
de leurs usages `X?X[1]:''` → `X` (noms collectés PAR FICHIER, jamais
de règle globale), 4 formes espacées).
- Définitions dupliquées supprimées : card_detail.html (multi-lignes),
database_table.js (1 ligne dans IIFE → chute sur le global).
- Les 3 variantes de base.html (IIFE getCsrf + 2 getCsrfToken) →
`return getCsrf()`.
- welcome.html garde sa lecture locale (page autonome sans base) ;
reste exactement 1 raw dans base.html (la déf head) + 2 dans welcome.
Test : test_csrf_server_rendered_no_placeholder (pas de placeholder +
token du hx-headers == cookie csrf_token).
suite **1092/1092** · ruff OK · node --check vert · docs à jour
This commit is contained in:
@@ -338,6 +338,34 @@ def test_http_client_shared_and_loop_scoped():
|
||||
assert second is not first
|
||||
|
||||
|
||||
def test_csrf_server_rendered_no_placeholder(client):
|
||||
"""A43-1 : `hx-headers` est rendu côté serveur avec le vrai jeton (plus
|
||||
de `__CSRF_PLACEHOLDER__` servi — la fenêtre de course JS disparaît),
|
||||
et la valeur vaut le cookie `csrf_token` de la session."""
|
||||
import json as _json
|
||||
|
||||
page = None
|
||||
for url in ("/", "/dashboard", "/board", "/notes", "/settings", "/workspaces"):
|
||||
cand = client.get(url)
|
||||
if cand.status_code == 200 and "htmx-config" in cand.text:
|
||||
page = cand
|
||||
break
|
||||
assert page is not None, "aucune page base.html atteignable"
|
||||
# 1ʳᵉ visite : cookie créé dans la response → on refait un aller-retour
|
||||
r = client.get(page.url if hasattr(page, "url") else "/dashboard")
|
||||
if "htmx-config" not in r.text:
|
||||
r = page
|
||||
assert "__CSRF_PLACEHOLDER__" not in r.text, "placeholder servi au navigateur"
|
||||
import re as _re
|
||||
|
||||
m = _re.search(r"hx-headers=\'([^\']*)\'", r.text)
|
||||
assert m, "attribut hx-headers absent"
|
||||
token = _json.loads(m.group(1).replace(""", '"'))["X-CSRF-Token"]
|
||||
cookie = client.cookies.get("csrf_token", "")
|
||||
assert cookie, "cookie csrf_token absent"
|
||||
assert token == cookie, (token[:8], cookie[:8])
|
||||
|
||||
|
||||
def test_no_duplicate_routes():
|
||||
"""A24 : deux routes même méthode+chemin → l'une écrase silencieusement l'autre."""
|
||||
from app.main import app
|
||||
|
||||
Reference in New Issue
Block a user