159 lines
7.1 KiB
PHP
159 lines
7.1 KiB
PHP
<?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>
|