2025-04-13 12:18:53 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Session Class
|
|
|
|
*
|
|
|
|
* Core session management functionality for the application
|
|
|
|
*/
|
|
|
|
class Session {
|
2025-06-08 08:52:53 +00:00
|
|
|
private static $initialized = false;
|
|
|
|
private static $sessionName = ''; // Will be set from config, if not we'll have a random session name
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a random session name
|
|
|
|
*/
|
|
|
|
private static function generateRandomSessionName(): string {
|
|
|
|
return 'sess_' . bin2hex(random_bytes(8)); // 16-character random string
|
|
|
|
}
|
2025-04-13 12:18:53 +00:00
|
|
|
private static $sessionOptions = [
|
|
|
|
'cookie_httponly' => 1,
|
|
|
|
'cookie_secure' => 1,
|
|
|
|
'cookie_samesite' => 'Strict',
|
|
|
|
'gc_maxlifetime' => 7200 // 2 hours
|
|
|
|
];
|
|
|
|
|
2025-06-08 08:52:53 +00:00
|
|
|
/**
|
|
|
|
* Initialize session configuration
|
|
|
|
*/
|
|
|
|
private static function initialize() {
|
|
|
|
if (self::$initialized) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
global $config;
|
|
|
|
|
2025-06-11 09:18:48 +00:00
|
|
|
// Get session name from config or generate a random one
|
|
|
|
self::$sessionName = $config['session']['name'] ?? self::generateRandomSessionName();
|
|
|
|
|
|
|
|
// Set session name before starting the session
|
|
|
|
session_name(self::$sessionName);
|
|
|
|
|
|
|
|
// Set session cookie parameters
|
|
|
|
$thisPath = $config['folder'] ?? '/';
|
|
|
|
$thisDomain = $config['domain'] ?? '';
|
|
|
|
$isSecure = isset($_SERVER['HTTPS']);
|
|
|
|
|
|
|
|
session_set_cookie_params([
|
|
|
|
'lifetime' => 0, // Session cookie (browser session)
|
|
|
|
'path' => $thisPath,
|
|
|
|
'domain' => $thisDomain,
|
|
|
|
'secure' => $isSecure,
|
|
|
|
'httponly' => true,
|
|
|
|
'samesite' => 'Strict'
|
|
|
|
]);
|
2025-06-08 08:52:53 +00:00
|
|
|
|
2025-06-11 09:18:48 +00:00
|
|
|
self::$initialized = true;
|
|
|
|
}
|
2025-06-08 08:52:53 +00:00
|
|
|
|
2025-06-11 09:18:48 +00:00
|
|
|
/**
|
|
|
|
* Get session name from config or generate a random one
|
|
|
|
*/
|
|
|
|
private static function getSessionNameFromConfig($config) {
|
|
|
|
if (isset($config['session']['name']) && !empty($config['session']['name'])) {
|
|
|
|
return $config['session']['name'];
|
2025-06-08 08:52:53 +00:00
|
|
|
}
|
2025-06-11 09:18:48 +00:00
|
|
|
return self::generateRandomSessionName();
|
2025-06-08 08:52:53 +00:00
|
|
|
}
|
|
|
|
|
2025-04-13 12:18:53 +00:00
|
|
|
/**
|
|
|
|
* Start or resume a session with secure options
|
|
|
|
*/
|
|
|
|
public static function startSession() {
|
2025-06-08 08:52:53 +00:00
|
|
|
self::initialize();
|
|
|
|
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
2025-06-11 09:18:48 +00:00
|
|
|
if (!headers_sent()) {
|
|
|
|
session_start(self::$sessionOptions);
|
|
|
|
}
|
2025-04-13 12:18:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy current session and clean up
|
|
|
|
*/
|
|
|
|
public static function destroySession() {
|
|
|
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
|
|
|
session_unset();
|
|
|
|
session_destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get current username if set
|
|
|
|
*/
|
|
|
|
public static function getUsername() {
|
|
|
|
return isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get current user ID if set
|
|
|
|
*/
|
|
|
|
public static function getUserId() {
|
|
|
|
return isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if current session is valid
|
2025-06-11 09:18:48 +00:00
|
|
|
*
|
|
|
|
* @param bool $strict If true, will return false for new/unauthenticated sessions
|
|
|
|
* @return bool True if session is valid, false otherwise
|
2025-04-13 12:18:53 +00:00
|
|
|
*/
|
2025-06-11 09:18:48 +00:00
|
|
|
public static function isValidSession($strict = true) {
|
|
|
|
// If session is not started or empty, it's not valid
|
|
|
|
if (session_status() !== PHP_SESSION_ACTIVE || empty($_SESSION)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In non-strict mode, consider empty session as valid (for login/logout)
|
|
|
|
if (!$strict && !isset($_SESSION['user_id']) && !isset($_SESSION['username'])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In strict mode, require user_id and username
|
|
|
|
if ($strict && (!isset($_SESSION['user_id']) || !isset($_SESSION['username']))) {
|
2025-04-13 12:18:53 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check session timeout
|
|
|
|
$session_timeout = isset($_SESSION['REMEMBER_ME']) ? (30 * 24 * 60 * 60) : 7200; // 30 days or 2 hours
|
|
|
|
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $session_timeout)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update last activity time
|
|
|
|
$_SESSION['LAST_ACTIVITY'] = time();
|
|
|
|
|
|
|
|
// Regenerate session ID periodically (every 30 minutes)
|
|
|
|
if (!isset($_SESSION['CREATED'])) {
|
|
|
|
$_SESSION['CREATED'] = time();
|
|
|
|
} else if (time() - $_SESSION['CREATED'] > 1800) {
|
|
|
|
// Regenerate session ID and update creation time
|
|
|
|
if (!headers_sent() && session_status() === PHP_SESSION_ACTIVE) {
|
|
|
|
$oldData = $_SESSION;
|
|
|
|
session_regenerate_id(true);
|
|
|
|
$_SESSION = $oldData;
|
|
|
|
$_SESSION['CREATED'] = time();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set remember me option for extended session
|
|
|
|
*/
|
|
|
|
public static function setRememberMe($value = true) {
|
|
|
|
$_SESSION['REMEMBER_ME'] = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear session data and cookies
|
|
|
|
*/
|
|
|
|
public static function cleanup($config) {
|
|
|
|
self::destroySession();
|
|
|
|
|
|
|
|
// Clear cookies if headers not sent
|
|
|
|
if (!headers_sent()) {
|
|
|
|
setcookie('username', '', [
|
|
|
|
'expires' => time() - 3600,
|
|
|
|
'path' => $config['folder'],
|
|
|
|
'domain' => $config['domain'],
|
|
|
|
'secure' => isset($_SERVER['HTTPS']),
|
|
|
|
'httponly' => true,
|
|
|
|
'samesite' => 'Strict'
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start fresh session
|
|
|
|
self::startSession();
|
2025-04-14 12:31:19 +00:00
|
|
|
|
|
|
|
// Reset session timeout flag
|
|
|
|
unset($_SESSION['session_timeout_shown']);
|
2025-04-13 12:18:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new authenticated session for a user
|
|
|
|
*/
|
|
|
|
public static function createAuthSession($userId, $username, $rememberMe, $config) {
|
2025-06-11 09:18:48 +00:00
|
|
|
// Ensure session is started
|
|
|
|
self::startSession();
|
2025-04-13 12:18:53 +00:00
|
|
|
|
|
|
|
// Set session variables
|
|
|
|
$_SESSION['user_id'] = $userId;
|
|
|
|
$_SESSION['username'] = $username;
|
|
|
|
$_SESSION['LAST_ACTIVITY'] = time();
|
2025-06-11 09:18:48 +00:00
|
|
|
$_SESSION['REMEMBER_ME'] = $rememberMe;
|
|
|
|
|
|
|
|
// Set cookie lifetime based on remember me
|
|
|
|
$cookieLifetime = $rememberMe ? time() + (30 * 24 * 60 * 60) : 0;
|
|
|
|
|
|
|
|
// Update session cookie with remember me setting
|
|
|
|
if (!headers_sent()) {
|
|
|
|
setcookie(
|
|
|
|
session_name(),
|
|
|
|
session_id(),
|
|
|
|
[
|
|
|
|
'expires' => $cookieLifetime,
|
|
|
|
'path' => $config['folder'] ?? '/',
|
|
|
|
'domain' => $config['domain'] ?? '',
|
|
|
|
'secure' => isset($_SERVER['HTTPS']),
|
|
|
|
'httponly' => true,
|
|
|
|
'samesite' => 'Strict'
|
|
|
|
]
|
|
|
|
);
|
|
|
|
|
|
|
|
// Set username cookie
|
|
|
|
setcookie('username', $username, [
|
|
|
|
'expires' => $cookieLifetime,
|
|
|
|
'path' => $config['folder'] ?? '/',
|
|
|
|
'domain' => $config['domain'] ?? '',
|
|
|
|
'secure' => isset($_SERVER['HTTPS']),
|
|
|
|
'httponly' => true,
|
|
|
|
'samesite' => 'Strict'
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2025-04-13 12:18:53 +00:00
|
|
|
if ($rememberMe) {
|
|
|
|
self::setRememberMe(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Store 2FA pending information in session
|
|
|
|
*/
|
|
|
|
public static function store2FAPending($userId, $username, $rememberMe = false) {
|
|
|
|
$_SESSION['2fa_pending_user_id'] = $userId;
|
|
|
|
$_SESSION['2fa_pending_username'] = $username;
|
|
|
|
if ($rememberMe) {
|
|
|
|
$_SESSION['2fa_pending_remember'] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear 2FA pending information from session
|
|
|
|
*/
|
|
|
|
public static function clear2FAPending() {
|
|
|
|
unset($_SESSION['2fa_pending_user_id']);
|
|
|
|
unset($_SESSION['2fa_pending_username']);
|
|
|
|
unset($_SESSION['2fa_pending_remember']);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get 2FA pending information
|
|
|
|
*/
|
|
|
|
public static function get2FAPending() {
|
|
|
|
if (!isset($_SESSION['2fa_pending_user_id']) || !isset($_SESSION['2fa_pending_username'])) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
|
|
'user_id' => $_SESSION['2fa_pending_user_id'],
|
|
|
|
'username' => $_SESSION['2fa_pending_username'],
|
|
|
|
'remember_me' => isset($_SESSION['2fa_pending_remember'])
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|