Reverse-Proxy
IP-Adressen vertrauenswürdiger Proxies (kommasepariert). Nur dann wird die echte Client-IP aus X-Forwarded-For für Rate-Limit & Audit verwendet.
diff --git a/admin/security.php b/admin/security.php
index afef7b1..d582660 100644
--- a/admin/security.php
+++ b/admin/security.php
@@ -16,8 +16,10 @@ $appTitle = $db->getSetting('app_title', 'UniFi Voucher System');
$error = '';
$success = '';
+$backupCodes = []; // nur direkt nach Erzeugung gefüllt
$hasPassword = !empty($user['password_hash']);
$totpEnabled = !empty($user['totp_enabled']);
+$setupRequired = isset($_GET['setup_required']);
// 2FA aktivieren (Code bestaetigen)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['enable_totp'])) {
@@ -31,14 +33,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['enable_totp'])) {
} elseif (!Totp::verify($secret, $code)) {
$error = 'Code ungültig. Bitte erneut versuchen.';
} else {
- $auth->enableTotp($user['id'], $secret);
+ $backupCodes = $auth->enableTotp($user['id'], $secret);
unset($_SESSION['totp_setup_secret']);
$totpEnabled = true;
- $success = 'Zwei-Faktor-Authentifizierung wurde aktiviert.';
+ $user = $auth->getCurrentUser();
+ $success = 'Zwei-Faktor-Authentifizierung wurde aktiviert. Bitte Recovery-Codes sicher speichern!';
}
}
}
+// Recovery-Codes neu erzeugen
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['regen_codes'])) {
+ if (!$auth->validateCsrfToken($_POST['csrf_token'] ?? '')) {
+ $error = 'Ungültiges Sicherheits-Token';
+ } elseif (!empty($user['totp_enabled'])) {
+ $backupCodes = $auth->regenerateBackupCodes($user['id']);
+ $user = $auth->getCurrentUser();
+ $success = 'Neue Recovery-Codes erzeugt. Die alten sind jetzt ungültig.';
+ }
+}
+
// 2FA deaktivieren
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['disable_totp'])) {
if (!$auth->validateCsrfToken($_POST['csrf_token'] ?? '')) {
@@ -88,6 +102,10 @@ input[type=text] { width:100%; padding:13px; border:2px solid #e0e0e0; border-ra
.btn { width:100%; padding:14px; border:none; border-radius:10px; font-size:15px; font-weight:600; cursor:pointer; margin-top:14px; }
.btn-primary { background:#667eea; color:#fff; } .btn-danger { background:#e25555; color:#fff; }
.back { display:block; text-align:center; margin-top:20px; color:#667eea; text-decoration:none; font-size:14px; }
+.codes-box { background:#fff8e6; border:1px solid #ffe3a3; border-radius:10px; padding:16px; margin-bottom:18px; }
+.codes-box strong { font-size:15px; } .codes-box p { color:#8a6d2f; font-size:13px; margin:6px 0 12px; }
+.codes { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
+.codes span { font-family:monospace; background:#fff; border:1px solid #ffe3a3; border-radius:6px; padding:8px; text-align:center; letter-spacing:2px; font-size:14px; }
@@ -95,15 +113,33 @@ input[type=text] { width:100%; padding:13px; border:2px solid #e0e0e0; border-ra
🔐 Zwei-Faktor-Authentifizierung
Konto: = htmlspecialchars($user['email']) ?>
+
+
Aus Sicherheitsgründen ist 2FA für Administratoren verpflichtend. Bitte jetzt einrichten.
+
= htmlspecialchars($error) ?>
= htmlspecialchars($success) ?>
+
+
+
🔑 Recovery-Codes
+
Bewahren Sie diese sicher auf. Jeder Code funktioniert einmal, falls Sie keinen Zugriff auf Ihre App haben.
+
+ = htmlspecialchars($c) ?>
+
+
+
+
● Nicht verfügbar
Ihr Konto meldet sich über Microsoft 365 an. 2FA wird dort in Ihrem Microsoft-Konto verwaltet.
● Aktiv
-
Bei jeder Anmeldung wird zusätzlich ein Code aus Ihrer Authenticator-App abgefragt.
+
Bei jeder Anmeldung wird zusätzlich ein Code aus Ihrer Authenticator-App abgefragt.
+ Verbleibende Recovery-Codes: = (int)$auth->backupCodesRemaining($user) ?>
+