feat(security): implement SOC 2 & ISO 27001 audit logging, password policy, and session timeout

This commit is contained in:
2026-09-11 15:35:47 +02:00
parent 88ec1418cf
commit 0942a94edd
12 changed files with 283 additions and 14 deletions
+4 -2
View File
@@ -39,8 +39,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($name) || empty($password) || empty($totpCode)) {
$error = "Alle velden (inclusief 2FA verificatiecode) zijn verplicht.";
} elseif (strlen($password) < 8) {
$error = "Het wachtwoord moet minimaal 8 tekens lang zijn.";
} elseif (($pwErr = fociz_validate_password($password)) !== null) {
$error = $pwErr;
} elseif ($password !== $confirmPassword) {
$error = "Wachtwoorden komen niet overeen.";
} elseif (!TOTP::verifyCode($secret, $totpCode)) {
@@ -75,6 +75,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$db->commit();
fociz_log_audit($newUserId, 'USER_REGISTERED_VIA_INVITE', 'user', $newUserId, ['email' => $invite['email'], 'role' => $invite['role']], 'SUCCESS', $invite['email']);
// Start dagsessie (geldig tot 23:59:59)
$_SESSION['user_id'] = $newUserId;
$_SESSION['user_name'] = $name;
+5
View File
@@ -74,6 +74,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$isIpKnown) {
// Stuur verificatie-mail en informeer gebruiker
fociz_send_new_ip_verification((int)$user['id'], $user['email'], $user['name'], $clientIp);
fociz_log_audit((int)$user['id'], 'LOGIN_NEW_IP_CHALLENGE', 'user', (int)$user['id'], ['ip' => $clientIp], 'PENDING', $user['email']);
$error = "Nieuwe inloglocatie gedetecteerd ({$clientIp}). Er is een verificatie-e-mail gestuurd naar {$user['email']}. Klik op de link in de e-mail om deze locatie toe te staan.";
} else {
// Succesvol stap 1: zet tijdelijke pending sessie voor 2FA
@@ -87,6 +88,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($email)) {
fociz_hit_rate_limit($emailRateKey, 900);
}
fociz_log_audit($user ? (int)$user['id'] : null, 'LOGIN_FAILED', 'user', $user ? (int)$user['id'] : null, ['reason' => 'Invalid credentials', 'ip' => $clientIp], 'FAILURE', $email);
$error = "Onjuist e-mailadres of wachtwoord.";
}
}
@@ -159,6 +161,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Dagsessie-token uitgeven (strikt geldig tot 23:59:59 van vandaag)
issue_day_session_token((int)$user['id']);
fociz_log_audit((int)$user['id'], 'LOGIN_SUCCESS', 'user', (int)$user['id'], ['ip' => $clientIp, 'auth_method' => 'PASSWORD+2FA'], 'SUCCESS', $user['email']);
unset($_SESSION['pending_2fa_user_id'], $_SESSION['pending_2fa_user_email'], $_SESSION['pending_2fa_user_name']);
header('Location: /planner.php');
@@ -166,6 +170,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} else {
fociz_hit_rate_limit($totpRateKey, 900);
fociz_hit_rate_limit($ipRateKey, 900);
fociz_log_audit((int)$userId, 'LOGIN_MFA_FAILED', 'user', (int)$userId, ['ip' => $clientIp], 'FAILURE', $_SESSION['pending_2fa_user_email'] ?? null);
$error = "Onjuiste 2FA code. Controleer uw authenticator app en probeer opnieuw.";
}
}
+4 -2
View File
@@ -37,8 +37,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $resetRecord) {
if (empty($password)) {
$error = "Vul een nieuw wachtwoord in.";
} elseif (strlen($password) < 8) {
$error = "Het wachtwoord moet minimaal 8 tekens lang zijn.";
} elseif (($pwErr = fociz_validate_password($password)) !== null) {
$error = $pwErr;
} elseif ($password !== $confirmPassword) {
$error = "De wachtwoorden komen niet overeen.";
} else {
@@ -62,6 +62,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $resetRecord) {
$db->commit();
$success = true;
fociz_log_audit(null, 'PASSWORD_RESET_COMPLETED', 'user', null, ['email' => $resetRecord['user_email']], 'SUCCESS', $resetRecord['user_email']);
} catch (Throwable $e) {
if ($db->inTransaction()) $db->rollBack();
error_log("Fout bij resetten wachtwoord: " . $e->getMessage());
+4 -2
View File
@@ -35,8 +35,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = "Alle velden (inclusief de 2FA verificatiecode) zijn verplicht.";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = "Voer een geldig e-mailadres in.";
} elseif (strlen($password) < 8) {
$error = "Het wachtwoord moet minimaal 8 tekens bevatten.";
} elseif (($pwErr = fociz_validate_password($password)) !== null) {
$error = $pwErr;
} elseif ($password !== $confirmPassword) {
$error = "De ingevoerde wachtwoorden komen niet overeen.";
} elseif (!TOTP::verifyCode($secret, $totpCode)) {
@@ -73,6 +73,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
clear_remember_cookie();
unset($_SESSION['setup_totp_secret']);
fociz_log_audit($adminId, 'INITIAL_ADMIN_SETUP', 'user', $adminId, ['email' => $email, 'name' => $name], 'SUCCESS', $email);
$success = true;
} catch (Exception $e) {
$error = "Fout bij aanmaken beheerder in MariaDB: " . $e->getMessage();
+2 -1
View File
@@ -1,4 +1,4 @@
<?php
<?php
/**
* Fociz Nieuwe Inloglocatie / IP Verificatie
*/
@@ -43,6 +43,7 @@ if (empty($token) || !preg_match('/^[a-f0-9]{64}$/', $token)) {
$success = true;
$userEmail = $record['email'];
$ipAddress = $record['ip_address'];
fociz_log_audit((int)$record['user_id'], 'IP_VERIFIED', 'user_known_ips', (int)$record['id'], ['ip' => $ipAddress], 'SUCCESS', $userEmail);
}
} catch (Throwable $e) {
error_log("verify-ip.php error: " . $e->getMessage());