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 viaDiagnosticsAuth:Operators(nom +PasswordHashPBKDF2 + rôles), aucune identité DB. DiagnosticPasswordHasher(PBKDF2-SHA256, 100 000 itérations, sel 32 o, clé 32 o, comparaison temps constant) etDiagnosticOperatorAuthenticator(échec générique).- Rôles
Support/Admin+ policiesDiagnosticsSupport(Support OU Admin) /DiagnosticsAdmin(Admin seul). Conventions : dossier/→ Support ;/System,/Clients→ Admin. - Connexion dev sans mot de passe (Development uniquement) ; opérateur
admin-localen 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),USAGEséquence.omika_diagnostics_apietomika_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.
32. Cookie
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, 5DevelopmentLicenseTestsé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.sqlsrc/Omika.Diagnostics.Core/Users/DiagnosticUserRecord.cssrc/Omika.Diagnostics.Core/Users/IDiagnosticUserRepository.cssrc/Omika.Diagnostics.Core/Security/DiagnosticUserPolicy.cssrc/Omika.Diagnostics.Core/Security/DiagnosticUserAuthenticator.cssrc/Omika.Diagnostics.Data/NpgsqlDiagnosticUserRepository.cssrc/Omika.Diagnostics.Web/Authorization/DiagnosticCookieValidator.cssrc/Omika.Diagnostics.Web/Authorization/LoginThrottle.cssrc/Omika.Diagnostics.Web/Pages/Users.cshtml+Users.cshtml.cssrc/Omika.Diagnostics.Web/Pages/Account.cshtml+Account.cshtml.cstools/Omika.DiagnosticHashTool/Omika.DiagnosticHashTool.csproj+Program.cstests/Omika.Diagnostics.Tests/Fakes/FakeDiagnosticUserRepository.cstests/Omika.Diagnostics.Tests/DiagnosticUserPolicyTests.cstests/Omika.Diagnostics.Tests/DiagnosticUserAuthenticatorTests.cstests/Omika.Diagnostics.Tests/LoginThrottleTests.cstests/Omika.Diagnostics.Tests/DiagnosticUserPageModelTests.cstests/Omika.Diagnostics.Tests/DiagnosticAuthorizationMatrixTests.cs
47. Fichiers modifiés
src/Omika.Diagnostics.Core/Security/DiagnosticPasswordHasher.cs(ajoutDummyHash)src/Omika.Diagnostics.Data/DiagnosticDataServiceCollectionExtensions.cs(DI users)src/Omika.Diagnostics.Web/Program.cs(throttle, cookie validator,/UsersAdmin)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