Security
This commit is contained in:
+104
-2
@@ -223,16 +223,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} elseif ($newPassword !== $confirmPassword) {
|
||||
$message = "Het nieuwe wachtwoord en de bevestiging komen niet overeen.";
|
||||
$messageType = 'error';
|
||||
} else {
|
||||
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
$stmtUp = $db->prepare("UPDATE users SET password_hash = ? WHERE id = ?");
|
||||
$stmtUp->execute([$newHash, $userId]);
|
||||
|
||||
// SOC 2 CC6.1 / ISO 27001 A.9.4.2: Trek overige sessies in bij wachtwoordwijziging
|
||||
fociz_revoke_other_sessions($userId, session_id());
|
||||
|
||||
fociz_log_audit($userId, 'PASSWORD_CHANGED', 'user', $userId, ['method' => 'profile'], 'SUCCESS', $user['email']);
|
||||
$message = "Uw wachtwoord is succesvol gewijzigd.";
|
||||
$message = "Uw wachtwoord is succesvol gewijzigd. Alle overige sessies op andere apparaten zijn uit veiligheidsoverwegingen beëindigd.";
|
||||
$messageType = 'success';
|
||||
}
|
||||
}
|
||||
|
||||
// 7b. Alle overige sessies op afstand beëindigen (SOC 2 / ISO 27001)
|
||||
elseif ($action === 'revoke_other_sessions') {
|
||||
$count = fociz_revoke_other_sessions($userId, session_id());
|
||||
$message = "Succesvol {$count} andere actieve sessie(s) op afstand beëindigd.";
|
||||
$messageType = 'success';
|
||||
}
|
||||
|
||||
// 8. Standaard avatar kiezen uit bibliotheek
|
||||
elseif ($action === 'select_preset_avatar') {
|
||||
$preset = trim($_POST['avatar_preset'] ?? '');
|
||||
@@ -331,6 +341,9 @@ $userAiRowSettings = json_decode($userAiIntRow['settings'] ?? '{}', true) ?: [];
|
||||
// Thema ophalen
|
||||
$userTheme = get_user_theme($userId);
|
||||
|
||||
// Actieve apparaten & sessies ophalen (SOC 2 / ISO 27001)
|
||||
$activeSessions = fociz_get_user_active_sessions($userId, session_id());
|
||||
|
||||
$pageTitle = 'Mijn Profiel & Koppelingen — Fociz';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
@@ -1255,6 +1268,72 @@ require_once __DIR__ . '/includes/header.php';
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actieve Apparaten & Sessies Card (SOC 2 CC6.1 / ISO 27001 A.9.4.2) -->
|
||||
<div class="fociz-card p-6 sm:p-7 space-y-5">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-5 border-b border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center space-x-3.5">
|
||||
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs flex-shrink-0">
|
||||
<i data-lucide="laptop" class="w-6 h-6"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Actieve Apparaten & Sessies</h3>
|
||||
<span class="px-2 py-0.5 rounded-full bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-200 dark:border-emerald-800/60 text-emerald-700 dark:text-emerald-300 font-semibold text-[10px]">
|
||||
<?= count($activeSessions) ?> <?= count($activeSessions) === 1 ? 'actieve sessie' : 'actieve sessies' ?>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
|
||||
Centraal overzicht van alle actieve inlogapparaten conform ISO 27001 en SOC 2 richtlijnen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (count($activeSessions) > 1): ?>
|
||||
<form method="POST" action="/profile.php" onsubmit="return confirm('Weet u zeker dat u alle andere actieve sessies op afstand wilt beëindigen? U blijft op dit apparaat ingelogd.')">
|
||||
<input type="hidden" name="action" value="revoke_other_sessions">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
|
||||
<button type="submit" class="inline-flex items-center space-x-2 px-3.5 py-2 rounded-xl bg-rose-50 hover:bg-rose-100 dark:bg-rose-950/30 dark:hover:bg-rose-900/40 text-rose-600 dark:text-rose-400 border border-rose-200 dark:border-rose-800/60 text-xs font-semibold transition cursor-pointer">
|
||||
<i data-lucide="log-out" class="w-3.5 h-3.5"></i>
|
||||
<span>Overal Uitloggen (Overige Sessies)</span>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2.5">
|
||||
<?php foreach ($activeSessions as $s): ?>
|
||||
<div class="p-3.5 rounded-2xl <?= $s['is_current'] ? 'bg-indigo-50/40 dark:bg-indigo-950/20 border-indigo-200/80 dark:border-indigo-800/50' : 'bg-slate-50/70 dark:bg-[#0c0f17]/60 border-slate-200 dark:border-slate-800' ?> border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-9 h-9 rounded-xl <?= $s['is_current'] ? 'bg-indigo-600 text-white shadow-xs' : 'bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400' ?> flex items-center justify-center flex-shrink-0">
|
||||
<i data-lucide="<?= stripos($s['device_name'] ?? '', 'Phone') !== false || stripos($s['device_name'] ?? '', 'iPhone') !== false || stripos($s['device_name'] ?? '', 'Android') !== false ? 'smartphone' : 'monitor' ?>" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-xs font-bold text-slate-900 dark:text-white"><?= htmlspecialchars($s['device_name'] ?: 'Onbekend apparaat') ?></span>
|
||||
<?php if ($s['is_current']): ?>
|
||||
<span class="px-2 py-0.5 rounded-md bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 border border-emerald-200 dark:border-emerald-800/60 text-[10px] font-bold">
|
||||
Dit apparaat (Huidig)
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="text-[11px] text-slate-500 dark:text-slate-400 flex items-center space-x-2 mt-0.5">
|
||||
<span class="font-mono"><?= htmlspecialchars($s['ip_address']) ?></span>
|
||||
<span>•</span>
|
||||
<span>Laatst actief: <?= date('d-m-Y H:i', strtotime($s['last_activity_at'])) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!$s['is_current']): ?>
|
||||
<button type="button" onclick="revokeSessionById(<?= (int)$s['id'] ?>)" class="self-end sm:self-center px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-rose-50 dark:bg-slate-800 dark:hover:bg-rose-950/30 text-slate-600 hover:text-rose-600 dark:text-slate-400 dark:hover:text-rose-400 text-xs font-semibold transition border border-slate-200 dark:border-slate-700 hover:border-rose-200 dark:hover:border-rose-800 cursor-pointer">
|
||||
Sessie Beëindigen
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -1443,6 +1522,29 @@ async function testPersonalAiConnection() {
|
||||
btnText.innerText = 'Test Verbinding';
|
||||
}
|
||||
}
|
||||
|
||||
async function revokeSessionById(sessionId) {
|
||||
if (!confirm('Weet u zeker dat u deze sessie op afstand wilt beëindigen?')) return;
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'revoke_session');
|
||||
formData.append('session_id', sessionId);
|
||||
formData.append('csrf_token', '<?= htmlspecialchars(get_csrf_token()) ?>');
|
||||
|
||||
const res = await fetch('/api/session.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(data.error || 'Fout bij beëindigen van sessie.');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Netwerkfout bij beëindigen van sessie: ' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
|
||||
Reference in New Issue
Block a user