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>
107 lines
4.6 KiB
HTML
107 lines
4.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Password dimenticata - NIS2 Agile</title>
|
|
<link rel="stylesheet" href="css/style.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
|
<style>
|
|
.back-link { display:block; text-align:center; margin-top:14px; font-size:.85rem; color:#6B7280; text-decoration:none; }
|
|
.back-link:hover { color: var(--color-primary, #2563eb); }
|
|
.auth-success { background:#ECFDF5; color:#065F46; border:1px solid #A7F3D0; padding:12px 16px; border-radius:6px; font-size:.9rem; margin-bottom:16px; display:none; }
|
|
.auth-success.visible { display:block; }
|
|
.auth-helper { font-size:.85rem; color:#6B7280; margin-bottom:18px; line-height:1.5; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="auth-page">
|
|
<div class="auth-card">
|
|
<div class="auth-header">
|
|
<div class="auth-logo">
|
|
<div class="auth-logo-icon">
|
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/>
|
|
</svg>
|
|
</div>
|
|
<span class="auth-logo-text">NIS2 <span>Agile</span></span>
|
|
</div>
|
|
<p class="auth-subtitle">Reimposta la tua password</p>
|
|
</div>
|
|
|
|
<div class="auth-body">
|
|
<div class="auth-error" id="err"></div>
|
|
<div class="auth-success" id="ok"></div>
|
|
|
|
<p class="auth-helper">Inserisci l'indirizzo email associato al tuo account. Ti invieremo un link valido 30 minuti per impostare una nuova password.</p>
|
|
|
|
<form id="forgot-form" novalidate>
|
|
<div class="form-group">
|
|
<label class="form-label" for="email">Indirizzo Email</label>
|
|
<input type="email" id="email" name="email" class="form-input"
|
|
placeholder="nome@azienda.it" autocomplete="email" required>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary btn-lg w-full" id="submit-btn">
|
|
Invia link
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="auth-footer">
|
|
<a href="login.html" class="back-link"><i class="fas fa-arrow-left"></i> Torna al login</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="js/api.js"></script>
|
|
<script>
|
|
const form = document.getElementById('forgot-form');
|
|
const err = document.getElementById('err');
|
|
const ok = document.getElementById('ok');
|
|
const btn = document.getElementById('submit-btn');
|
|
|
|
form.addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
err.classList.remove('visible');
|
|
ok.classList.remove('visible');
|
|
|
|
const email = document.getElementById('email').value.trim();
|
|
if (!email) {
|
|
err.textContent = 'Inserisci l\'indirizzo email.';
|
|
err.classList.add('visible');
|
|
return;
|
|
}
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Invio in corso...';
|
|
try {
|
|
const res = await fetch('/api/auth/forgot-password', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email: email })
|
|
});
|
|
const data = await res.json();
|
|
if (res.status === 429) {
|
|
err.textContent = data.message || 'Troppe richieste. Riprova più tardi.';
|
|
err.classList.add('visible');
|
|
} else if (data.success) {
|
|
ok.textContent = data.message;
|
|
ok.classList.add('visible');
|
|
form.style.display = 'none';
|
|
} else {
|
|
err.textContent = data.message || 'Errore. Riprova.';
|
|
err.classList.add('visible');
|
|
}
|
|
} catch (e) {
|
|
err.textContent = 'Errore di connessione al server.';
|
|
err.classList.add('visible');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Invia link';
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|