fix: A12–A24 — SSRF, auth routes legacy, uploads, N+1 et routes doublonnes (v7.3.3)
- A12 — `og_fetcher` : GET sans `follow_redirects`, `_is_public_host` revérifié à chaque saut (max 5) ; `POST /board/api/og/metadata` → 400 sur hôte privé/loopback - A13 — router automations sous `Depends(_require_session)` (CRUD, run, press-button) + `created_by` sans fallback ; action `webhook` validée par `_is_public_host` avant POST (SSRF) - A15 — webhooks sortants : `_require_admin` sur GET/POST/DELETE + `_is_public_host` sur l'URL en création - A17 — router legacy `/api` sous `Depends(_require_session_or_bearer)` (session ou Bearer `/api/v1`), allowlist explicite `/api/health` + `/api/frontend-error` - A22 — les 2 uploads locales : session exigée (`_require_user_id`) + `validate_upload` branché (taille + extension) + `FLOWDECK_DATA_DIR` au lieu de `/data` codé en dur - A23 — N+1 : COUNT→`GROUP BY` (dashboard), cards→`executemany` (board sync), duplicata de propriétés→`executemany` + remap des ids par SELECT (collections) - A24 — 2 routes écrasées supprimées : `GET /api/projects` (api.py) et `GET /workspace` (workspace.py) + test « aucun doublon méthode+chemin » - Tests : +9 dans `tests/test_audit_p0_fixes.py` (SSRF, 401s, validate_upload, doublons de routes) ; tests OG sur hôtes résolubles (la garde fait du DNS) - suite **1025/1025** · `ruff check app tests` OK
This commit is contained in:
+22
-18
@@ -5,7 +5,7 @@ import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, Request
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from app.auth.session import SessionManager
|
||||
@@ -15,7 +15,27 @@ from app.routers.board import STATUS_COLORS, STATUS_LABELS, _issue_column, _map_
|
||||
from app.services.gitea_client import gitea
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(tags=["api"], prefix="/api")
|
||||
|
||||
# A17 : ce router legacy ne sert plus un anonymous. Deux exceptions assumées —
|
||||
# le probe d'infra (Docker HEALTHCHECK / CI) et la remontée d'erreur client.
|
||||
_API_PUBLIC_PATHS = {"/api/health", "/api/frontend-error"}
|
||||
|
||||
|
||||
async def _require_session_or_bearer(request: Request) -> None:
|
||||
"""Session de cookie **ou** Bearer d'API valide sur toutes les routes /api."""
|
||||
if request.url.path in _API_PUBLIC_PATHS:
|
||||
return
|
||||
if SessionManager.decode_session(request.cookies.get("flowdeck_session", "")):
|
||||
return
|
||||
auth = request.headers.get("Authorization", "")
|
||||
if auth.startswith("Bearer "):
|
||||
from app.routers.public_api import verify_token
|
||||
verify_token(auth)
|
||||
return
|
||||
raise HTTPException(401, "Authentication required")
|
||||
|
||||
|
||||
router = APIRouter(tags=["api"], prefix="/api", dependencies=[Depends(_require_session_or_bearer)])
|
||||
|
||||
# ── Simple rate limiter (in-memory, per-IP) ──
|
||||
_rate_limit_store: dict[str, tuple[float, int]] = {}
|
||||
@@ -79,22 +99,6 @@ async def stats():
|
||||
}
|
||||
|
||||
|
||||
@router.get("/projects")
|
||||
async def projects(search: str = Query(default=""), show_archived: bool = Query(default=False)):
|
||||
"""List Gitea projects (JSON)."""
|
||||
try:
|
||||
repos = await gitea.get_user_repos(page=1, limit=50)
|
||||
if search:
|
||||
q = search.lower()
|
||||
repos = [r for r in repos if q in r.get("full_name", "").lower() or q in (r.get("description") or "").lower()]
|
||||
if not show_archived:
|
||||
repos = [r for r in repos if not r.get("archived", False)]
|
||||
repos.sort(key=lambda r: r.get("updated_at", ""), reverse=True)
|
||||
except Exception:
|
||||
repos = []
|
||||
return {"projects": repos}
|
||||
|
||||
|
||||
@router.post("/move")
|
||||
async def move_card(
|
||||
request: Request,
|
||||
|
||||
Reference in New Issue
Block a user