fix: A19 (partiel) — CSRF réservé aux vrais cas d'exemption (v7.3.5)
- Scan de tous les appels `fetch` non-GET du front par préfixe : 12 préfixes n'ont AUCUN call site sans `X-CSRF-Token` → sortis de `EXCLUDED_PATHS` (`/db/`, `/workspace`, `/workspace/automations`, `/board/api/favorites`, `/api/pages`, `/api/recents`, `/api/notifications`, `/api/comments`, `/api/automations`, `/api/github`, `/api/admin`, `/api/onboarding`) - `welcome.html` : les 2 POST `/api/onboarding/*` reçoivent le header (`adminFetch` prouve que `/api/admin` était déjà couvert) - reste A19 (front à équiper) : `/api/workspace` (+`/api/workspaces`), `/api/local-workspace`, `/api/settings`, `/api/gitea`, `/api/agent` — 49 fetch - tests : helper `anon_csrf()` (anonyme + CSRF valide) pour isoler les 401 de route du 403 middleware — 4 tests d'anonymat ajustés - suite **1026/1026** · `ruff check app tests` OK
This commit is contained in:
+1
-1
@@ -153,7 +153,7 @@ async def lifespan(_app: FastAPI):
|
||||
|
||||
app = FastAPI(
|
||||
title="FlowDeck",
|
||||
version="7.3.4",
|
||||
version="7.3.5",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
lifespan=lifespan,
|
||||
|
||||
+16
-1
@@ -18,7 +18,22 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
||||
SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
|
||||
# NOTE: ``/scim/v2`` is Bearer-token only (no cookie auth), so CSRF does not
|
||||
# apply — IdP SCIM clients (Okta, Entra) cannot send an X-CSRF-Token.
|
||||
EXCLUDED_PATHS = {"/api/webhook", "/api/v1", "/api/v2", "/scim/v2", "/auth/callback", "/auth/register", "/auth/local-login", "/auth/local-verify", "/auth/2fa", "/auth/saml", "/auth/oidc", "/auth/webauthn", "/board/api/favorites", "/api/workspace", "/api/local-workspace", "/api/settings", "/db/", "/workspace", "/api/frontend-error", "/api/admin", "/api/gitea", "/api/github", "/api/pages", "/api/recents", "/api/csrf-token", "/api/notifications", "/api/comments", "/api/agent", "/api/automations", "/workspace/automations", "/api/onboarding", "/s/", "/f/"}
|
||||
# A19 : le CSRF ne s'exempte plus que pour ce qui ne dépend PAS d'un cookie de
|
||||
# session (Bearer, webhooks entrants, callbacks OAuth/SSO, pages publiques,
|
||||
# probe d'infra, remontée d'erreur client).
|
||||
# Retirés après scan du front (tous les appels non-GET envoient déjà
|
||||
# `X-CSRF-Token`) : /db/, /workspace, /workspace/automations,
|
||||
# /board/api/favorites, /api/pages, /api/recents, /api/notifications,
|
||||
# /api/comments, /api/automations, /api/github, /api/admin, /api/onboarding.
|
||||
# Reste A19 (front à équiper d'abord) : /api/workspace (couvre aussi
|
||||
# /api/workspaces), /api/local-workspace, /api/settings, /api/gitea, /api/agent.
|
||||
EXCLUDED_PATHS = {
|
||||
"/api/webhook", "/api/v1", "/api/v2", "/scim/v2",
|
||||
"/auth/callback", "/auth/register", "/auth/local-login", "/auth/local-verify",
|
||||
"/auth/2fa", "/auth/saml", "/auth/oidc", "/auth/webauthn",
|
||||
"/s/", "/f/", "/api/frontend-error", "/api/csrf-token",
|
||||
"/api/workspace", "/api/local-workspace", "/api/settings", "/api/gitea", "/api/agent",
|
||||
}
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
# Webhook receiver, OAuth callback, and internal API are exempt
|
||||
|
||||
@@ -155,7 +155,7 @@ function onboarding() {
|
||||
async createWorkspace() {
|
||||
this.saving = true;
|
||||
try {
|
||||
const r = await fetch('/api/onboarding/workspace', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({name:this.wsName.trim()})});
|
||||
const r = await fetch('/api/onboarding/workspace', {method:'POST', headers:{'Content-Type':'application/json', 'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, body: JSON.stringify({name:this.wsName.trim()})});
|
||||
const d = await r.json();
|
||||
if (!r.ok) { this.toast(d.detail || 'Erreur', true); return; }
|
||||
this.workspaceId = d.id;
|
||||
@@ -172,7 +172,7 @@ function onboarding() {
|
||||
async createProject() {
|
||||
this.saving = true;
|
||||
try {
|
||||
const r = await fetch('/api/onboarding/project', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({title:this.projectTitle.trim(), workspace_id:this.workspaceId})});
|
||||
const r = await fetch('/api/onboarding/project', {method:'POST', headers:{'Content-Type':'application/json', 'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, body: JSON.stringify({title:this.projectTitle.trim(), workspace_id:this.workspaceId})});
|
||||
const d = await r.json();
|
||||
if (!r.ok) { this.toast(d.detail || 'Erreur', true); return; }
|
||||
this.toast('Projet créé 🎉');
|
||||
|
||||
Reference in New Issue
Block a user