Implementazione completa del progetto allineamento alla suite Evix (TRPG/lg231),
basato sul doc canonico docs/GAP_TRPG_NIS2_ALIGNMENT.md (5 fasi, 18 gap).
Version 1.0.0 → 1.5.0
Fase 1 — SSO Federation (v1.1.0)
- Migration 015_sso_columns: users.sso_identity_id + password_version
- application/services/SsoHelper.php (client SSO dual-mode, cURL nativo, zero deps)
- AuthController::login() + changePassword() conditional SSO (SSO_MODE=local default)
Fase 2 — Multi-device Sessions (v1.2.0)
- Migration 016_active_sessions: tabella + refresh_tokens.session_jti
- BaseController::requireAuth() verifica jti + last_activity throttle + parseDeviceLabel
- login() genera jti, logout/changePassword revoca selettiva
- GET/DELETE /auth/sessions[/{id}]
- UI settings.html tab Sicurezza con lista device + revoca
Fase 3 — Password Reset + Tenant Switcher (v1.3.0)
- Migration 017_password_reset_tokens (TTL 30min, single-use)
- POST /auth/forgot-password (risposta opaca) + reset-password
- Pagine forgot-password.html + reset-password.html (con strength bar)
- EmailService::sendPasswordReset
- POST /auth/switchContext con rotazione JWT + organization_id claim
- Dropdown tenant in sidebar esposto a tutti gli utenti con ≥2 org
Fase 4 — Impersonate + Preferences + Versioning UI (v1.4.0)
- POST /auth/impersonate (super_admin o consulente stesso firm, TTL 1h, audit)
- Migration 018_user_preferences: users.theme/timezone/notif_email/notif_inapp
- GET/PUT /auth/preferences
- Sidebar footer mostra versione + changelog modal su click
Fase 5 — Branding white-label + Auth-gate (v1.5.0)
- Migration 019_firm_branding (logo/colori/brand_name per consulting firm)
- BrandingController GET /branding/current (auth opzionale) + PUT
- common.js auto-applica CSS variables al boot
- public/js/auth-gate.js (gate password client-side per docs riservati, da TRPG)
Skip motivati:
- G15 demo login: simulator esistenti coprono
- G18 refactor controllers: rinviato (~5gg, valore tecnico solo)
Cron sync SSO: AgileHub Ticket #220 aperto a team AGILEHUB per estendere
sso-password-sync.sh al DB nis2_agile_db. Prerequisito per switch SSO_MODE=dual.
Backup files: tutti i file modificati hanno .bak.pre-{fase}-{ts} sia in DEV
sia in /var/www/nis2-agile/.backups/ su Hetzner (rollback ready).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.9 KiB
Contesto Ultima Sessione
Data: 2026-05-29 Durata: sessione molto lunga — progetto allineamento NIS2↔TRPG completato
Cosa abbiamo fatto
Progetto allineamento NIS2 ↔ TRPG (suite Evix)
Doc canonico: docs/GAP_TRPG_NIS2_ALIGNMENT.md
Analisi gap tra TRPG v1.54.1 e NIS2 v1.0.0 → 18 gap (G01-G18) raggruppati in 5 fasi. 6 decisioni utente confermate (§10 del doc). Tutte le 5 fasi completate in unica sessione. Version 1.0.0 → 1.5.0.
Fase 1 — SSO Federation (v1.1.0)
- Migration
015_sso_columns.sql: aggiungeusers.sso_identity_id,users.password_version - Nuovo
application/services/SsoHelper.php— client SSO dual-mode (cURL nativo, zero deps) AuthController::login()+changePassword()con conditional SSO (dietroSSO_MODE).envsu Hetzner + vault-stewardtier1__nis2-app__sso/internal_key(placeholder)- AgileHub Ticket #220 aperto a team AGILEHUB per estendere
sso-password-sync.sh - SSO_MODE=local di default → comportamento utente invariato
Fase 2 — Multi-device Sessions (v1.2.0)
- Migration
016_active_sessions.sql: tabellaactive_sessions(jti tracking) +refresh_tokens.session_jti BaseController::requireAuth()verifica jti + last_activity throttleBaseController::parseDeviceLabel($ua)— parsing UA-friendlylogin()genera jti + insert active_sessions,logout()revoca selettiva,changePassword()revoca altre sessioni- 3 nuovi endpoint:
GET/DELETE /auth/sessions[/{id}] - UI
settings.htmltab Sicurezza: card "Sessioni Attive" con device list + revoca
Fase 3 — Password Reset + Context Switch (v1.3.0)
- Migration
017_password_reset.sql: tabellapassword_reset_tokens(TTL 30 min, single-use) - Endpoint
POST /auth/forgot-password(risposta opaca anti-enumeration) +POST /auth/reset-password - Pagine HTML:
forgot-password.html,reset-password.html(con strength bar) login.html: link "Password dimenticata?" ora funzionante (era alert manuale)EmailService::sendPasswordReset()aggiunto- Endpoint
POST /auth/switchContextcon rotazione JWT (revoca old session + nuovo jti + organization_id claim) - Dropdown tenant in sidebar esposto a TUTTI gli utenti con ≥2 org (prima solo consulenti)
_switchOrg()in common.js ora chiama switchContext + setTokens
Fase 4 — Impersonate + Preferences + Versioning UI (v1.4.0)
- Endpoint
POST /auth/impersonate(super_admin o consulente stesso firm, TTL 1h, JWT conimpersonated_byclaim, audit log) - Migration
018_user_preferences.sql:users.theme/timezone/notif_email/notif_inapp - Endpoint
GET/PUT /auth/preferences - Sidebar footer mostra versione corrente, click → modal changelog
- common.js
_loadVersionFooter()fetcha/version.jsonal boot
Fase 5 — Branding + Auth-gate (v1.5.0)
- Migration
019_firm_branding.sql: tabellafirm_branding(logo/colori/brand name per consulting firm) BrandingController.php(NUOVO):GET /branding/current(auth opzionale),PUT /branding(super_admin o consulente)- common.js
_loadFirmBranding()applica CSS variables al boot public/js/auth-gate.jscopiato/adattato da TRPG (gate password client-side per documenti riservati)- G15 skip: simulator esistenti coprono demo flows
- G18 skip: refactor controller rinviato (~5gg investimento, valore tecnico)
Scoperta CRITICA durante Fase 3: topologia DB
Vedi MEMORY.md → project_db_topology.md per dettagli.
PHP-FPM nel container nis2-app (clear_env=yes + .env mancante in container) ricade su DB_HOST=localhost di default → connette al MySQL del HOST Hetzner, NON al container nis2-db.
Conseguenza: tutte le migration vanno applicate via:
mysql -u$DB_USER -p$DB_PASS -h localhost $DB_NAME < migration.sql
dal HOST Hetzner, NON docker exec nis2-db mysql.
Lo si è scoperto perché Fase 3 ha fatto 500 con "table password_reset_tokens doesn't exist" anche se la migration era stata applicata "con successo" — andava sul DB sbagliato.
File creati/modificati (riepilogo)
SQL (5 migration nuove, tutte applicate al DB host):
docs/sql/015_sso_columns.sqldocs/sql/016_active_sessions.sqldocs/sql/017_password_reset.sqldocs/sql/018_user_preferences.sqldocs/sql/019_firm_branding.sql
PHP nuovi:
application/services/SsoHelper.phpapplication/controllers/BrandingController.php
PHP modificati:
application/controllers/AuthController.php(+13 metodi)application/controllers/BaseController.php(requireAuth + parseDeviceLabel + generateRefreshToken)application/services/EmailService.php(+sendPasswordReset)application/config/config.php(+2 costanti)public/index.php(+12 route + branding controller)
HTML/JS:
public/login.html(link forgot-password)public/forgot-password.html(NUOVO)public/reset-password.html(NUOVO)public/settings.html(tab Sicurezza con sessions)public/js/common.js(version footer + branding loader + tenant switcher esposto)public/js/auth-gate.js(NUOVO)public/version.json(1.0.0 → 1.5.0)
Doc:
docs/GAP_TRPG_NIS2_ALIGNMENT.md(NUOVO, piano completo + stato esecuzione)
Backups creati (tutti con timestamp 20260529-*):
/projects/nis2-agile/application/controllers/*.bak.pre-{sso,sessions,fase3,fase4}-*/projects/nis2-agile/public/*.bak.pre-{sessions,fase3,fase4}-*- Hetzner
/var/www/nis2-agile/.backups/*
File deployati su Hetzner
Tutti via scp -i .ssh-temp/id_ed25519_nis2-agile_8h_*:
- 4 PHP in
/var/www/nis2-agile/application/controllers/ - 1 PHP in
/var/www/nis2-agile/application/services/ - 1 PHP in
/var/www/nis2-agile/application/config/ - 5 HTML/JSON in
/var/www/nis2-agile/public/ - 2 JS in
/var/www/nis2-agile/public/js/
Nessun docker restart eseguito (non necessario, bind mount + PHP-FPM rilegge ad ogni request).
Decisioni utente confermate (§10 doc)
- SSO_MODE iniziale:
local(switch a dual dopo ≥7gg validazione) - Backfill SSO: lazy on-login (no script massivo)
- Sessioni concorrenti: nessun limite
- TTL token reset: 30 min
- Cadenza version bump: MINOR per fase (eseguito 1.1.0 → 1.5.0)
- Branding white-label: mantenuto in Fase 5 (eseguito)
Problemi aperti / dipendenze esterne
- AgileHub Ticket #220 (priorità per switch a
dual): estenderesso-password-sync.shper includere DBnis2_agile_db. Status: OPEN, dispatchGroup=RESOLVER, dispatchProduct=AGILEHUB. - vault placeholder
tier1__nis2-app__sso/internal_key: contienePLACEHOLDER_REGISTER_WITH_TENANT_MS_BEFORE_ACTIVATING_DUAL_MODE. Va sostituito con chiave reale ottenuta dal Tenant MS prima del switch. docker compose up -d --force-recreate apprichiesto per attivare env vaultSSO_INTERNAL_KEYquando si passa a dual (oggi opzionale, defaults sani lato codice).SSO_MODE=local→dual: modifica una sola riga in.env. Da eseguire solo dopo aver risolto le 3 dipendenze sopra. Bumpare version dopo (oltre 1.5.0).
Prossimi passi consigliati
- Validare 1.5.0 in produzione con il primo utente reale (test password reset + sessions UI + tenant switcher)
- Attendere risoluzione AgileHub Ticket #220
- Ottenere SSO_INTERNAL_KEY dal Tenant MS
- Replace placeholder vault:
docker exec vault-steward node /app/cli/vault-cli.js migrate tier1__nis2-app__sso internal_key '<real>' - Cambiare
.envsu Hetzner:SSO_MODE=local→SSO_MODE=dual docker compose up -d --force-recreate appper ricaricare env- Smoke test login utente con identità SSO esistente
- Bumpare a 1.6.0 con changelog "SSO dual mode attivato"
File chiave da sapere
CLAUDE.md— single source of truth governancedocs/GAP_TRPG_NIS2_ALIGNMENT.md— piano 5 fasi + stato esecuzioneMEMORY.md(auto) →project_db_topology.md— lezione CRITICA su DB host vs containerMEMORY.md(auto) →project_alignment_trpg.md— stato progetto allineamento