Files
nis2-agile/public/reset-password.html
DevEnv nis2-agileandClaude Opus 4.8 36513fa722 [FEAT] PWA installabile Android/iOS conforme AGID (v1.15.0)
- manifest.webmanifest + service worker (offline shell, /api network-only, asset stale-while-revalidate)
- set icone (192/512 + maskable + apple-touch + favicon), generate zero-deps (pure Node+zlib)
- tag PWA + viewport viewport-fit=cover (zoom mantenuto, WCAG 1.4.4) in 77 pagine HTML
- AGID/WCAG 2.1 AA: touch target >=44px, orientamento/zoom liberi, safe-area standalone
- generatori riproducibili: scripts/gen-pwa-icons.mjs + scripts/inject-pwa-head.mjs
- bump version 1.14.1 -> 1.15.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:09:54 +02:00

189 lines
9.1 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Imposta nuova password - NIS2 Agile</title>
<!-- Bootstrap Italia v2.18.1 — self-hostato da /vendor (MAI CDN). Variante "-bi" della reset-password.html. -->
<link rel="stylesheet" href="vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
<style>
body { min-height: 100vh; display: flex; align-items: center; background: #f5f6f7; }
.auth-wrap { width: 100%; max-width: 460px; margin: 0 auto; padding: 24px; }
.auth-brand { text-align: center; margin-bottom: 24px; }
.auth-brand .brand-name { font-weight: 700; font-size: 1.5rem; color: #06c; }
.auth-brand .brand-name span { color: #17324d; }
.auth-brand .brand-sub { color: #5a6772; font-size: .9rem; margin-top: 4px; }
.pw-wrap { position: relative; }
.pw-wrap .form-control { padding-right: 44px; }
.pw-toggle { position: absolute; right: 8px; top: 8px; background: none; border: none; color: #5a6772; cursor: pointer; padding: 6px; }
.ico { width: 1em; height: 1em; fill: currentColor; vertical-align: -.125em; }
</style>
<!-- PWA:start -->
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#0066CC">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="NIS2 Agile">
<meta name="application-name" content="NIS2 Agile">
<script src="/js/pwa.js?v=20260614" defer></script>
<!-- PWA:end -->
</head>
<body>
<main class="auth-wrap">
<div class="auth-brand">
<div class="brand-name">NIS2 <span>Agile</span></div>
<div class="brand-sub">Imposta una nuova password</div>
</div>
<div class="card card-bg has-bkg-grey shadow">
<div class="card-body">
<h1 class="h4 mb-3">Nuova password</h1>
<div class="alert alert-danger d-none" id="err" role="alert"></div>
<div class="alert alert-success d-none" id="ok" role="status"></div>
<form id="reset-form" novalidate>
<div class="form-group">
<label for="pw" class="active">Nuova password</label>
<div class="pw-wrap">
<input type="password" id="pw" class="form-control"
placeholder="Almeno 8 caratteri" autocomplete="new-password" required
aria-describedby="strength-text">
<button type="button" class="pw-toggle" tabindex="-1" onclick="togglePw('pw',this)" aria-label="Mostra o nascondi la password">
<svg class="icon icon-sm" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-password-visible"></use></svg>
</button>
</div>
<div class="progress mt-2" style="height:4px;">
<div class="progress-bar" id="strength-bar" role="progressbar" style="width:0%;" aria-hidden="true"></div>
</div>
<div class="form-text" id="strength-text" aria-live="polite"></div>
</div>
<div class="form-group">
<label for="pw2" class="active">Conferma password</label>
<div class="pw-wrap">
<input type="password" id="pw2" class="form-control"
placeholder="Ripeti la password" autocomplete="new-password" required>
<button type="button" class="pw-toggle" tabindex="-1" onclick="togglePw('pw2',this)" aria-label="Mostra o nascondi la password">
<svg class="icon icon-sm" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-password-visible"></use></svg>
</button>
</div>
</div>
<p class="text-muted small mb-3">Minimo 8 caratteri, con almeno una maiuscola, una minuscola e un numero.</p>
<button type="submit" class="btn btn-primary w-100" id="submit-btn">
Imposta password
</button>
</form>
</div>
</div>
<p class="text-center mt-3 mb-0">
<a href="login.html">
<svg class="ico me-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-arrow-left"></use></svg>
Torna al login
</a>
</p>
</main>
<script src="vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script>
if (window.bootstrap && bootstrap.loadFonts) { bootstrap.loadFonts('vendor/bootstrap-italia/dist/fonts'); }
</script>
<!-- Stessa logica della reset-password.html: ZERO modifiche backend -->
<script>
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token') || '';
const err = document.getElementById('err');
const ok = document.getElementById('ok');
const form = document.getElementById('reset-form');
const btn = document.getElementById('submit-btn');
const pwInput = document.getElementById('pw');
const pwBar = document.getElementById('strength-bar');
const pwText = document.getElementById('strength-text');
if (!token) {
err.textContent = 'Token mancante. Richiedi un nuovo link di reset.';
err.classList.remove('d-none');
form.style.display = 'none';
}
function togglePw(id, button) {
const i = document.getElementById(id);
i.type = (i.type === 'password') ? 'text' : 'password';
const use = button.querySelector('use');
use.setAttribute('href', 'vendor/bootstrap-italia/dist/svg/sprites.svg#' + (i.type === 'text' ? 'it-password-invisible' : 'it-password-visible'));
}
function computeStrength(pw) {
let s = 0;
if (pw.length >= 8) s++;
if (pw.length >= 12) s++;
if (/[A-Z]/.test(pw) && /[a-z]/.test(pw)) s++;
if (/\d/.test(pw) && /[^A-Za-z0-9]/.test(pw)) s++;
return s;
}
pwInput.addEventListener('input', function() {
const s = computeStrength(pwInput.value);
const colors = ['#d4d8db', '#d9364f', '#e58a00', '#06c', '#008758'];
const labels = ['', 'Debole', 'Media', 'Buona', 'Ottima'];
pwBar.style.width = (pwInput.value.length > 0 ? (s * 25) : 0) + '%';
pwBar.style.backgroundColor = colors[s];
pwText.textContent = pwInput.value.length > 0 ? 'Sicurezza: ' + labels[s] : '';
});
form.addEventListener('submit', async function(e) {
e.preventDefault();
err.classList.add('d-none');
ok.classList.add('d-none');
const pw = document.getElementById('pw').value;
const pw2 = document.getElementById('pw2').value;
if (pw !== pw2) {
err.textContent = 'Le due password non coincidono.';
err.classList.remove('d-none');
return;
}
if (pw.length < 8) {
err.textContent = 'La password deve essere di almeno 8 caratteri.';
err.classList.remove('d-none');
return;
}
btn.disabled = true;
btn.textContent = 'Salvataggio...';
try {
const res = await fetch('/api/auth/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: token, new_password: pw })
});
const data = await res.json();
if (data.success) {
ok.textContent = data.message + ' Sarai reindirizzato al login.';
ok.classList.remove('d-none');
form.style.display = 'none';
setTimeout(function() { window.location.href = 'login.html'; }, 2500);
} else {
err.textContent = data.message || 'Errore. Token non valido o scaduto.';
err.classList.remove('d-none');
}
} catch (e) {
err.textContent = 'Errore di connessione al server.';
err.classList.remove('d-none');
} finally {
btn.disabled = false;
btn.textContent = 'Imposta password';
}
});
</script>
</body>
</html>