false, 'error' => 'Nur POST erlaubt.'], 405); if (!csrf_valid()) passkey_out(['ok' => false, 'error' => 'Die Seite war zu lange geöffnet. Bitte neu laden und noch einmal versuchen.'], 403); $pdo = get_db(); $action = (string) ($_POST['action'] ?? ''); $rp = webauthn_rp(); if (!webauthn_enabled()) { passkey_out(['ok' => false, 'error' => 'Passkeys sind in dieser Installation ausgeschaltet.'], 403); } if (!$rp['secure']) { passkey_out(['ok' => false, 'error' => 'Passkeys brauchen eine HTTPS-Adresse. Die Website-URL in den Einstellungen beginnt nicht mit https://.'], 400); } $cred = json_decode((string) ($_POST['credential'] ?? ''), true); if ($action === 'login_options') { if (!rate_limit_check('passkey_login_options', client_ip(), 60, 15)) passkey_out(['ok' => false, 'error' => 'Zu viele Versuche. Bitte warten Sie einige Minuten.'], 429); passkey_out(['ok' => true, 'options' => webauthn_request_options()]); } if ($action === 'login_verify') { if (!rate_limit_check('passkey_login_attempt', client_ip(), 15, 15)) passkey_out(['ok' => false, 'error' => 'Zu viele Versuche. Bitte warten Sie einige Minuten.'], 429); if (!is_array($cred)) passkey_out(['ok' => false, 'error' => 'Antwort des Geräts ist ungültig.'], 400); $r = webauthn_login_finish($pdo, $cred); if (empty($r['ok'])) { login_guard_failed(null, '', 'web', 'Passkey abgelehnt: ' . (string) ($r['error'] ?? '')); passkey_out(['ok' => false, 'error' => (string) ($r['error'] ?? 'Anmeldung fehlgeschlagen.')], 401); } rate_limit_reset('passkey_login_attempt', client_ip()); unset($_SESSION['totp_pending_staff_id']); staff_login($r['staff']); passkey_out(['ok' => true, 'redirect' => '/staff/reports.php']); } // Ab hier: nur angemeldete Mitarbeiter $staff = current_staff(); if (!$staff) passkey_out(['ok' => false, 'error' => 'Bitte melden Sie sich an.'], 401); if ($action === 'register_options' || $action === 'register_verify') { // Ein neuer Passkey bleibt dauerhaft gültig: darum zuerst das Passwort bestätigen (schützt vor einer fremden offenen Sitzung) if ($action === 'register_options') { if (!rate_limit_check('passkey_register', (string) $staff['id'], 10, 15)) passkey_out(['ok' => false, 'error' => 'Zu viele Versuche. Bitte warten Sie einige Minuten.'], 429); if (!password_verify((string) ($_POST['password'] ?? ''), (string) $staff['password_hash'])) passkey_out(['ok' => false, 'error' => 'Das Passwort ist falsch.'], 403); passkey_out(['ok' => true, 'options' => webauthn_creation_options($pdo, $staff)]); } if (!is_array($cred)) passkey_out(['ok' => false, 'error' => 'Antwort des Geräts ist ungültig.'], 400); $r = webauthn_register_finish($pdo, $staff, $cred, (string) ($_POST['name'] ?? '')); if (empty($r['ok'])) passkey_out(['ok' => false, 'error' => (string) ($r['error'] ?? 'Registrierung fehlgeschlagen.')], 400); flash('success', 'Passkey hinzugefügt. Beim nächsten Anmelden können Sie „Mit Passkey anmelden“ wählen.'); audit_log((int) $staff['id'], 'Passkey hinzugefügt'); login_guard_record($pdo, 'passkey_added', (int) $staff['id'], (string) $staff['email'], 'web', 'Passkey hinzugefügt'); passkey_out(['ok' => true]); } passkey_out(['ok' => false, 'error' => 'Unbekannte Aktion.'], 400);