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

14 KiB

T23 — GESTION UTILISATEURS DIAGNOSTICS.WEB

Statut : T23 VALIDÉ — AUTHENTIFICATION DIAGNOSTICS.WEB PRÊTE POUR PRODUCTION Aucun Git. Aucune Production. Identités de recette : admin-test / support-test (jetables, préprod).


1. Résumé exécutif

T23 ajoute la gestion complète des utilisateurs de Omika.Diagnostics.Web (personnes qui ouvrent l'interface), distincte des clients Diagnostics (serveurs, ClientCode+ApiKey). Authentification hybride : utilisateurs PostgreSQL diagnostics_users en priorité, sinon opérateurs bootstrap DiagnosticsAuth:Operators. Hash PBKDF2-SHA256 réutilisé (DiagnosticPasswordHasher), jamais de mot de passe en clair, page /Users (Admin uniquement), page /Account (Mon compte), protections (dernier Admin, auto-*, brute-force, timing, cookie, CSRF). Recette live 33/33 PASS, migration 010 idempotente, permissions négatives confirmées, Diagnostics.Tests 536/536, Omika.Tests 3367/3370 (identique baseline), builds Release 0 erreur.


2. Authentification avant T23

  • Cookie auth ASP.NET Core (AddCookie) ; opérateurs injectés via DiagnosticsAuth:Operators (nom + PasswordHash PBKDF2 + rôles), aucune identité DB.
  • DiagnosticPasswordHasher (PBKDF2-SHA256, 100 000 itérations, sel 32 o, clé 32 o, comparaison temps constant) et DiagnosticOperatorAuthenticator (échec générique).
  • Rôles Support/Admin + policies DiagnosticsSupport (Support OU Admin) / DiagnosticsAdmin (Admin seul). Conventions : dossier / → Support ; /System, /Clients → Admin.
  • Connexion dev sans mot de passe (Development uniquement) ; opérateur admin-local en Development.

3. Architecture finale authentification

Login (POST /Login)
  → LoginThrottle (5 échecs/5 min → blocage 10 min, clé username+IP)
  → DiagnosticUserAuthenticator.AuthenticateAsync
       1. DB : GetByUsernameAsync (insensible casse) → Verify PBKDF2 + enabled
          (DB autoritaire, jamais de fallback si username DB existe)
       2. sinon opérateurs DiagnosticsAuth:Operators (bootstrap)
       3. sinon hash factice (anti-timing) → échec générique
  → succès DB : SignInAsync (claims Name + Role + user_source=db) + UpdateLastLoginAsync (best-effort)
Cookie : HttpOnly / SameSite=Lax / Secure=Always (hors Dev) / 12 h glissante
  → OnValidatePrincipal : si user_source=db, re-vérifie enabled (~60 s de cache mémoire)

4. Migration 010

infra/postgresql/010_diagnostics_web_users.sql — idempotente (CREATE TABLE IF NOT EXISTS + CREATE UNIQUE INDEX IF NOT EXISTS + GRANT), non destructive, appliquée deux fois : 0 erreur.

5. Table diagnostics_users

id BIGSERIAL PK, username TEXT NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL, enabled BOOLEAN DEFAULT TRUE, created_at_utc, updated_at_utc, password_changed_at_utc, last_login_at_utc (TIMESTAMPTZ). Unicité insensible à la casse : index LOWER(username). Aucune colonne password/plain_password/clear_password.

6. Ownership / GRANT PostgreSQL

  • Owner : omika_diagnostics_owner (table + séquence).
  • REVOKE ALL FROM PUBLIC.
  • omika_diagnostics_web : SELECT, INSERT, UPDATE(password_hash, role, enabled, updated_at_utc, password_changed_at_utc, last_login_at_utc), USAGE séquence.
  • omika_diagnostics_api et omika_diagnostics_worker : aucun droit.

7. Permissions négatives (testé réellement sur omika_diagnostics_preprod)

Rôle SELECT diagnostics_users résultat
omika_diagnostics_api refusé (droit refusé) ✅
omika_diagnostics_worker refusé (droit refusé) ✅
omika_diagnostics_web OK (SELECT/INSERT/UPDATE) ✅
omika_diagnostics_web DELETE refusé (pas de DELETE) ✅

8. DiagnosticPasswordHasher

Réutilisé tel quel (aucun nouvel algorithme). Ajout de DummyHash (hash factice, format valide) pour l'égalisation de temps lors d'un username inconnu. Les 4 tests existants restent verts.

9. Login hybride

DiagnosticUserAuthenticator.AuthenticateAsync : DB prioritaire (§36), fallback opérateurs, échec générique « Identifiants invalides. » (jamais « inconnu »/« désactivé »/« mauvais mot de passe »). Testé : DB correct ✅, DB faux ✅, DB désactivé ✅, opérateur fallback ✅, DB autoritaire (DB désactivé + opérateur même nom → refus) ✅, inconnu ✅.

10. Bootstrap config

DiagnosticsAuth:Operators conservé (fail-closed si vide). Outil hors-ligne tools/Omika.DiagnosticHashTool (<password> ou --stdin) génère le hash PBKDF2. Aucun endpoint Web anonyme de génération de hash.

11. Login utilisateur DB

Verify(password, password_hash) puis contrôle enabled ; last_login_at_utc mis à jour (best-effort) après succès. Vérifié en DB : last_login_at_utc renseigné après login.

12. Page Utilisateurs

/Users (« Utilisateurs ») Admin uniquement, dans la zone Administration à côté de « Clients Diagnostics ».

13. Création

Formulaire username / password / confirmation / rôle. Le Web hache automatiquement (DiagnosticPasswordHasher.Hash) puis stocke uniquement password_hash. L'Admin ne génère/copie jamais de hash.

14. Validation username

Obligatoire, trim, unicité insensible à la casse, ≤ 64, lettres/chiffres/./-/_.

15. Validation password

Non vide, ≥ 12 caractères, confirmation identique. Pas de politique de complexité superflue.

16. Support

Dashboard, Diagnostics, Erreurs, Traces, Synchronisation, Telemetry, Parc, Versions, Référentiel, Mon compte. (Audité : inchangé, IsSupport = Support OU Admin.)

17. Admin

Tout Support + Clients Diagnostics + Utilisateurs + Système.

18. Matrice autorisations (testée avec deux comptes distincts)

Page Support Admin
Dashboard ✅ ✅
Diagnostics ✅ ✅
Erreurs ✅ ✅
Traces ✅ ✅
Synchronisation ✅ ✅
Telemetry ✅ ✅
Parc ✅ ✅
Versions ✅ ✅
Référentiel ✅ ✅
System ❌ ✅
Clients Diagnostics ❌ ✅
Utilisateurs ❌ ✅

19. System

Audité : /System affiche l'état opérationnel (PostgreSQL, checkpoint d'import, info API). Décision : Admin uniquement (cohérent avec l'information opérationnelle affichée). Inchangé.

20. Activation/désactivation

enabled=true/false (pas de DELETE en V1). Bouton « Désactiver »/« Réactiver ».

21. Changement rôle

Support → Admin / Admin → Support (select + « Changer rôle »).

22. Dernier Admin

Impossible de désactiver/rétrograder le dernier Admin actif (« Impossible de retirer le dernier administrateur actif. »). Testé réellement.

23. Auto-désactivation / rétrogradation

Un Admin ne peut ni désactiver son propre compte, ni retirer son propre rôle Admin. Testé.

24. Reset mot de passe

Action « Réinitialiser le mot de passe » (nouveau + confirmation) → Hash + UPDATE password_hash + password_changed_at_utc + updated_at_utc.

25. Mon compte

Page /Account, accessible à tout utilisateur authentifié (Support + Admin).

26. Changement mot de passe

Mot de passe actuel (vérifié réellement via Verify), nouveau, confirmation → Hash(nouveau)

  • UPDATE. Pour un opérateur bootstrap : non modifiable (notice).

27. Session utilisateur désactivé

OnValidatePrincipal re-vérifie enabled pour les utilisateurs DB, à fréquence bornée (~60 s, cache mémoire mono-instance). Testé : cookie existant rejeté (302 → /Login) à la re-vérification. Documenté : au pire ~1 minute de grâce, jamais « plusieurs jours ».

28. Last login

last_login_at_utc mis à jour après login DB réussi (best-effort). Vérifié en DB.

29. Mauvais login

Message générique « Identifiants invalides. » pour username inconnu / mauvais password / désactivé. Aucune fuite « existe » / « désactivé ». Testé.

30. Brute force

LoginThrottle (in-memory, mono-instance) : 5 échecs / 5 min → blocage 10 min (clé username+IP). Pas de Redis. Unit-testé.

31. Timing login

Hash factice (DiagnosticPasswordHasher.DummyHash) quand username inconnu → même coût PBKDF2 que pour un vrai hash (même principe ApiKey). Résiduel documenté : entrées vides ne paient pas le hash.

HttpOnly=true, SameSite=Lax, SecurePolicy=Always hors Développement (Production HTTPS), ExpireTimeSpan=12h, SlidingExpiration=true.

33. CSRF

Toutes les actions sensibles (Create/Disable/Enable/ChangeRole/ResetPassword/ChangeOwnPassword/ Login/Logout) utilisent l'antiforgery Razor Pages (FormTagHelper injecte le token ; validation automatique des handlers POST).

34. Sécurité HTML

Scan du HTML rendu /Users : 0 password_hash, 0 hash PBKDF2 complet, 0 secret. Les <input type=password> ne sont jamais préremplis. Testé.

35. Sécurité PostgreSQL

Scan diagnostics_users : colonnes attendues uniquement, 0 colonne password/plain_password/ clear_password, tous les password_hash commencent par 100000. (PBKDF2). Testé.

36. Tests admin-test

login OK ; Dashboard OK ; Diagnostics OK ; Clients OK ; Users OK.

37. Tests support-test

login OK ; Dashboard OK ; Diagnostics OK ; Telemetry OK ; Parc OK ; Clients → refus (302 AccessDenied) ; Users → refus (302 AccessDenied).

38. Tests automatisés

+73 tests (5 fichiers) : validateurs/guard, authentificateur hybride, throttle, PageModels (Users + Account), matrice autorisations, hash factice, FakeDiagnosticUserRepository.

39. Diagnostics.Tests

536/536 (baseline T22 : 463/463). Tous les anciens tests passent.

40. Omika.Tests

3367/3370 en Debug — identique à la baseline T22 (« 3 échecs connus »). Aucune nouvelle régression. Les 3 échecs Debug sont : 2 Avalonia headless (InterventionsViewConstructionTests)

  • 1 HFSQL/OLE DB (ResolveAsync_Batik_...) nécessitant un serveur HFSQL réel, tous hors périmètre T23. (Note : en Release, 5 DevelopmentLicenseTests échouent en plus car le bypass dev est compilé hors #if DEBUG — comportement attendu, pas une régression.)

41. Builds

Omika.Diagnostics.Core, .Data, .Web, .Api, .Worker + Omika.Api en Release : 0 erreur.

42. Documentation

docs/DEPLOIEMENT_PRODUCTION_DIAGNOSTICS.md : ajout §21 (gestion utilisateurs), §22 (bootstrap premier Admin + procédure 12 étapes), mise à jour §11 (rôles) et §12 (inventaire 001→010).

43. Procédure bootstrap Production

12 étapes documentées (§22.2) : déployer → opérateur bootstrap temporaire (hash seul) → démarrer → se connecter → /Users → créer Admin DB → se déconnecter → tester Admin DB → retirer bootstrap → redémarrer → confirmer bootstrap refusé → confirmer Admin DB fonctionnel.

44. Ordre migrations 001→010

001 → 002 → 003 → 004 → 005 → 006 → 007 → 008 → 009 → 010. Installation neuve : appliquer toutes dans l'ordre ; installation existante : uniquement les manquantes, dans l'ordre.

45. Nettoyage scratch

_v/ (outil scratch SQLite, non référencé par aucun projet/solution) supprimé proprement après vérification (0 référence).

46. Fichiers créés

  • infra/postgresql/010_diagnostics_web_users.sql
  • src/Omika.Diagnostics.Core/Users/DiagnosticUserRecord.cs
  • src/Omika.Diagnostics.Core/Users/IDiagnosticUserRepository.cs
  • src/Omika.Diagnostics.Core/Security/DiagnosticUserPolicy.cs
  • src/Omika.Diagnostics.Core/Security/DiagnosticUserAuthenticator.cs
  • src/Omika.Diagnostics.Data/NpgsqlDiagnosticUserRepository.cs
  • src/Omika.Diagnostics.Web/Authorization/DiagnosticCookieValidator.cs
  • src/Omika.Diagnostics.Web/Authorization/LoginThrottle.cs
  • src/Omika.Diagnostics.Web/Pages/Users.cshtml + Users.cshtml.cs
  • src/Omika.Diagnostics.Web/Pages/Account.cshtml + Account.cshtml.cs
  • tools/Omika.DiagnosticHashTool/Omika.DiagnosticHashTool.csproj + Program.cs
  • tests/Omika.Diagnostics.Tests/Fakes/FakeDiagnosticUserRepository.cs
  • tests/Omika.Diagnostics.Tests/DiagnosticUserPolicyTests.cs
  • tests/Omika.Diagnostics.Tests/DiagnosticUserAuthenticatorTests.cs
  • tests/Omika.Diagnostics.Tests/LoginThrottleTests.cs
  • tests/Omika.Diagnostics.Tests/DiagnosticUserPageModelTests.cs
  • tests/Omika.Diagnostics.Tests/DiagnosticAuthorizationMatrixTests.cs

47. Fichiers modifiés

  • src/Omika.Diagnostics.Core/Security/DiagnosticPasswordHasher.cs (ajout DummyHash)
  • src/Omika.Diagnostics.Data/DiagnosticDataServiceCollectionExtensions.cs (DI users)
  • src/Omika.Diagnostics.Web/Program.cs (throttle, cookie validator, /Users Admin)
  • src/Omika.Diagnostics.Web/Pages/Login.cshtml.cs (hybride + throttle + last login + message)
  • src/Omika.Diagnostics.Web/Pages/Shared/_Layout.cshtml (menu Utilisateurs + Mon compte)
  • docs/DEPLOIEMENT_PRODUCTION_DIAGNOSTICS.md
  • supprimé : _v/

48. Bugs trouvés

  • BLOCKER : aucun.
  • MAJEUR : aucun.
  • MINEUR : (1) PostgreSQL down → les utilisateurs DB retombent silencieusement sur les opérateurs config (fail-open cohérent avec l'outil, documenté) ; un opérateur bootstrap portant le même username qu'un utilisateur DB ne pourrait être distingué que DB indisponible. (2) Grâce ~60 s avant re-déconnexion d'un utilisateur désactivé (cache mémoire, documenté).
  • COSMÉTIQUE : lien « Mon compte » réutilise la classe .nav-user (style minimal).

49. Actions restantes avant Production

  • Créer la vraie identité Admin Production (jamais dans ce rapport) via la procédure §22.
  • Poser les vrais mots de passe PostgreSQL des rôles serveur (\password, jamais en script/log).
  • Configurer les URLs/ports HTTPS + reverse proxy Apache réels.
  • Publier et déployer les services (hors périmètre T23, à valider séparément).

50. Décision

T23 VALIDÉ — AUTHENTIFICATION DIAGNOSTICS.WEB PRÊTE POUR PRODUCTION