mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 01:53:03 +02:00
feat: enforce idle + absolute timeouts on admin session
This commit is contained in:
+48
-1
@@ -19,6 +19,12 @@ class AdminAuth
|
||||
private const MAX_ATTEMPTS = 5;
|
||||
private const COOLDOWN_MINUTES = 15;
|
||||
|
||||
// Session lifetime (server-side): idle timeout and absolute timeout.
|
||||
// Aligned with OWASP ASVS / NIST 800-63B guidance for admin panels.
|
||||
private const IDLE_TIMEOUT_SECONDS = 1800; // 30 min with no activity
|
||||
private const ABSOLUTE_TIMEOUT_SECONDS = 43200; // 12 h regardless of activity
|
||||
private const COOKIE_LIFETIME_SECONDS = 604800; // 7 d — upper bound only
|
||||
|
||||
/** Test hook: override the Database connection (e.g. temp DB in smoke tests). */
|
||||
private static ?Database $dbOverride = null;
|
||||
|
||||
@@ -53,7 +59,7 @@ class AdminAuth
|
||||
}
|
||||
// Harden session cookie (item #8)
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 604800,
|
||||
'lifetime' => self::COOKIE_LIFETIME_SECONDS,
|
||||
'path' => '/admin',
|
||||
'secure' => (php_sapi_name() !== 'cli-server'),
|
||||
'httponly' => true,
|
||||
@@ -62,6 +68,44 @@ class AdminAuth
|
||||
session_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce server-side idle + absolute timeouts on an authenticated session.
|
||||
*
|
||||
* Called on every gated request. If the session has been idle longer than
|
||||
* IDLE_TIMEOUT_SECONDS, or has existed longer than ABSOLUTE_TIMEOUT_SECONDS
|
||||
* since login, the session is destroyed and the caller is redirected.
|
||||
*
|
||||
* Only acts on authenticated sessions; leaves unauthenticated sessions
|
||||
* (including throttle counters) untouched.
|
||||
*/
|
||||
private static function enforceSessionTimeout(): void
|
||||
{
|
||||
if (empty($_SESSION[self::SESSION_KEY])) {
|
||||
return; // Not authenticated — nothing to time out.
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$loginAt = (int) ($_SESSION['admin_login_at'] ?? $now);
|
||||
$activity = (int) ($_SESSION['admin_last_activity'] ?? $loginAt);
|
||||
|
||||
$idle = $now - $activity;
|
||||
$absolute = $now - $loginAt;
|
||||
|
||||
if ($idle > self::IDLE_TIMEOUT_SECONDS || $absolute > self::ABSOLUTE_TIMEOUT_SECONDS) {
|
||||
self::logout();
|
||||
header('Location: ' . self::LOGIN_URL);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Rotate the session ID periodically to limit fixation/replay window.
|
||||
if ($absolute > 0 && $absolute >= self::IDLE_TIMEOUT_SECONDS && $absolute % self::IDLE_TIMEOUT_SECONDS === 0) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
|
||||
// Refresh the activity timestamp on every authenticated request.
|
||||
$_SESSION['admin_last_activity'] = $now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the admin password hash from site_settings.
|
||||
* Returns null if not set (dev mode).
|
||||
@@ -90,6 +134,7 @@ class AdminAuth
|
||||
public static function requireLogin(): void
|
||||
{
|
||||
self::startSession();
|
||||
self::enforceSessionTimeout();
|
||||
$storedHash = self::getStoredHash();
|
||||
if ($storedHash === null) {
|
||||
return; // No password configured → dev / cli-server, skip.
|
||||
@@ -156,6 +201,7 @@ class AdminAuth
|
||||
session_regenerate_id(true);
|
||||
$_SESSION[self::SESSION_KEY] = true;
|
||||
$_SESSION['admin_login_at'] = time();
|
||||
$_SESSION['admin_last_activity'] = time();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -284,6 +330,7 @@ HTML;
|
||||
public static function isAuthenticated(): bool
|
||||
{
|
||||
self::startSession();
|
||||
self::enforceSessionTimeout();
|
||||
$storedHash = self::getStoredHash();
|
||||
if ($storedHash === null) {
|
||||
return true; // No password configured → dev mode.
|
||||
|
||||
Reference in New Issue
Block a user