185 lines
5.1 KiB
PHP
Executable File
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;
|
|
} |