| 
									
										
										
										
											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']) | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |