Files
deskhub/DeskHub-Webseite/staff/login.php
T
2026-10-24 20:14:39 +00:00

159 lines
7.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../includes/bootstrap.php';
require_once __DIR__ . '/../includes/totp.php';
if (current_staff()) {
redirect('/staff/reports.php');
}
$errors = [];
$need2fa = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrf_valid()) {
// Die Anmeldeseite war so lange offen, dass die Sitzung auf dem Server inzwischen aufgeräumt wurde: neu anmelden statt Fehlerseite
$errors[] = 'Die Anmeldeseite war zu lange geöffnet und die Sitzung ist abgelaufen. Bitte melden Sie sich jetzt noch einmal an.';
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? 'login';
if ($action === 'verify_totp') {
$pendingId = $_SESSION['totp_pending_staff_id'] ?? null;
$code = trim($_POST['totp_code'] ?? '');
if (!$pendingId) {
$errors[] = 'Sitzung abgelaufen. Bitte neu anmelden.';
} else {
$stmt = get_db()->prepare('SELECT * FROM staff WHERE id = ? AND is_active = 1');
$stmt->execute([$pendingId]);
$staff = $stmt->fetch();
if ($staff && totp_verify($staff['totp_secret'], $code)) {
unset($_SESSION['totp_pending_staff_id']);
staff_login($staff);
redirect('/staff/reports.php');
}
$errors[] = 'Ungültiger Code. Bitte erneut versuchen.';
$need2fa = true;
}
} else {
$email = trim($_POST['email'] ?? '');
$pass = $_POST['password'] ?? '';
if (!rate_limit_check('staff_login_attempt', client_ip(), 10, 15)) {
$errors[] = 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in einigen Minuten erneut.';
} elseif ($email && !rate_limit_check('staff_login_email', strtolower($email), 5, 15)) {
// Konto-basierte Sperre – verhindert Brute-Force über wechselnde IPs
$errors[] = 'Zu viele Anmeldeversuche für dieses Konto. Bitte warten Sie einige Minuten.';
} else {
$stmt = get_db()->prepare('SELECT * FROM staff WHERE LOWER(email) = LOWER(?) AND is_active = 1');
$stmt->execute([$email]);
$staff = $stmt->fetch();
if ($staff && password_verify($pass, $staff['password_hash'])) {
// Erfolgreicher Login – Zähler zurücksetzen
rate_limit_reset('staff_login_email', strtolower($email));
if ($staff['totp_enabled']) {
$_SESSION['totp_pending_staff_id'] = $staff['id'];
$need2fa = true;
} else {
staff_login($staff);
redirect('/staff/reports.php');
}
} else {
$errors[] = 'E-Mail-Adresse oder Passwort ist falsch.';
}
}
}
}
$pageTitle = 'Mitarbeiter-Login';
$activePage = '';
// Staff-Header hat Sidebar – für Login-Seite eigenes schlankes HTML
$banner = get_setting('banner_path');
$logo = get_setting('logo_path');
$displayName = get_setting('display_name', config()['site']['name']);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mitarbeiter-Login – <?= e($displayName) ?></title>
<link rel="stylesheet" href="/assets/css/style.css?v=<?= @filemtime(__DIR__ . "/../assets/css/style.css") ?: time() ?>">
<?php $customThemeStylesheet = active_custom_theme_stylesheet(); ?>
<?php if ($customThemeStylesheet): ?>
<link rel="stylesheet" href="<?= e($customThemeStylesheet) ?>">
<?php endif; ?>
<link rel="manifest" href="/manifest-staff.php">
<meta name="theme-color" content="#0f6f6a">
<?= site_icon_links() ?>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/sw.js').catch(function(){});}</script>
</head>
<body class="login-page staff-login-page"<?php if ($banner): ?> style="background-image:url('<?= e($banner) ?>'); background-size:cover; background-position:center"<?php endif; ?>>
<?= i18n_switcher_html('floating') ?>
<!-- Schlanker Header -->
<header class="staff-login-header">
<div class="staff-login-brand">
<?php if ($logo): ?>
<img src="<?= e($logo) ?>" alt="">
<?php endif; ?>
<?php if (!$logo || get_setting('logo_hide_name', '0') !== '1'): ?>
<span class="staff-login-brand-name"><?= e($displayName) ?></span>
<?php endif; ?>
</div>
<a href="/login.php" class="staff-login-back">← Zum Kundenportal</a>
</header>
<!-- Auth-Bereich -->
<div class="login-screen staff-login-screen">
<div class="auth-card login-card staff-login-card">
<h1 class="staff-login-title">Mitarbeiterbereich</h1>
<div class="sub">Anmeldung für Support-Mitarbeiter und Administratoren.</div>
<?php foreach ($errors as $err): ?>
<div class="flash error"><?= e($err) ?></div>
<?php endforeach; ?>
<?php foreach (get_flashes() as $f): ?>
<div class="flash <?= e($f['type']) ?>"><?= e($f['message']) ?></div>
<?php endforeach; ?>
<?php if ($need2fa): ?>
<p style="font-size:.88rem; color:#64748b; margin-bottom:14px">Bitte geben Sie den 6-stelligen Code aus Ihrer Authenticator-App ein.</p>
<form method="post">
<?= csrf_field() ?>
<input type="hidden" name="action" value="verify_totp">
<label for="totp_code">Authenticator-Code</label>
<input type="text" id="totp_code" name="totp_code" inputmode="numeric" pattern="\d{6}" maxlength="6" required autofocus placeholder="000000" style="letter-spacing:.2em; font-size:1.3rem; text-align:center">
<div class="btn-row">
<button type="submit" class="btn login-submit">Verifizieren</button>
</div>
</form>
<p style="margin-top:14px; font-size:.8rem; text-align:center"><a href="/staff/login.php">← Zurück zum Login</a></p>
<?php else: ?>
<form method="post">
<?= csrf_field() ?>
<input type="hidden" name="action" value="login">
<label for="email">E-Mail-Adresse</label>
<input type="email" id="email" name="email" required autofocus autocomplete="email" autocapitalize="none" autocorrect="off" spellcheck="false">
<label for="password">Passwort</label>
<input type="password" id="password" name="password" required autocomplete="current-password" autocapitalize="none" autocorrect="off" spellcheck="false">
<div style="text-align:right; margin-top:6px">
<a href="/staff/forgot_password.php" style="font-size:.8rem">Passwort vergessen?</a>
</div>
<div class="btn-row">
<button type="submit" class="btn login-submit">Anmelden</button>
</div>
</form>
<?php endif; ?>
</div>
</div>
<!-- Schlanker Footer -->
<footer class="staff-login-footer">
<span>Copyright © <?= date('Y') ?> <?= e($displayName) ?> – Alle Rechte vorbehalten.</span>
</footer>
<script>
// Hält die Sitzung der offenen Anmeldeseite am Leben: Der Server räumt ungenutzte Sitzungen nach etwa 24 Minuten auf, und dann wäre das
// Anmeldeformular ungültig. Ein kurzer Abruf alle 8 Minuten genügt.
setInterval(function () { fetch('/staff/session_ping.php?check=1', { credentials: 'same-origin', cache: 'no-store' }).catch(function () {}); }, 8 * 60 * 1000);
</script>
</body>
</html>