diff --git a/database/migrations/structure/20200613_1549_sessions.sql b/database/migrations/structure/20200613_1549_sessions.sql new file mode 100644 index 0000000..e568b0f --- /dev/null +++ b/database/migrations/structure/20200613_1549_sessions.sql @@ -0,0 +1,6 @@ +CREATE TABLE `sessions` ( + `id` varchar(64) NOT NULL, + `data` text NOT NULL, + `updated` timestamp NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; diff --git a/main.php b/main.php index 301c0e5..aeab442 100644 --- a/main.php +++ b/main.php @@ -10,21 +10,11 @@ const REVISION_DATE = ''; $dotenv = Dotenv\Dotenv::createImmutable(ROOT); $dotenv->load(); -if (!empty($_ENV['DEV'])) { - error_reporting(E_ALL); - - ini_set('display_errors', '1'); -} else { - ini_set('display_errors', '0'); -} - class Container { static MapGuesser\Interfaces\Database\IConnection $dbConnection; static MapGuesser\Routing\RouteCollection $routeCollection; + static \SessionHandlerInterface $sessionHandler; } Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']); -Container::$routeCollection = new MapGuesser\Routing\RouteCollection(); - -session_start(); diff --git a/public/index.php b/public/index.php index 7f8b3d1..ed4e391 100644 --- a/public/index.php +++ b/public/index.php @@ -1,8 +1,7 @@ get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']); -Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']); -Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']); -Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']); -Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']); -Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) { - $routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']); - $routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']); - $routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']); - $routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']); -}); -Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) { - $routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']); - $routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']); - $routeCollection->post('admin.saveMap', 'saveMap/{mapId}/json', [MapGuesser\Controller\MapAdminController::class, 'saveMap']); -}); - $match = Container::$routeCollection->match($method, explode('/', $url)); if ($match !== null) { diff --git a/src/Database/Query/Modify.php b/src/Database/Query/Modify.php index 6c84496..f731461 100755 --- a/src/Database/Query/Modify.php +++ b/src/Database/Query/Modify.php @@ -16,6 +16,8 @@ class Modify private array $original = []; + private ?string $externalId = null; + private bool $autoIncrement = true; public function __construct(IConnection $connection, string $table) @@ -31,6 +33,13 @@ class Modify return $this; } + public function setExternalId($id): Modify + { + $this->externalId = $id; + + return $this; + } + public function setAutoIncrement(bool $autoIncrement = true): Modify { $this->autoIncrement = $autoIncrement; @@ -87,7 +96,9 @@ class Modify private function insert(): void { - if (!$this->autoIncrement) { + if ($this->externalId !== null) { + $this->attributes[$this->idName] = $this->externalId; + } elseif (!$this->autoIncrement) { $this->attributes[$this->idName] = $this->generateKey(); } diff --git a/src/Session/DatabaseSessionHandler.php b/src/Session/DatabaseSessionHandler.php new file mode 100644 index 0000000..a50a668 --- /dev/null +++ b/src/Session/DatabaseSessionHandler.php @@ -0,0 +1,113 @@ +columns(['data']); + $select->whereId($id); + + $result = $select->execute()->fetch(IResultSet::FETCH_ASSOC); + + if ($result === null) { + return ''; + } + + $this->exists = true; + + return $result['data']; + } + + public function write($id, $data): bool + { + $modify = new Modify(\Container::$dbConnection, 'sessions'); + + if ($this->exists) { + $modify->setId($id); + } else { + $modify->setExternalId($id); + } + + $modify->set('data', $data); + $modify->set('updated', (new DateTime())->format('Y-m-d H:i:s')); + $modify->save(); + + $written = true; + + return true; + } + + public function destroy($id): bool + { + $modify = new Modify(\Container::$dbConnection, 'sessions'); + $modify->setId($id); + $modify->delete(); + + return true; + } + + public function gc($maxlifetime): int + { + $select = new Select(\Container::$dbConnection, 'sessions'); + $select->columns(['id']); + $select->where('updated', '<', (new DateTime('-' . $maxlifetime . ' seconds'))->format('Y-m-d H:i:s')); + + $result = $select->execute(); + + while ($session = $result->fetch(IResultSet::FETCH_ASSOC)) { + $modify = new Modify(\Container::$dbConnection, 'sessions'); + $modify->setId($session['id']); + $modify->delete(); + } + + return true; + } + + public function create_sid(): string + { + return hash('sha256', random_bytes(10) . microtime()); + } + + public function validateId($id): bool + { + return preg_match('/^[a-f0-9]{64}$/', $id); + } + + public function updateTimestamp($id, $data): bool + { + if ($this->written) { + return true; + } + + $modify = new Modify(\Container::$dbConnection, 'sessions'); + + $modify->setId($id); + $modify->set('updated', (new DateTime())->format('Y-m-d H:i:s')); + $modify->save(); + + return true; + } +} diff --git a/web.php b/web.php new file mode 100644 index 0000000..34eb015 --- /dev/null +++ b/web.php @@ -0,0 +1,40 @@ +get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']); +Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']); +Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']); +Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']); +Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']); +Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) { + $routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']); + $routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']); + $routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']); + $routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']); +}); +Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) { + $routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']); + $routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']); + $routeCollection->post('admin.saveMap', 'saveMap/{mapId}/json', [MapGuesser\Controller\MapAdminController::class, 'saveMap']); +}); + +Container::$sessionHandler = new MapGuesser\Session\DatabaseSessionHandler(); + +session_set_save_handler(Container::$sessionHandler, true); +session_start([ + 'gc_maxlifetime' => 604800, + 'cookie_lifetime' => 604800, + 'cookie_httponly' => true, + 'cookie_samesite' => 'Lax' +]);