session_token); } public static function unsetInstance(): void { self::$instance = null; } public function __construct() { } public function getUserId(): int { return $this->user_id ?? 0; } public function getUser(): User { return User::byId($this->getUserId()); } public function getToken(): string { return $this->session_token; } public function getSmallToken(int $rounds = 5, int $length = 8, int $offset = 0): string { $token = $this->getToken(); $tokenLength = strlen($token) - $length; for($i = 0; $i < $rounds; $i++) $offset = ord($token[$offset]) % $tokenLength; return str_rot13(substr($token, $offset, $length)); } public function getCreated(): int { return $this->session_created ?? 0; } public function getExpires(): int { return $this->session_expires ?? 0; } public function getBump(): bool { return $this->session_bump ?? false; } public function getFirstIp(): string { return $this->session_ip_first ?? ''; } public function getLastIp(): ?string { return $this->session_ip_last ?? null; } public function update(): void { $update = DB::prepare(' UPDATE `ytkns_users_sessions` SET `session_expires` = IF(:bump, NOW() + INTERVAL 1 MONTH, `session_expires`), `session_ip_last` = INET6_ATON(:ip), `session_used` = NOW() WHERE `session_token` = :token '); $update->bindValue('bump', $this->getBump() ? 1 : 0); $update->bindValue('ip', $_SERVER['REMOTE_ADDR']); $update->bindValue('token', $this->getToken()); $update->execute(); } public function destroy(): void { $destroy = DB::prepare(' DELETE FROM `ytkns_users_sessions` WHERE `session_token` = :token '); $destroy->bindValue('token', $this->getToken()); $destroy->execute(); } public static function generateToken(int $length = 64): string { $token = random_bytes($length); $chars = strlen(self::TOKEN_CHARS); for($i = 0; $i < $length; $i++) $token[$i] = self::TOKEN_CHARS[ord($token[$i]) % $chars]; return $token; } public static function create(User $user, bool $bump = false): self { $token = self::generateToken(); $create = DB::prepare(' INSERT INTO `ytkns_users_sessions` ( `user_id`, `session_token`, `session_ip_first`, `session_bump` ) VALUES ( :user, :token, INET6_ATON(:ip), :bump ) '); $create->bindValue('user', $user->getId()); $create->bindValue('token', $token); $create->bindValue('ip', $_SERVER['REMOTE_ADDR']); $create->bindValue('bump', $bump ? 1 : 0); $create->execute(); try { return self::byToken($token); } catch(UserSessionNotFoundException $ex) { throw new UserSessionCreatedFailedException; } } public static function byToken(string $token): self { $getSession = DB::prepare(' SELECT `user_id`, `session_token`, `session_bump`, UNIX_TIMESTAMP(`session_created`) AS `session_created`, UNIX_TIMESTAMP(`session_expires`) AS `session_expires`, INET6_NTOA(`session_ip_first`) AS `session_ip_first`, INET6_NTOA(`session_ip_last`) AS `session_ip_last` FROM `ytkns_users_sessions` WHERE `session_token` = :token '); $getSession->bindValue('token', $token); $session = $getSession->execute() ? $getSession->fetchObject(self::class) : false; if(!$session) throw new UserSessionNotFoundException; return $session; } public static function purge(): void { DB::exec(' DELETE FROM `ytkns_users_sessions` WHERE `session_expires` <= NOW() '); } }