feat: add TOTP 2FA with QR code and manual secret entry
This commit is contained in:
+40
-10
@@ -209,17 +209,27 @@ footer a:hover { color: #2563eb; }
|
||||
</div>
|
||||
|
||||
<form class="form active" id="form-login" onsubmit="doLogin(event)">
|
||||
<div class="field">
|
||||
<label for="l-user">Benutzername</label>
|
||||
<input type="text" id="l-user" autocomplete="username" placeholder="dein.name" required>
|
||||
<span style="font-size:11px;color:#9ca3af;margin-top:2px">Der Teil deiner Schul-E-Mail vor dem @</span>
|
||||
<div id="login-step-1">
|
||||
<div class="field" style="margin-bottom:14px">
|
||||
<label for="l-user">Benutzername</label>
|
||||
<input type="text" id="l-user" autocomplete="username" placeholder="dein.name" required>
|
||||
<span style="font-size:11px;color:#9ca3af;margin-top:2px">Der Teil deiner Schul-E-Mail vor dem @</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="l-pass">Passwort</label>
|
||||
<input type="password" id="l-pass" autocomplete="current-password" placeholder="••••••" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="l-pass">Passwort</label>
|
||||
<input type="password" id="l-pass" autocomplete="current-password" placeholder="••••••" required>
|
||||
<div id="login-step-2" style="display:none">
|
||||
<div class="field">
|
||||
<label for="l-totp">2FA-Code</label>
|
||||
<input type="text" id="l-totp" autocomplete="one-time-code" placeholder="6-stelliger Code" maxlength="6" inputmode="numeric">
|
||||
<span style="font-size:11px;color:#9ca3af;margin-top:2px">Code aus deiner Authenticator-App eingeben</span>
|
||||
</div>
|
||||
<button type="button" style="font-size:12px;color:#6b7280;background:none;border:none;cursor:pointer;padding:0;margin-top:4px" onclick="backToStep1()">← Zurück</button>
|
||||
</div>
|
||||
<div class="notice notice-red" id="login-err"></div>
|
||||
<button class="btn-submit" type="submit">Anmelden</button>
|
||||
<button class="btn-submit" type="submit" id="login-btn">Anmelden</button>
|
||||
</form>
|
||||
|
||||
<form class="form" id="form-reg" onsubmit="doRegister(event)">
|
||||
@@ -296,12 +306,32 @@ function showErr(id, msg) {
|
||||
}
|
||||
function clearErr(id) { document.getElementById(id).classList.remove('show'); }
|
||||
|
||||
let totpPending = false;
|
||||
|
||||
function backToStep1() {
|
||||
totpPending = false;
|
||||
document.getElementById('login-step-1').style.display = '';
|
||||
document.getElementById('login-step-2').style.display = 'none';
|
||||
document.getElementById('login-btn').textContent = 'Anmelden';
|
||||
clearErr('login-err');
|
||||
}
|
||||
|
||||
async function doLogin(e) {
|
||||
e.preventDefault(); clearErr('login-err');
|
||||
const r = await fetch('/api/login', { method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ username: document.getElementById('l-user').value, password: document.getElementById('l-pass').value }) });
|
||||
const body = { username: document.getElementById('l-user').value, password: document.getElementById('l-pass').value };
|
||||
if (totpPending) body.totp_token = document.getElementById('l-totp').value;
|
||||
const r = await fetch('/api/login', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
||||
const d = await r.json();
|
||||
if (!r.ok) { showErr('login-err', d.error); return; }
|
||||
if (d.requireTotp) {
|
||||
totpPending = true;
|
||||
document.getElementById('login-step-1').style.display = 'none';
|
||||
document.getElementById('login-step-2').style.display = '';
|
||||
document.getElementById('login-btn').textContent = 'Bestätigen';
|
||||
document.getElementById('l-totp').value = '';
|
||||
document.getElementById('l-totp').focus();
|
||||
return;
|
||||
}
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user