Files
Omika/docs/DEPLOIEMENT_PRODUCTION_DIAGNOSTICS.md
T
2026-10-23 18:46:50 +02:00

447 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Déploiement Production — Diagnostics multi-serveurs (T19/T20)
> Statut : préparation (aucun déploiement réel effectué). Document de référence pour le
> futur déploiement contrôlé du Diagnostic Center central et de l'API métier/mobile chez
> chaque client. Aucune valeur de secret (clé, mot de passe) n'est contenue ici.
---
## 1. Responsabilités des composants (§30)
| Composant | Rôle | Installation |
|---|---|---|
| **Omika.Api** | API métier/mobile installée **chez chaque client** (licence, bootstrap, métier, `POST /api/diagnostics`). Le mobile ne connaît QUE cette API. | Serveur du client |
| **Omika.Diagnostics.Api** | API **centrale**, installée **uniquement sur le serveur Omika**. Reçoit les lots des serveurs clients (`POST /api/v1/diagnostics/batches`), authentifie (ClientCode + ApiKey), impose le ClientCode authentifié, écrit le spool central. Aucune logique métier. | Serveur central Omika |
| **Omika.Diagnostics.Worker** | Import du spool central JSONL → PostgreSQL (`diagnostic_event`). Déduplication par `event_id`. | Serveur central Omika |
| **Omika.Diagnostics.Web** | Interface de consultation/administration (Dashboard, Diagnostics, Erreurs, Télémétrie, Référentiel, **Clients Diagnostics**, Système). | Serveur central Omika |
Le **mobile** ne connaît jamais `Omika.Diagnostics.Api/Worker/Web`, ni PostgreSQL Diagnostics, ni aucune
clé central. Le **serveur client** ne connaît jamais la ConnectionString PostgreSQL centrale
(§43) : seul le composant central autorisé détient cette configuration.
---
## 2. Architecture de flux
```
APPAREILS MOBILES ──HTTPS──▶ SERVEUR DU CLIENT
Omika.Api (+ Omika.Worker métier)
└─▶ Spool local borné (DiagnosticsForwarding.Spool)
└─▶ DiagnosticsForwarder (BackgroundService dans Omika.Api)
└─ HTTPS ──▶ SERVEUR OMIKA CENTRAL
Omika.Diagnostics.Api
└─▶ Spool central (Diagnostics:Central)
└─▶ Omika.Diagnostics.Worker
└─▶ PostgreSQL omika_diagnostics
└─▶ Omika.Diagnostics.Web
```
---
## 3. Services Windows cibles (§51)
### Serveur client
- `OmikaApi` (process Omika.Api ; contient le forwarder de diagnostics — **aucun service dédié**).
- `OmikaWorker` (Worker de synchronisation métier, service réel existant).
- API Licence / service existant (inchangé).
### Serveur central Omika
- `OmikaDiagnosticsApi` (API centrale, Kestrel).
- `OmikaDiagnosticsWorker` (import JSONL → PostgreSQL).
- `OmikaDiagnosticsWeb` (interface, Kestrel derrière reverse proxy Apache).
- PostgreSQL (`omika_diagnostics`).
Les noms de services sont posés par les scripts d'activation serveur (`sc.exe` / `New-Service`)
et doivent correspondre aux valeurs `UseWindowsService(...)` du code.
---
## 4. Ports (§52)
| Service | Port local Développement | Port Production (à confirmer selon convention existante) |
|---|---|---|
| Omika.Api (client) | 5097 | défini par `Kestrel:Endpoints` / `appsettings.Production.json` (loopback derrière Apache) |
| Omika.Diagnostics.Api (central) | 5098 | à définir (loopback Kestrel derrière Apache/HTTPS) |
| Omika.Diagnostics.Web (central) | 5080 | à définir (loopback Kestrel derrière Apache/HTTPS) |
| PostgreSQL | 5432 | 5432 (loopback central uniquement) |
**Règle** : les services web sont exposés en **HTTPS** via le reverse proxy Apache ; Kestrel reste
en HTTP sur loopback. Aucun port n'est choisi arbitrairement : la convention Apache/Kestrel
existante de `omika.applilogik.fr` s'applique.
---
## 5. HTTPS (§37/§38)
- Le flux **serveur client → API centrale** est **HTTPS uniquement** en Production.
- `DiagnosticsForwarding:Endpoint` doit être une URL **`https://…`**.
- **Règle T20** : si l'environnement est **Production** et que l'Endpoint est en **`http://`**,
le forwarder **refuse l'envoi** (log explicite une fois), le **métier continue** (fail-open),
le spool local est conservé. `Omika.Api` n'est **jamais** arrêté à cause de Diagnostics.
- Le certificat TLS réel n'est **pas** configuré dans ce document (à poser sur le reverse proxy).
---
## 6. Configuration Omika.Api (§31/§32/§33/§34/§35)
Section `DiagnosticsForwarding` (dans `src/Omika.Api/appsettings.json`, surchargée par variables
d'environnement / `appsettings.Production.json` **non versionné**) :
| Clé | Défaut | Rôle |
|---|---|---|
| `DiagnosticsForwarding:Enabled` | `false` | Kill switch. **`DiagnosticsForwarding__Enabled=true` uniquement lors du déploiement serveur client.** |
| `DiagnosticsForwarding:Endpoint` | `""` | URL centrale absolue **HTTPS** (ex. `https://…/api/v1/diagnostics/batches`). Laisser vide tant qu'aucune URL réelle. |
| `DiagnosticsForwarding:ApiKey` | `""` | **Secret** à installer sur le serveur client via `DiagnosticsForwarding__ApiKey`. Jamais dans Git/log/rapport. |
| `DiagnosticsForwarding:TimeoutSeconds` | `10` | Timeout réseau d'un envoi (le forwarder est background : jamais de blocage métier). |
| `DiagnosticsForwarding:BatchSize` | `100` | Événements max par lot. |
| `DiagnosticsForwarding:MaxBatchBytes` | `524288` | Taille max d'un lot (premier seuil). |
| `DiagnosticsForwarding:RetryInitialSeconds` | `15` | Backoff initial. |
| `DiagnosticsForwarding:RetryMaxMinutes` | `30` | Plafond du backoff. |
| `DiagnosticsForwarding:Spool:MaxFileSizeMb` | `10` | Rotation de fichier. |
| `DiagnosticsForwarding:Spool:MaxTotalSizeMb` | `1024` | Quota disque total. |
| `DiagnosticsForwarding:Spool:RetentionDays` | `14` | Rétention. |
Le défaut `Enabled=false` est **volontaire** (sécurité, opt-in). Aucun placeholder fictif
(`https://diagnostics.exemple.fr`) ne doit devenir une valeur réelle accidentellement.
---
## 7. Configuration Omika.Diagnostics.Api (§36)
Section `Diagnostics` (dans `src/Omika.Diagnostics.Api/appsettings.json`, surchargée par variables) :
| Clé | Défaut | Rôle |
|---|---|---|
| `ConnectionStrings:DiagnosticsDatabase` (ou `Diagnostics:ConnectionString`) | `""` | PostgreSQL central (secret, injecté serveur). |
| `Diagnostics:Central:Directory` | `logs/diagnostics-central` | **Spool central** — en Production, chemin explicite (voir §9). |
| `Diagnostics:Central:MaxRequestBodyBytes` | `524288` | Corps de requête borné. |
| `Diagnostics:Central:MaxEventsPerBatch` | `100` | Événements max par lot. |
| `Diagnostics:Central:SchemaVersion` | `1` | Version de schéma de transport. |
URL/port : via `ASPNETCORE_URLS` (ou `Kestrel:Endpoints`) derrière le reverse proxy HTTPS.
---
## 8. Spool client — emplacement Production (§40)
Le service Windows peut démarrer depuis `C:\Windows\System32` : **ne jamais dépendre du current
working directory**. Utiliser un chemin explicite configurable :
```
C:\ProgramData\Omika\Diagnostics\Spool
```
(via `Observability:Remote:Directory` / `DiagnosticsForwarding` — à poser dans la configuration
serveur client). Le quota (`MaxTotalSizeMb`) et la rétention (`RetentionDays`) évitent un
remplissage disque : à quota atteint, les fichiers les plus anciens sont supprimés (log du
nombre), jamais le fichier actif, jamais d'échec métier.
---
## 9. Spool central — emplacement Production (§41)
Même réflexion pour `Omika.Diagnostics.Api` :
```
C:\ProgramData\Omika\Diagnostics\Central
```
(via `Diagnostics:Central:Directory`). Le Worker central lit ce dossier (import JSONL → PostgreSQL,
checkpoint `import-checkpoint.json`).
---
## 10. Permissions Windows (§42)
Principe de moindre privilège (jamais Administrateur inutilement) :
| Compte service | Dossier | Droits |
|---|---|---|
| `OmikaApi` | `C:\ProgramData\Omika\Diagnostics\Spool` | lecture + écriture |
| `OmikaDiagnosticsApi` | `C:\ProgramData\Omika\Diagnostics\Central` | lecture + écriture |
| `OmikaDiagnosticsWorker` | `C:\ProgramData\Omika\Diagnostics\Central` | lecture (checkpoint → écriture du checkpoint selon le dossier) |
---
## 11. PostgreSQL (§43/§54)
- Base dédiée `omika_diagnostics`. PostgreSQL **ne doit jamais être exposé** aux serveurs clients :
seuls les composants centraux autorisés y accèdent (firewall : loopback central uniquement).
- Rôles : voir migrations `003`/`004`/`009`/`010` (`omika_diagnostics_owner`, `_worker`, `_web`, `_api`).
- `omika_diagnostics_api` (LOGIN) : **Omika.Diagnostics.Api** — `SELECT` + `UPDATE(last_seen_at_utc)` sur `diagnostics_clients` uniquement. **Aucun** droit sur `diagnostics_users`.
- `omika_diagnostics_web` (LOGIN) : **Omika.Diagnostics.Web** — `SELECT` sur `diagnostic_event` ; `SELECT`/`INSERT`/`UPDATE` sur `diagnostics_clients` (admin `/Clients`) ; `SELECT`/`INSERT`/`UPDATE` (colonnes `password_hash`, `role`, `enabled`, `updated_at_utc`, `password_changed_at_utc`, `last_login_at_utc`) + `USAGE` séquence sur `diagnostics_users` (admin `/Users` + login).
- `omika_diagnostics_worker` (LOGIN) : **Omika.Diagnostics.Worker** — `INSERT`/`SELECT`/`DELETE` sur `diagnostic_event` ; **aucun** droit sur `diagnostics_clients` ni `diagnostics_users`.
- Le serveur client **NE DOIT PAS** connaître la ConnectionString PostgreSQL centrale.
---
## 12. Migrations — inventaire (§44/§45/§46)
Ordre d'application sur le serveur central : `001 → 002 → 003 → 004 → 005 → 006 → 007 → 008 → 009 → 010`.
| Fichier | But | Développement appliqué ? | Production nécessaire ? |
|---|---|---|---|
| `001_diagnostics_schema.sql` | `diagnostic_event` + `telemetry_metric_hourly` + index + déduplication `event_id`. | Oui | Oui |
| `002_actor_key.sql` | Colonnes `actor_key`/`actor_key_version` (HMAC pseudonymisation). | Oui | Oui |
| `003_diagnostics_roles.sql` | Rôles/grants serveur (`owner`/`worker`/`web`), moindre privilège. | **Non** (à exécuter sur le serveur PostgreSQL Production, en superuser) | Oui |
| `004_diagnostics_retention.sql` | Grant `SELECT, DELETE` au worker pour la rétention bornée. | **Non** (idem, serveur Production) | Oui |
| `005_diagnostics_client_context.sql` | Colonnes `client_code`/`server_instance`/`environment`. | Oui | Oui |
| `006_diagnostics_device_license.sql` | Colonnes appareil/licence. | Oui | Oui |
| `007_diagnostics_usage.sql` | Colonnes `screen_code`/`feature_code`. | Oui | Oui |
| `008_diagnostics_clients.sql` | Table `diagnostics_clients` (registre des serveurs clients, hash SHA-256). | Oui | Oui |
| `009_diagnostics_clients_grants.sql` | Ownership + GRANT moindre privilège de `diagnostics_clients` (`_api`/`_web`), rôle `omika_diagnostics_api`. | **Non** (serveur PostgreSQL Production, superuser) | Oui |
| `010_diagnostics_web_users.sql` | Table `diagnostics_users` (utilisateurs Web) + ownership `omika_diagnostics_owner` + GRANT moindre privilège `_web` uniquement (aucun droit API/Worker). | **Non** (serveur PostgreSQL Production, superuser) | Oui |
**003/004/009/010** : contiennent des `CREATE ROLE` / `ALTER ROLE` / `GRANT` au niveau **serveur/instance**.
Elles ne sont **pas** appliquées sur le PC de développement (elles exigent le superuser PostgreSQL
Production et fixent les comptes de service). Elles devront être exécutées **sur le serveur
PostgreSQL Production**, dans l'ordre, **avant** le pointage des comptes de service
Worker/Web/Api (`omika_diagnostics_worker` / `omika_diagnostics_web` / `omika_diagnostics_api`),
puis les mots de passe posés manuellement (`\password`, jamais dans un script/log).
Ne pas les exécuter localement « pour du vert ».
> **T22** : `008` crée `diagnostics_clients` **après** `003`/`004`, donc sans ownership ni GRANT
> applicatif. `009` transfère la propriété à `omika_diagnostics_owner` (même propriétaire logique
> que `diagnostic_event`/`telemetry_metric_hourly`), crée le rôle `omika_diagnostics_api` (absent du
> modèle `003`), et pose les GRANT minimum identifiés par l'audit des consommateurs réels :
> API = `SELECT` + `UPDATE(last_seen_at_utc)` ; Web = `SELECT`/`INSERT`/`UPDATE(enabled, api_key_hash,
> key_prefix, key_created_at_utc)` + `USAGE` séquence ; Worker = aucun. `009` est idempotent
> (re-exécutable sans erreur) et utilise `current_database()` pour `GRANT CONNECT` (portable,
> testé sur `omika_diagnostics_preprod`).
---
## 13. Authentification ApiKey — audit final (§55/§56)
- Clé : **32 octets aléatoires**, **Base64Url**, préfixe **`omkdiag_`**.
- Stockage : **SHA-256 hexadécimal (64)** uniquement (`diagnostics_clients.api_key_hash`) + `key_prefix`.
- Vérification : `CryptographicOperations.FixedTimeEquals` ; hash factice si client inconnu (anti-timing).
- **401** : client inconnu OU mauvaise clé (indistinguables). **403** : client désactivé.
- **Tenant autoritaire** : tout ClientCode payload ≠ authentifié → **400** + anomalie (jamais de confiance au payload).
- Réponses sans fuite : jamais de hash, de clé, de stacktrace, ni l'existence précise d'un client.
---
## 14. Procédure — nouveau client Production (§73)
1. `Omika.Diagnostics.Web` → **Clients Diagnostics** (Admin).
2. **Nouveau client**.
3. Saisir le **CodeClient exact** (ex. `BATIK.01`, normalisé `UPPER(TRIM)`).
4. **Créer le client** → la clé est générée.
5. **Copier immédiatement** la clé (bouton « Copier »).
6. Installer la clé sur le serveur client : variable `DiagnosticsForwarding__ApiKey`.
7. Configurer `DiagnosticsForwarding__Endpoint` (URL centrale **HTTPS**).
8. Activer : `DiagnosticsForwarding__Enabled=true`.
9. Redémarrer `OmikaApi`.
10. Vérifier `last_seen_at_utc` dans Clients Diagnostics.
11. Vérifier un diagnostic réel (Dashboard / Diagnostics).
---
## 15. Clé perdue (§74)
La clé **n'est pas récupérable** (hash seul stocké). Si elle n'a pas été copiée :
faire **Régénérer la clé**, puis mettre à jour le serveur client avec la nouvelle clé.
---
## 16. Rotation de clé (§75)
Version V1 : **régénération → ancienne clé immédiatement invalide**. Pendant l'intervalle où le
serveur client n'a pas encore la nouvelle clé, les envois échouent en `401` et le **spool local
s'accumule** (backlog borné, reprise automatique). Acceptable.
---
## 17. Panne centrale (§76)
- **Ne pas arrêter** `OmikaApi` (le métier continue).
- **Ne pas vider** le spool manuellement (les événements seront renvoyés).
- Vérifier l'espace disque (quota/rétention bornent le spool).
- Vérifier les logs du forwarder.
- Attendre le rétablissement : reprise automatique (backoff borné 15 s → 30 min).
---
## 18. Disque spool (§77)
- `MaxTotalSizeMb` (défaut 1024) : quota disque total ; à dépassement, suppression des fichiers
les plus anciens (jamais le fichier actif, log du nombre supprimé).
- `RetentionDays` (défaut 14) : purge des fichiers plus anciens.
- `MaxFileSizeMb` (défaut 10) : rotation de fichier.
- **Fail-open absolu** : aucune de ces opérations ne bloque le métier.
> **Validation empirique T22** (pipeline réel, panne centrale simulée) :
> - Quota `MaxTotalSizeMb=1` : 6000 événements fake (~3,6 Mo) → `DiagnosticsForwarder a abandonné
> 14 fichier(s) de spool`, spool final 768 Ko (≤ 1 Mo), fichier actif préservé, `/health` 200
> (aucun crash métier), Working Set stable ~115–125 Mo (backlog sur disque ≠ RAM).
> - Rétention `RetentionDays=1` : fichier ancien (-2 j) supprimé, fichier récent + fichier actif
> conservés, central DOWN pendant tout le test (la maintenance ne dépend pas du central).
> - **Correctif T22** : `EnforceBounds` (quota/rétention) était appelé uniquement quand le spool
> était vide ; pendant une panne centrale (backlog jamais vide), le quota n'était donc pas
> appliqué. Il est désormais appelé à chaque passe du forwarder, indépendamment du backlog.
> Test : `ForwardOnce_BacklogOverQuota_StillEnforcesBounds`.
---
## 19. Ordre de démarrage (reprise/rollback)
**Démarrage central** : PostgreSQL → `OmikaDiagnosticsWorker` → `OmikaDiagnosticsApi` →
`OmikaDiagnosticsWeb`.
**Démarrage client** : `OmikaWorker` (métier) → `OmikaApi` (métier + forwarder).
**Rollback** : `DiagnosticsForwarding__Enabled=false` + redémarrage `OmikaApi` coupe immédiatement
tout envoi central sans toucher au métier ni au spool (aucune perte de données).
---
## 20. Dépendance SQLite (mobile / Desktop local)
> Mise à jour **T22** — audit + correctif CVE.
- **Version retenue** : `Microsoft.Data.Sqlite 10.0.10` (inchangé) + **`SQLitePCLRaw` 2.1.12**
épinglé transitivement (`Directory.Packages.props`, `CentralPackageTransitivePinningEnabled=true`).
- **Raison** : `SQLitePCLRaw.lib.e_sqlite3 2.1.11` (transitif de `Microsoft.Data.Sqlite 10.0.10`)
embarque une bibliothèque SQLite native < 3.50.2, vulnérable à **CVE-2025-6965 /
GHSA-2m69-gcr7-jv3q** (troncature d'entier dans `findOrCreateAggInfoColumn`, `resolve.c`).
Correctif = SQLite native **3.50.2**, livré par le patch **2.1.12** (famille 2.x, drop-in,
ABI-compatible). **Pas** de saut vers SQLitePCLRaw 3.x (breaking) ni vers
`Microsoft.Data.Sqlite 11` (majeur).
- **Projets concernés** : `Omika.App.Local` (offline-first : cache métier, brouillons, sync,
`DiagnosticOutboxSqliteStore`) → `Omika.App` → têtes `Omika.App.Desktop` / `Omika.App.Android` /
`Omika.App.iOS`. Aucun impact sur `Omika.Api`, `Omika.Worker` ni les projets Diagnostics.
- **Validation** :
- `dotnet restore` OK ; `dotnet list package --include-transitive --vulnerable` →
**« aucun package vulnérable »** ; arbre confirmé `SQLitePCLRaw.* = 2.1.12`.
- Tests SQLite/Outbox ciblés : **148/148**.
- Builds Release : `Omika.App.Desktop` 0 erreur, `Omika.App.Android` 0 erreur.
- `Omika.App.Desktop` (Debug, flux dev) démarré réellement : ouverture `omika.db` OK +
bootstrap `DEV BOOTSTRAP` OK (aucun crash SQLite).
> **Surface réellement exposée** : la vulnérabilité nécessite une requête SQL construite avec un
> nombre extrême de termes d'agrégation ; Omika n'exécute que des requêtes applicatives fixes et
> paramétrées (jamais de SQL utilisateur), sur une base **locale** non accessible à distance. Le
> correctif 2.1.12 est néanmoins appliqué (moindre coût, aucun risque de régression constaté).
---
## 21. Gestion des utilisateurs Diagnostics.Web (T23)
### 21.1 Distinction fondamentale
| Concept | Définition | Identifiant | Stockage |
|---|---|---|---|
| **Utilisateur Diagnostics** | personne qui ouvre `Omika.Diagnostics.Web` | `username` | `diagnostics_users` (hash PBKDF2-SHA256) |
| **Client Diagnostics** | serveur client qui envoie des événements | `ClientCode` | `diagnostics_clients` (hash SHA-256) |
Les deux registres sont **totalement séparés** et ne doivent jamais être confondus.
### 21.2 Modèle utilisateur
- Table `diagnostics_users` (migration `010`) : `id`, `username` (unique, insensible à la casse),
`password_hash`, `role` (`Support` / `Admin`), `enabled`, `created_at_utc`, `updated_at_utc`,
`password_changed_at_utc`, `last_login_at_utc`.
- **Aucun mot de passe en clair** : seule la colonne `password_hash` existe. Le hash est produit
par `DiagnosticPasswordHasher` (PBKDF2-SHA256, 100 000 itérations, sel aléatoire).
- **Aucune suppression physique** (V1) : un utilisateur est **désactivé** (`enabled=false`), jamais `DELETE`.
### 21.3 Rôles
| Rôle | Accès |
|---|---|
| **Support** | Dashboard, Diagnostics, Erreurs, Traces, Synchronisation, Telemetry, Parc, Versions, Référentiel, Mon compte. |
| **Admin** | Tout Support + **Clients Diagnostics** + **Utilisateurs** + **Système**. |
`/System`, `/Clients` et `/Users` sont **Admin uniquement**. Un Support y obtient un refus
(redirection `/AccessDenied`). Un Admin conserve tous les droits de consultation Support.
### 21.4 Actions d'administration (`/Users`, Admin uniquement)
- **Création** : saisie `username` + `password` + `confirmation` + `rôle`. Le Web hache
automatiquement (`DiagnosticPasswordHasher.Hash`) ; l'Admin ne génère/copie **jamais** un hash.
- **Désactivation / Réactivation** (`enabled=false/true`).
- **Changement de rôle** `Support → Admin` / `Admin → Support`.
- **Réinitialisation du mot de passe** (nouveau mot de passe + confirmation → hash + UPDATE).
### 21.5 Protections
- **Dernier Admin** : impossible de désactiver ou rétrograder le dernier Admin actif
(« Impossible de retirer le dernier administrateur actif. »).
- **Auto-désactivation** : un Admin ne peut pas désactiver son propre compte.
- **Auto-rétrogradation** : un Admin ne peut pas retirer son propre rôle Admin.
### 21.6 Règles de validation
- **Username** : obligatoire, `trim`, unicité insensible à la casse, borné (≤ 64), caractères
`lettres / chiffres / . / - / _`.
- **Mot de passe** : non vide, **≥ 12 caractères**, confirmation identique. Pas de politique de
complexité superflue (V1).
### 21.7 Connexion (login hybride)
1. Si le `username` existe dans `diagnostics_users` → la DB est **autoritaire** (jamais de fallback).
- Vérification PBKDF2 (`DiagnosticPasswordHasher.Verify`) ; `enabled=false` → refus.
2. Sinon → fallback opérateurs `DiagnosticsAuth:Operators` (bootstrap temporaire).
3. Message d'échec **générique** : « Identifiants invalides. » (jamais « inconnu » / « désactivé » / « mauvais mot de passe »).
- `last_login_at_utc` est mis à jour (best-effort) après un login DB réussi.
- Anti-bruteforce léger (in-memory, mono-instance) : 5 échecs / 5 min → blocage 10 min
(clé = username + IP). Aucun Redis, aucune session distribuée.
- Anti-timing : hash factice calculé quand le username est inconnu (même principe que l'ApiKey).
- Cookie : `HttpOnly`, `SameSite=Lax`, `Secure=Always` hors Développement, expiration 12 h
(glissante). Production = HTTPS.
- Un utilisateur désactivé avec un cookie existant est re-déconnecté à la prochaine
re-vérification DB (fréquence bornée ~60 s, cache mémoire mono-instance) ; au pire ~1 minute
de grâce, jamais « plusieurs jours ».
### 21.8 « Mon compte » (`/Account`)
- Tout utilisateur authentifié peut changer **son propre** mot de passe :
`mot de passe actuel` (vérifié réellement) + `nouveau` + `confirmation`. En cas de mot de passe
actuel faux → refus **sans** modifier le hash.
- Les opérateurs bootstrap (configuration) ne peuvent pas changer leur mot de passe ici.
---
## 22. Bootstrap du premier Admin Production (T23)
Production démarre **sans** utilisateur DB. Conserver la possibilité de configurer
temporairement un opérateur bootstrap (`DiagnosticsAuth:Operators`) pour créer le premier Admin DB.
### 22.1 Générer le hash bootstrap (hors ligne, pas d'endpoint Web)
Outil console dédié (jamais un endpoint Web anonyme) :
```
dotnet run --project tools/Omika.DiagnosticHashTool -- "<mot-de-passe-bootstrap>"
# ou, pour éviter de laisser le mot de passe dans l'historique du shell :
dotnet run --project tools/Omika.DiagnosticHashTool -- --stdin
```
> ⚠️ Le **mot de passe** bootstrap ne doit **jamais** être dans Git, dans le rapport, ni dans un
> `appsettings` versionné. Seul le **hash** est placé dans la configuration / le secret store
> Production.
### 22.2 Procédure premier déploiement (12 étapes)
1. Déployer `Omika.Diagnostics.Web` (services + reverse proxy HTTPS, cf. §3-§5).
2. Configurer **temporairement** l'opérateur bootstrap Admin (hash seulement) :
`DiagnosticsAuth__Operators__0__Username` / `...__PasswordHash` / `...__Roles__0=Admin`.
3. Démarrer le service `OmikaDiagnosticsWeb`.
4. Se connecter avec l'opérateur bootstrap.
5. Aller sur **`/Users`**.
6. Créer l'**Admin DB permanent** (username + mot de passe + rôle `Admin`).
7. Se **déconnecter**.
8. Tester le login avec l'**Admin DB** (plus le bootstrap).
9. **Retirer** l'opérateur bootstrap de la configuration (`DiagnosticsAuth:Operators = []`).
10. Redémarrer le service `OmikaDiagnosticsWeb`.
11. Confirmer que le **bootstrap est refusé** (login → « Identifiants invalides. »).
12. Confirmer que l'**Admin DB fonctionne** (Dashboard / Diagnostics / Clients / Users).
### 22.3 Installation Production neuve
Si PostgreSQL Diagnostics Production est créé **de zéro** : appliquer **`001 → 010`** dans l'ordre
(jamais uniquement les dernières). Si une installation existe déjà : appliquer **uniquement les
migrations manquantes**, dans l'ordre.