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.
144 lines
7.0 KiB
Markdown
144 lines
7.0 KiB
Markdown
# 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)
|
|
|
|
```powershell
|
|
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 :
|
|
|
|
```bash
|
|
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 :
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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.
|