fix: A21 phase 2c — 190 routes hors loop, 86 % total (v7.26.0)
FlowDeck CI / lint (push) Successful in 1m55s
FlowDeck CI / test (push) Successful in 15m23s
FlowDeck CI / docker (push) Canceled after 0s

4 passes (283 → 93 routes async sur 667 = 86 % hors loop, avant 61 %) :

A. RACINE AUTH — `get_current_user` (auth/session.py) était `async def`
   SANS aucun await (cookie decode = synchrone) ; idem ses clones :
   `agent._current_user_id/_workspace_id/_current_admin` (34 sites) et
   `sso._require_admin` (corps 0 await, 6 sites) → `def` +
   47 `await` supprimés. Piège : 3 call sites passaient par l'alias `gcu`
   (grep littéral aveugle) — 8 tests en échec → corrigés.

B. Re-scan : 19 routes devenues SANS await → `def` (agent 8, sso 5,
   web_clipper 3, projects 2, auth 1…).

C/D. 155 routes dont les seuls awaits = `request.json()` / événements :
   - try/except `body = {}` → `Body(default={})` (même tolérance)
   - try/except `raise HTTPException(400)` → `Body(...)` REQUIS
     (422 FastAPI — aucun test ne couvrait le 400)
   - forme conditionnelle `request.json() if content-type else {}`
     (54 sites) → défaut `{}` (sans corps = `{}` dans les 2 cas)
   - `await fire_*` → `run_event_sync(...)` ; imports `Body` /
     `run_event_sync` ajoutés aux routers convertis

Reste async (93, justifié) : form/upload/file (22), réseau gitea/llm/oidc,
`_json_body` (9), 2 JSON inline en argument, 1 fallback logique
(capture_frontend_error), 1 lecture conditionnelle (web_clipper), mixtes.

suite **1089/1089** · ruff OK · docs à jour
This commit is contained in:
2026-10-01 22:17:48 -04:00
parent 8d0d69e7b8
commit 45e59009c3
34 changed files with 2479 additions and 680 deletions
+8 -24
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
import logging
import secrets
from fastapi import APIRouter, Query, Request
from fastapi import APIRouter, Body, Query, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from app.auth.session import SessionManager
@@ -220,15 +220,11 @@ def login(request: Request, provider: str = Query("gitea")):
@router.post("/register")
async def register(request: Request):
def register(request: Request, body: dict = Body(default={})):
"""Register a new local account."""
from app.db import get_conn
from app.password_utils import hash_password
try:
body = await request.json()
except Exception:
body = {}
email = body.get("email", "").strip()
password = body.get("password", "").strip()
name = body.get("name", email.split("@")[0] if "@" in email else email)
@@ -277,7 +273,7 @@ async def register(request: Request):
@router.post("/local-login")
async def local_login(request: Request):
def local_login(request: Request, body: dict = Body(default={})):
"""Login with email + password."""
import time
@@ -285,10 +281,6 @@ async def local_login(request: Request):
from app.db import get_conn
from app.password_utils import is_locked, verify_password
try:
body = await request.json()
except Exception:
body = {}
email = body.get("email", "").strip()
password = body.get("password", "").strip()
@@ -410,7 +402,7 @@ async def callback(
oauth_mode = request.session.pop("oauth_mode", "")
if oauth_mode == "link":
from app.auth.session import get_current_user as gcu
current = await gcu(request)
current = gcu(request)
if not current:
return HTMLResponse("<h1>Not logged in — please log in first</h1>", status_code=400)
from app.db import get_conn as _gc
@@ -480,10 +472,10 @@ def logout(request: Request):
@router.get("/user")
async def current_user(request: Request):
def current_user(request: Request):
"""Return current user info as JSON."""
from app.auth.session import get_current_user as gcu
user = await gcu(request)
user = gcu(request)
if not user:
return {"authenticated": False}
return {"authenticated": True, "user": user}
@@ -492,16 +484,12 @@ async def current_user(request: Request):
# ── v7.2.0 — TOTP 2FA ─────────────────────────────────────────────────────
@router.post("/local-verify")
async def local_verify(request: Request):
def local_verify(request: Request, body: dict = Body(default={})):
"""Exchange a 2FA ``pending`` token + TOTP/backup code for a session."""
from fastapi.responses import JSONResponse
from app.db import get_conn
from app.services import two_factor as _2fa
try:
body = await request.json()
except Exception:
body = {}
user_id = _2fa.redeem_pending(body.get("pending", ""))
if not user_id:
return JSONResponse({"error": "Challenge expired — log in again"}, status_code=401)
@@ -544,15 +532,11 @@ def twofa_setup(request: Request):
@router.post("/2fa/activate")
async def twofa_activate(request: Request):
def twofa_activate(request: Request, body: dict = Body(default={})):
from fastapi.responses import JSONResponse
from app.services import two_factor as _2fa
user = _session_user_or_401(request)
try:
body = await request.json()
except Exception:
body = {}
try:
codes = _2fa.activate_secret(user["id"], body.get("secret", ""),
body.get("code", ""))