true]; $this->db = new \PDO(...$args); $this->setup(); } public static function get(): self { if (!isset(self::$instance)) { self::$instance = new self(); } self::$instance->setCookie(); return self::$instance; } protected function setup(): void { $this->db->exec( <<<'END' create table if not exists session ( token text primary key not null, identity text not null default 'N;', challenges text not null default 'N;' ); END ); } protected function domain(): string { return explode('.', $_SERVER['HTTP_HOST'], 2)[1]; } protected function setCookie(): void { if (isset($_COOKIE['PSSO_session'])) return; header( 'Set-Cookie: PSSO_session=' . $this->currentToken() . '; Domain=' . $this->domain() . '; HttpOnly' . '; Max-Age=' . static::$config['auth']['lifetime'] . '; Path=/' . '; SameSite=Lax' . '; Secure' ); } protected function currentToken(): string { return $this->token = $this->token ?? $_COOKIE['PSSO_session'] ?? base64_encode(random_bytes(129)); } protected function setColumn(string $column, mixed $data): void { $stmt = $this->db->prepare( "insert into session(token, $column) values (?, ?) " . "on conflict do update set $column=excluded.$column" ); $stmt->execute([$this->currentToken(), serialize($data)]); } public function getColumn(string $column): mixed { $stmt = $this->db->prepare("select $column from session where token=?"); $stmt->execute([$this->currentToken()]); return unserialize($stmt->fetchColumn(0)) ?: null; } public function setIdentity(mixed $identity): void { $this->setColumn('identity', $identity); } public function getIdentity(): mixed { return $this->getColumn('identity'); } public function setChallenges(mixed $challenges): void { $this->setColumn('challenges', $challenges); } public function getChallenges(): mixed { return $this->getColumn('challenges'); } }