isLoggedIn()) { header('Location: index.php'); exit; } I18n::init(); $db = Database::getInstance(); $appTitle = $db->getSetting('app_title', 'UniFi Voucher System'); $logoUrl = $db->getSetting('logo_url', ''); $faviconUrl = $db->getSetting('favicon_url', ''); $systemUrl = rtrim($db->getSetting('system_url', ''), '/'); // Fallback: URL automatisch erkennen (wie im Mailer), sonst ist der // Reset-Link in der E-Mail relativ und damit kaputt. if (empty($systemUrl)) { $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http'; $scriptPath = dirname($_SERVER['SCRIPT_NAME']); $scriptPath = $scriptPath === '/' ? '' : $scriptPath; $systemUrl = $protocol . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . $scriptPath; } $error = ''; $success = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $email = trim($_POST['email'] ?? ''); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $error = __('error_email_invalid'); } else { // IP-Rate-Limit gegen Mail-Bombing: max. 5 Reset-Anfragen / 15 Min. // Bei Limit trotzdem die generische Erfolgsmeldung zeigen (keine // Information darueber preisgeben, ob das Konto existiert). $resetLimited = throttleHit($db, 'password_reset', 5, 15) === true; $user = $resetLimited ? null : $db->fetchOne("SELECT * FROM users WHERE email = ? AND is_active = 1 AND password_hash IS NOT NULL", [$email]); // Always show success (don't reveal whether email exists) if ($user) { try { // Delete old tokens for this user $db->execute("DELETE FROM password_reset_tokens WHERE user_id = ?", [$user['id']]); // Generate token $token = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', strtotime('+1 hour')); $db->execute( "INSERT INTO password_reset_tokens (user_id, token, expires_at) VALUES (?, ?, ?)", [$user['id'], $token, $expiresAt] ); // Send email $resetUrl = $systemUrl . '/reset_password.php?token=' . $token; $mailer = new Mailer(); $subject = $appTitle . ' – Passwort zurücksetzen'; $body = "Hallo {$user['name']},\n\n" . "Sie haben eine Passwort-Rücksetzung angefordert.\n\n" . "Klicken Sie auf den folgenden Link, um Ihr Passwort zurückzusetzen (gültig für 1 Stunde):\n\n" . $resetUrl . "\n\n" . "Falls Sie dies nicht angefordert haben, ignorieren Sie diese E-Mail.\n\n" . $appTitle; $mailer->sendRaw($user['email'], $subject, $body); // Audit log $db->execute( "INSERT INTO audit_log (user_id, action, entity_type, entity_id, details, ip_address) VALUES (?, 'password_reset', 'user', ?, 'Reset-Link angefordert', ?)", [$user['id'], $user['id'], $_SERVER['REMOTE_ADDR'] ?? ''] ); } catch (Exception $e) { // Silent – don't reveal errors to user } } $success = __('reset_success'); } } ?>
= __('reset_subtitle') ?>