fix: corrige path traversal, fuites d'erreurs et dépréciations
- Empêche le path traversal du catch-all SPA (vérif is_relative_to dist)
- Migre on_event("startup") vers le handler lifespan de FastAPI
- Masque les exceptions internes des erreurs 500 (log au lieu de str(e))
- Remplace datetime.utcnow déprécié par un helper UTC timezone-aware
- Simplifie except (ValueError, Exception) redondant
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Application FastAPI pour l'extraction de comptes rendus de gérance."""
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -19,6 +20,13 @@ from .routes import (
|
||||
config_router,
|
||||
)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Initialize database on application startup."""
|
||||
init_db()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Plesna Gérance API",
|
||||
description="API pour extraire les informations structurées des PDFs de comptes rendus de gérance Oralia/ICS.",
|
||||
@@ -26,16 +34,10 @@ app = FastAPI(
|
||||
docs_url="/api/docs",
|
||||
redoc_url="/api/redoc",
|
||||
openapi_url="/api/openapi.json",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
# Initialize database on startup
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Initialize database on application startup."""
|
||||
init_db()
|
||||
|
||||
|
||||
# Include routers
|
||||
app.include_router(extraction_router)
|
||||
app.include_router(documents_router)
|
||||
@@ -74,12 +76,14 @@ if FRONTEND_DIST.exists():
|
||||
app.mount("/assets", StaticFiles(directory=FRONTEND_DIST / "assets"), name="assets")
|
||||
|
||||
# Catch-all route for SPA - must be last
|
||||
_dist_root = FRONTEND_DIST.resolve()
|
||||
|
||||
@app.get("/{full_path:path}", include_in_schema=False)
|
||||
async def serve_spa(full_path: str):
|
||||
"""Serve the SPA for all non-API routes."""
|
||||
# If requesting a file that exists, serve it
|
||||
file_path = FRONTEND_DIST / full_path
|
||||
if file_path.is_file():
|
||||
# If requesting a file that exists (and stays within dist), serve it
|
||||
file_path = (FRONTEND_DIST / full_path).resolve()
|
||||
if file_path.is_file() and file_path.is_relative_to(_dist_root):
|
||||
return FileResponse(file_path)
|
||||
# Otherwise serve index.html for SPA routing
|
||||
return FileResponse(FRONTEND_DIST / "index.html")
|
||||
return FileResponse(_dist_root / "index.html")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Documents routes - CRUD operations for documents."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, Form
|
||||
@@ -16,6 +17,8 @@ from ..schemas import SaveRequest, SaveResponse, DocumentSummary
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["documents"])
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.post("/save", response_model=SaveResponse)
|
||||
async def save_document(
|
||||
@@ -60,9 +63,10 @@ async def save_document(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.exception("Erreur lors de la sauvegarde du document")
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Erreur lors de la sauvegarde: {str(e)}"
|
||||
status_code=500, detail="Erreur lors de la sauvegarde du document."
|
||||
)
|
||||
|
||||
|
||||
@@ -134,9 +138,10 @@ async def save_document_with_pdf(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.exception("Erreur lors de la sauvegarde du document avec PDF")
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Erreur lors de la sauvegarde: {str(e)}"
|
||||
status_code=500, detail="Erreur lors de la sauvegarde du document."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -76,9 +76,11 @@ async def ask_ia(request: AskRequest) -> AskResponse:
|
||||
status_code=503,
|
||||
detail=f"Erreur Ollama : {e.response.status_code}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"IA ask error: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
except Exception:
|
||||
logger.exception("IA ask error")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Erreur interne lors du traitement de la question."
|
||||
)
|
||||
|
||||
|
||||
@router.get("/health", response_model=HealthResponse)
|
||||
|
||||
Reference in New Issue
Block a user