consumeRecoveryToken(Auth::pendingUserId(), (string)($_POST['recovery_token'] ?? ''))) { Auth::completeSecondFactor($pdo, 'recovery_token'); // Der Token ist jetzt verbraucht. Das muss der Betreiber // erfahren, und zwar dort, wo er nach dem Anmelden landet - // also ueber die allgemeine Meldezeile und nicht in einem // Unterreiter, den er vielleicht wochenlang nicht aufschlaegt. $_SESSION['dc_flash'] = [ 'type' => 'danger', 'message' => 'Sie haben sich mit dem Notfalltoken angemeldet. ' . 'Er ist damit verbraucht. Unter System → Benutzer einen neuen erzeugen, ' . 'sonst bleibt nur noch der Hardwarekey.', ]; header('Location: /index.php'); exit; } Auth::recordSecondFactorFailure($pdo, 'recovery_token_invalid'); $error = 'Der Notfalltoken stimmt nicht.'; } catch (\Throwable $e) { Logger::error('Notfalltoken-Pruefung fehlgeschlagen', ['error' => $e->getMessage()]); $error = 'Anmeldung derzeit nicht moeglich. Bitte spaeter erneut versuchen.'; } } } if ($_SERVER['REQUEST_METHOD'] === 'POST' && $step === '') { $username = trim((string)($_POST['username'] ?? '')); $password = (string)($_POST['password'] ?? ''); // CSRF-Schutz auch am Login: verhindert erzwungene Fremdanmeldungen. if (!Csrf::isValid(is_string($_POST['csrf_token'] ?? null) ? $_POST['csrf_token'] : null)) { $error = 'Sitzung abgelaufen. Bitte erneut versuchen.'; } elseif ($username === '' || $password === '') { $error = 'Bitte fuellen Sie alle Felder aus.'; } else { try { $pdo = Db::init(); $ipLockout = Auth::lockoutSeconds($pdo, Http::clientIp()); $accLockout = Auth::accountLockoutSeconds($pdo, $username); if ($ipLockout > 0) { $error = sprintf( 'Zu viele fehlgeschlagene Anmeldeversuche von diesem Netzwerk. Bitte in %d Minute(n) erneut versuchen.', (int)ceil($ipLockout / 60) ); } elseif ($accLockout > 0) { $error = sprintf( 'Dieses Benutzerkonto ist aufgrund zu vieler Fehlversuche vorübergehend gesperrt. Bitte in %d Minute(n) erneut versuchen.', (int)ceil($accLockout / 60) ); } else { $result = Auth::startLogin($pdo, $username, $password); if ($result === Auth::LOGIN_COMPLETE) { header('Location: /index.php'); exit; } if ($result === Auth::LOGIN_SECOND_FACTOR) { // Nicht weiterleiten: Die zweite Stufe wird unten // gerendert. Ein Redirect wuerde nur eine Runde kosten // und der Adresszeile ansehen lassen, wie weit jemand // gekommen ist. $error = null; } } if ($error === null && Auth::pendingUserId() <= 0 && !Auth::isLoggedIn()) { // Pruefen, ob das Konto oder die IP durch diesen Fehlversuch soeben gesperrt wurde $newAccLockout = Auth::accountLockoutSeconds($pdo, $username); $newIpLockout = Auth::lockoutSeconds($pdo, Http::clientIp()); if ($newAccLockout > 0) { $error = sprintf( 'Zu viele Fehlversuche. Dieses Benutzerkonto wurde für %d Minute(n) gesperrt.', (int)ceil($newAccLockout / 60) ); } elseif ($newIpLockout > 0) { $error = sprintf( 'Zu viele Fehlversuche. Ihre IP-Adresse wurde für %d Minute(n) gesperrt.', (int)ceil($newIpLockout / 60) ); } else { $error = 'Ungueltige Anmeldedaten.'; } } } catch (\Throwable $e) { // Die urspruengliche Fassung gab hier die rohe PDO-Meldung samt // Hostname und Benutzernamen an jeden anonymen Besucher aus. Logger::error('Anmeldung fehlgeschlagen (technischer Fehler)', ['error' => $e->getMessage()]); $error = 'Anmeldung derzeit nicht moeglich. Bitte spaeter erneut versuchen.'; } } } // Erst hier abfragen, nicht weiter oben: Der Zwischenzustand entsteht unter // Umstaenden erst durch die Auswertung darueber. $pendingUsername = Auth::pendingUsername(); ?>
= $pendingUsername !== '' ? 'Zweiter Schritt: Hardwarekey' : 'Bitte melden Sie sich an, um fortzufahren' ?>