Implement CSRF protection for user invitation and management actions; add QR code generation script; secure internal includes directory with .htaccess

This commit is contained in:
2026-09-11 14:46:08 +02:00
parent 55618ffaed
commit cf0ceeca82
18 changed files with 413 additions and 163 deletions
+32 -6
View File
@@ -42,13 +42,24 @@ if (isset($_GET['action']) && $_GET['action'] === 'restart') {
// POST Verwerking
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$db = get_db_connection();
$clientIp = fociz_get_client_ip();
$ipRateKey = "login_ip_{$clientIp}";
$ipLimit = fociz_check_rate_limit($ipRateKey, 10, 900);
// STAP 1: Wachtwoord verificatie
if ($step === 1) {
if (!verify_csrf_token($_POST['csrf_token'] ?? null)) {
$error = "Sessie verlopen of beveiligingstoken ongeldig. Ververs de pagina en probeer opnieuw.";
} elseif (!$ipLimit['allowed']) {
$error = "Te veel inlogpogingen vanaf dit netwerkadres. Probeer het over " . ceil($ipLimit['retry_after'] / 60) . " minuten opnieuw.";
} elseif ($step === 1) {
// STAP 1: Wachtwoord verificatie
$email = strtolower(trim($_POST['email'] ?? ''));
$password = $_POST['password'] ?? '';
$emailRateKey = "login_email_{$email}";
$emailLimit = fociz_check_rate_limit($emailRateKey, 5, 900);
if (empty($email) || empty($password)) {
if (!$emailLimit['allowed']) {
$error = "Dit account is tijdelijk geblokkeerd wegens te veel mislukte pogingen. Probeer het over " . ceil($emailLimit['retry_after'] / 60) . " minuten opnieuw.";
} elseif (empty($email) || empty($password)) {
$error = "Vul e-mailadres en wachtwoord in.";
} else {
$stmt = $db->prepare("SELECT id, name, email, password_hash, two_factor_secret, two_factor_enabled FROM users WHERE email = ?");
@@ -56,8 +67,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
fociz_clear_rate_limit($emailRateKey);
// Controleer of de inloglocatie/IP bekend is
$clientIp = fociz_get_client_ip();
$isIpKnown = fociz_is_ip_known_for_user((int)$user['id'], $clientIp);
if (!$isIpKnown) {
@@ -72,6 +83,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$step = 2;
}
} else {
fociz_hit_rate_limit($ipRateKey, 900);
if (!empty($email)) {
fociz_hit_rate_limit($emailRateKey, 900);
}
$error = "Onjuist e-mailadres of wachtwoord.";
}
}
@@ -86,7 +101,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
exit;
}
if (empty($totpCode)) {
$totpRateKey = "login_totp_{$userId}";
$totpLimit = fociz_check_rate_limit($totpRateKey, 5, 900);
if (!$totpLimit['allowed']) {
$error = "Te veel ongeldige verificatiecodes. Probeer het over " . ceil($totpLimit['retry_after'] / 60) . " minuten opnieuw.";
} elseif (empty($totpCode)) {
$error = "Vul de 6-cijferige 2FA code in uit uw authenticator app.";
} else {
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
@@ -120,8 +140,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
if ($isValid) {
fociz_clear_rate_limit($totpRateKey);
fociz_clear_rate_limit($ipRateKey);
// Volledige login succesvol (geldig tot 23:59:59 van vandaag)
$clientIp = fociz_get_client_ip();
fociz_register_user_ip((int)$user['id'], $clientIp, true);
$_SESSION['user_id'] = $user['id'];
@@ -142,6 +164,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Location: /planner.php');
exit;
} else {
fociz_hit_rate_limit($totpRateKey, 900);
fociz_hit_rate_limit($ipRateKey, 900);
$error = "Onjuiste 2FA code. Controleer uw authenticator app en probeer opnieuw.";
}
}
@@ -229,6 +253,7 @@ $pageTitle = "Fociz — Inloggen";
<?php if ($step === 1): ?>
<!-- STAP 1: E-mail & Wachtwoord -->
<form method="POST" action="/auth/login.php" class="space-y-4">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">E-mailadres</label>
<div class="relative">
@@ -256,6 +281,7 @@ $pageTitle = "Fociz — Inloggen";
<!-- STAP 2: 2FA TOTP Code -->
<form method="POST" action="/auth/login.php" class="space-y-5">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
<div class="text-center">
<div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 mb-2">
<i data-lucide="key" class="w-6 h-6"></i>