Files
Api-DataSentinel/README.md
T
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

7.0 KiB

Api-DataSentinel

API FastAPI de monitoring de la qualité des données — Data Sentinel (XEFI). Backend en lecture seule sur SQL Server, sécurisé par authentification JWT.

Stack

  • Python 3.12 · FastAPI 0.136 · uvicorn
  • SQL Server 2022 via pyodbc (ODBC Driver 18)
  • Auth : JWT (python-jose) + bcrypt (passlib), rate-limit (slowapi)

Lancement en local (dev)

python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python -m uvicorn main:app --reload --port 8000
  • Swagger : http://127.0.0.1:8000/docs · Santé : http://127.0.0.1:8000/health

Sans variables d'environnement, l'API se connecte en authentification Windows sur localhost. Pour une instance nommée, définir DB_SERVER (ex. DB_SERVER=MonPoste\SQLEXPRESS, sans DB_PORT).

Configuration (variables d'environnement)

Variable Rôle Défaut
DB_SERVER hôte SQL Server (ou hôte\instance) localhost
DB_PORT / DB_NAME port / base — laisser DB_PORT vide pour une instance nommée — / DataSentinel
DB_USER / DB_PASSWORD compte applicatif ; sa présence active l'authentification SQL — (sinon auth Windows)
DB_TRUSTED_CONNECTION force l'authentification Windows même si DB_USER est défini
DB_DRIVER pilote ODBC ODBC Driver 18 for SQL Server
CORS_ORIGINS origines autorisées (séparées par ,) localhost:5173,localhost:3000
JWT_SECRET clé de signature JWT — obligatoire en déploiement clé aléatoire régénérée à chaque démarrage
APP_BUILD SHA du commit construit, injecté par la CI et exposé par /health local
JWT_ALGORITHM / JWT_EXPIRE_MINUTES algo / durée du token HS256 / 60

Authentification & rôles

  • POST /auth/login (form username/password) → {access_token, token_type, user}. Rate-limité 5/min.
  • GET /auth/me → profil courant.
  • Tous les endpoints de données exigent Authorization: Bearer <token> (401 sinon). Seul /health est public.
  • Rôles : Admin, Superviseur, Consultant. Les routes /admin/* exigent Admin (403 sinon).

Comptes de démo

Identifiant Mot de passe Rôle Usage
Juré (JURE@NEXA.com) 123456 Admin Compte de visite / évaluation — à communiquer aux personnes qui consultent le site
admin Admin2026! Admin Compte d'administration technique
superviseur Super2026! Superviseur Illustration du RBAC (pas d'accès /admin/*)
consultant Conseil2026! Consultant Illustration du RBAC (lecture seule)

Ces comptes sont créés par sql/data_sentinel_auth.sql. Pour (re)créer le seul compte Juré sur une base déjà déployée, sans rejouer tout le script d'auth :

sqlcmd -S <serveur> -d DataSentinel -U <user> -P <mdp> -i sql/create_admin_jure.sql

Les mots de passe ci-dessus sont des identifiants de démonstration : ils sont stockés hachés (bcrypt, coût 12) et doivent être régénérés avant toute mise en production réelle (POST /admin/users/{id}/reset-password).

Organisation du code

main.py ne fait que l'assemblage (configuration, middlewares, montage des routeurs). Chaque domaine fonctionnel vit dans routers/ :

Module Responsabilité
routers/authentification.py /auth/login (rate-limité), /auth/me
routers/referentiels.py services, catégories, contacts — lecture + CRUD Admin
routers/monitorings.py nomenclature + données détaillées par table dédiée
routers/dashboard.py VUE_CONSO : vue consolidée, filtres, KPI
routers/historique.py snapshots journaliers (TABLE_FINAL)
routers/evolution.py VUE_TABLE_FINAL_CONSO : courbes global / monitoring / service
routers/admin.py comptes utilisateurs et journal d'audit
routers/rgpd.py portabilité et droit à l'oubli
domain.py enums (UserRole, AuditAction) et mapping MONITO_TABLES
helpers.py conversion des lignes pyodbc, écriture du journal d'audit

Endpoints (résumé)

  • Données (protégés) : /categories, /services, /contacts, /monitorings[...], /dashboard[...], /historique[...], /evolution/*.
  • Référentiels (Admin en écriture) : POST/PUT/DELETE sur /services, /categories, /contacts et /monitorings. La suppression est refusée (409) tant que des enregistrements y sont rattachés ; un monitoring est désactivé (actif = 0) et jamais supprimé, car TABLE_FINAL référence son identifiant.
  • Admin (Admin) : GET/POST /admin/users, PUT/DELETE /admin/users/{id}, POST /admin/users/{id}/reset-password, GET /admin/journal.
  • RGPD : GET /me/data-export (portabilité), DELETE /me (droit à l'oubli + anonymisation).

Spécification complète : GET /openapi.json (export dans docs/openapi.json).

Vérifier qu'un déploiement a pris

GET /health expose le SHA du commit dont l'image a été construite :

curl -s https://datasentinel-api.nfteam.ovh/health
# {"api":"ok","database":"ok","version":"1.0.0","build":"cb84e20e1f2a",...}

Comparer build au dernier commit poussé sur main. S'ils diffèrent, le conteneur tourne encore une ancienne image : docker compose pull puis docker compose up -d (un up -d seul ne retélécharge rien). Le front expose la même information sur /version.json.

Sécurité

  • JWT_SECRET doit être défini en déploiement. À défaut, l'API démarre quand même mais tire une clé aléatoire à chaque lancement (sessions perdues au redémarrage) : aucun secret de repli n'est écrit dans le dépôt, un secret public permettrait de forger un jeton d'administrateur.
  • En-têtes : X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Strict-Transport-Security.
  • CORS restreint aux origines CORS_ORIGINS, tous verbes + credentials.
  • Requêtes SQL paramétrées (noms de tables/colonnes whitelistés) ; le compte applicatif n'est pas sa. Tables d'auth : [USER] + JOURNAL_AUDIT (sql/data_sentinel_auth.sql, identique au dump livré dans RENDU/02_Dump_SQL/).
  • Journal d'audit alimenté à chaque login + action admin.

Tests

pip install pytest httpx && pytest -q

Curseur SQL simulé (aucune vraie BDD) : santé, auth (succès/échec), protection 401, RBAC 403, 404, validation des rôles par enum (422), refus de suppression d'un référentiel rattaché (409), désactivation logique d'un monitoring.

Docker & CI/CD

  • Dockerfile : image Python 3.12 + msodbcsql18.
  • CI Gitea Actions (.gitea/workflows/build.yml) : build → pytest (dans l'image) → push vers git.nfteam.ovh/neckfire/datasentinel-api + notification ntfy. Déclenché sur push main.
  • Déploiement, exploitation, sauvegarde : voir RUNBOOK.md et le stack d'hébergement (homelab/dev/datasentinel/). La base tourne sur un SQL Server partagé (dev-mssql), pas dédiée à l'app.