Files
Api-DataSentinel/main.py
Antho 4bc4770e2d fix(securite): supprime le secret JWT de repli présent dans le dépôt
JWT_SECRET retombait sur "data-sentinel-secret-change-in-prod", une valeur
lisible par quiconque a accès au code : un oubli de configuration suffisait
à permettre de forger un jeton d'administrateur.

Sans JWT_SECRET, une clé aléatoire est désormais tirée au démarrage, avec un
avertissement explicite. Le démarrage n'est volontairement pas bloqué : un
oubli de variable d'environnement ne doit pas transformer une configuration
incomplète en indisponibilité totale du service. Contrepartie assumée et
documentée : les sessions ne survivent pas à un redémarrage tant que la
variable n'est pas définie.

Vérifié : la production signe déjà avec un secret propre, ce correctif ne
change donc rien à son fonctionnement.

Ajoute par ailleurs APP_BUILD, injecté par la CI depuis le SHA du commit et
exposé par GET /health (et /version.json côté front). Jusqu'ici, rien ne
permettait de savoir quelle version tournait réellement : un déploiement
non appliqué était indiscernable d'un déploiement réussi.
2026-08-15 14:46:49 +02:00

123 lines
3.9 KiB
Python

# ============================================================
# main.py — API FastAPI Data Sentinel
# COYAUD Anthony | 2026
# V2 : une table dédiée par monitoring
#
# Ce module ne contient que l'assemblage de l'application :
# configuration, middlewares et montage des routeurs. Chaque
# domaine fonctionnel vit dans routers/.
#
# Lancement : uvicorn main:app --reload --port 8000
# Swagger : http://localhost:8000/docs
# ============================================================
import os
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from config import Config, get_cursor
from domain import MONITO_TABLES
from rate_limit import limiter
from routers import (
admin,
authentification,
dashboard,
evolution,
historique,
monitorings,
referentiels,
rgpd,
)
# Origines autorisées : depuis CORS_ORIGINS (séparées par des virgules) en prod,
# localhost par défaut en dev.
CORS_ORIGINS = [
o.strip()
for o in os.getenv("CORS_ORIGINS", "http://localhost:5173,http://localhost:3000").split(",")
if o.strip()
]
# ============================================================
# Initialisation
# ============================================================
app = FastAPI(
title = Config.API_TITLE,
version = Config.API_VERSION,
description = Config.API_DESCRIPTION,
)
# Limiteur de débit (anti brute-force sur /auth/login).
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(
CORSMiddleware,
allow_origins = CORS_ORIGINS,
allow_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers = ["*"],
allow_credentials = True,
)
# En-têtes de sécurité sur toutes les réponses.
@app.middleware("http")
async def security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
return response
# ============================================================
# SANTÉ — route publique
# ============================================================
@app.get("/health", tags=["Système"])
def health_check():
"""
Ping API + test connexion SQL Server.
`build` porte le SHA du commit dont l'image a été construite (injecté par
la CI). C'est le seul moyen fiable de vérifier qu'un déploiement a bien
pris : comparer ce champ au dernier commit poussé.
"""
try:
with get_cursor() as cursor:
cursor.execute("SELECT 1")
cursor.fetchone()
db_status = "ok"
except Exception as e:
db_status = f"error: {str(e)}"
return {
"api" : "ok",
"database" : db_status,
"version" : Config.API_VERSION,
"build" : Config.BUILD,
"nb_monitorings" : len(MONITO_TABLES),
}
# ============================================================
# Montage des routeurs
#
# Les routeurs de données déclarent eux-mêmes leur dépendance
# d'authentification. /health et /auth/login restent publics.
# ============================================================
app.include_router(authentification.router)
app.include_router(referentiels.router)
app.include_router(monitorings.router, prefix="/monitorings")
app.include_router(dashboard.router, prefix="/dashboard")
app.include_router(historique.router, prefix="/historique")
app.include_router(evolution.router, prefix="/evolution")
app.include_router(admin.router)
app.include_router(rgpd.router)