Files

185 lines
5.1 KiB
PHP
Executable File

<?php
declare(strict_types=1);
require_once __DIR__ . '/db.php';
use App\Database\Database;
class MySqlSessionHandler implements SessionHandlerInterface
{
private ?PDO $connection = null;
private bool $ipMismatch = false;
public function open(string $savePath, string $sessionName): bool
{
try {
$database = new Database();
$this->connection = $database->getConnection();
return true;
} catch (Throwable $exception) {
return false;
}
}
public function close(): bool
{
$this->connection = null;
return true;
}
public function read(string $id): string
{
if ($this->connection === null) {
if (!$this->open('', '')) {
return '';
}
}
$statement = $this->connection->prepare(
'SELECT data, ip_address
FROM sessions
WHERE id = :id'
);
$statement->bindValue(':id', $id, PDO::PARAM_STR);
$statement->execute();
$row = $statement->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
return '';
}
$currentIp = $_SERVER['REMOTE_ADDR'] ?? '';
if (!empty($row['ip_address']) && $row['ip_address'] !== $currentIp) {
$this->ipMismatch = true;
return '';
}
$this->updateAccessAndIp($id, $currentIp);
return (string) $row['data'];
}
public function write(string $id, string $data): bool
{
if ($this->connection === null) {
if (!$this->open('', '')) {
return false;
}
}
$currentIp = $_SERVER['REMOTE_ADDR'] ?? '';
$access = time();
$userId = isset($_SESSION['user_id']) ? (int) $_SESSION['user_id'] : null;
$createdAt = date('Y-m-d H:i:s');
$statement = $this->connection->prepare(
'INSERT INTO sessions (id, access, data, user_id, ip_address, created_at)
VALUES (:id, :access, :data, :user_id, :ip_address, :created_at)
ON DUPLICATE KEY UPDATE
access = VALUES(access),
data = VALUES(data),
user_id = VALUES(user_id),
ip_address = VALUES(ip_address)'
);
$statement->bindValue(':id', $id, PDO::PARAM_STR);
$statement->bindValue(':access', $access, PDO::PARAM_INT);
$statement->bindValue(':data', $data, PDO::PARAM_STR);
if ($userId === null) {
$statement->bindValue(':user_id', null, PDO::PARAM_NULL);
} else {
$statement->bindValue(':user_id', $userId, PDO::PARAM_INT);
}
$statement->bindValue(':ip_address', $currentIp, PDO::PARAM_STR);
$statement->bindValue(':created_at', $createdAt, PDO::PARAM_STR);
try {
return $statement->execute();
} catch (Throwable $e) {
return false;
}
}
public function destroy(string $id): bool
{
if ($this->connection === null) {
return false;
}
$statement = $this->connection->prepare(
'DELETE FROM sessions WHERE id = :id'
);
$statement->bindValue(':id', $id, PDO::PARAM_STR);
return $statement->execute();
}
public function gc(int $maxLifetime): int|false
{
if ($this->connection === null) {
return false;
}
$statement = $this->connection->prepare(
'DELETE FROM sessions WHERE access < :time'
);
$statement->bindValue(':time', time() - $maxLifetime, PDO::PARAM_INT);
return $statement->execute();
}
public function validateIp(string $id): bool
{
if ($this->connection === null) {
return true;
}
$statement = $this->connection->prepare(
'SELECT ip_address FROM sessions WHERE id = :id'
);
$statement->bindValue(':id', $id, PDO::PARAM_STR);
$statement->execute();
$row = $statement->fetch(PDO::FETCH_ASSOC);
$currentIp = $_SERVER['REMOTE_ADDR'] ?? '';
return empty($row['ip_address']) || $row['ip_address'] === $currentIp;
}
public function hasIpMismatch(): bool
{
return $this->ipMismatch;
}
private function updateAccessAndIp(string $id, string $ip): void
{
$statement = $this->connection->prepare(
'UPDATE sessions
SET access = :access,
ip_address = :ip_address
WHERE id = :id'
);
$statement->bindValue(':access', time(), PDO::PARAM_INT);
$statement->bindValue(':ip_address', $ip, PDO::PARAM_STR);
$statement->bindValue(':id', $id, PDO::PARAM_STR);
$statement->execute();
}
}
$handler = new MySqlSessionHandler();
session_set_save_handler($handler, true);
session_start();
// Only enforce IP mismatch for authenticated sessions (user_id present)
if (!empty($_SESSION['user_id']) && $handler->hasIpMismatch()) {
session_unset();
session_destroy();
setcookie(session_name(), '', time() - 42000, '/');
header('Location: /index.php');
exit;
}