v-wordpress-plugin-updater/v-update-api/app/Core/SessionManager.php
2026-04-06 09:47:05 -04:00

181 lines
4.7 KiB
PHP

<?php
// phpcs:ignoreFile PSR1.Files.SideEffects.FoundWithSymbols
/**
* Project: UpdateAPI
* Author: Vontainment <services@vontainment.com>
* License: https://opensource.org/licenses/MIT MIT License
* Link: https://vontainment.com
* Version: 4.5.0
*
* File: SessionManager.php
* Description: Centralized session management
*/
namespace App\Core;
use App\Models\BlacklistModel;
use App\Core\ErrorManager;
class SessionManager
{
/**
* Singleton instance of the SessionManager.
*/
private static ?SessionManager $instance = null;
/**
* Private constructor to prevent direct instantiation.
*/
private function __construct()
{
}
/**
* Retrieve the singleton instance.
*
* @return SessionManager
*/
public static function getInstance(): SessionManager
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Start the session with secure cookie parameters.
*
* @return void
*/
public function start(): void
{
$secureFlag = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
session_set_cookie_params([
'path' => '/',
'httponly' => true,
'secure' => $secureFlag,
'samesite' => 'Lax',
]);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
}
/**
* Destroy the current session and its data.
*
* @return void
*/
public function destroy(): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
$params = session_get_cookie_params();
setcookie(session_name(), '', [
'expires' => time() - 42000,
'path' => $params['path'] ?? '/',
'domain' => $params['domain'] ?? '',
'secure' => (bool) ($params['secure'] ?? false),
'httponly' => (bool) ($params['httponly'] ?? true),
'samesite' => $params['samesite'] ?? 'Lax',
]);
$_SESSION = [];
session_destroy();
}
}
/**
* Regenerate the session ID to prevent fixation.
*
* @return void
*/
public function regenerate(): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
session_regenerate_id(true);
}
}
/**
* Retrieve a value from the session.
*
* @param string $key Session key to retrieve.
* @param mixed $default Default value if key does not exist.
* @return mixed
*/
public function get(string $key, mixed $default = null): mixed
{
return $_SESSION[$key] ?? $default;
}
/**
* Set a session value.
*
* @param string $key Session key to set.
* @param mixed $value Value to store.
* @return void
*/
public function set(string $key, mixed $value): void
{
$_SESSION[$key] = $value;
}
/**
* Validate the current session and update timeout.
*
* @return bool True if the session is valid and user is logged in
*/
public function isValid(): bool
{
$timeoutLimit = defined('SESSION_TIMEOUT_LIMIT') ? SESSION_TIMEOUT_LIMIT : 1800;
$timeout = $this->get('timeout');
$timeoutExceeded = is_int($timeout) && (time() - $timeout > $timeoutLimit);
$userAgent = $this->get('user_agent');
$userAgentChanged = is_string($userAgent) && $userAgent !== ($_SERVER['HTTP_USER_AGENT'] ?? '');
if ($timeoutExceeded || $userAgentChanged) {
$this->destroy();
return false;
}
$this->set('timeout', time());
return $this->get('logged_in') === true;
}
/**
* Enforce that the current request comes from an authenticated user.
*
* Returns true when the session is valid and the user is authenticated.
* Returns false when the session is invalid (caller should redirect to login).
*
* @return bool True if authenticated, false otherwise.
*/
public function requireAuth(): bool
{
return $this->isValid();
}
/**
* Determine whether the current request originates from a blacklisted IP.
*
* @return bool True when remote IP is valid and blacklisted.
*/
public function isBlacklistedRequest(): bool
{
$ip = filter_var($_SERVER['REMOTE_ADDR'] ?? '', FILTER_VALIDATE_IP);
if (!$ip) {
return false;
}
if (BlacklistModel::isBlacklisted($ip)) {
ErrorManager::getInstance()->log("Blacklisted IP attempted access: $ip", 'error');
return true;
}
return false;
}
}