timestamp; } public function updateTimestamp(): void { $this->timestamp = self::timestamp(); } public function hasProperty(string $name): bool { return isset($this->props[$name]); } public function getProperty(string $name): string { return $this->props[$name] ?? ''; } public function setProperty(string $name, string $value): void { $this->props[$name] = $value; $this->updateTimestamp(); } public function removeProperty(string $name): void { unset($this->props[$name]); $this->updateTimestamp(); } public function isValid(): bool { if($this->getUserId() < 1 || empty($this->getSessionToken())) return false; return true; } public function getUserId(): string { return $this->getProperty('u'); } public function setUserId(string $userId): self { $this->setProperty('u', $userId); return $this; } public function getSessionToken(): string { if($this->hasProperty('s')) return $this->getProperty('s'); if($this->hasProperty('t')) return bin2hex($this->getProperty('t')); return ''; } public function setSessionToken(string $token): self { $this->setProperty('s', $token); return $this; } public function hasImpersonatedUserId(): bool { return $this->hasProperty('i'); } public function getImpersonatedUserId(): int { $value = (int)$this->getProperty('i'); return $value < 1 ? -1 : $value; } public function setImpersonatedUserId(int $userId): void { $this->setProperty('i', (string)$userId); } public function removeImpersonatedUserId(): void { $this->removeProperty('i'); } public function pack(bool $base64 = true): string { $data = ''; foreach($this->props as $name => $value) { // very smart solution for this issue, you definitely won't be confused by this later // down the line when a variable suddenly despawns from the token $nameLength = strlen($name); $valueLength = strlen($value); if($nameLength > 255 || $valueLength > 255) continue; $data .= chr($nameLength) . $name . chr($valueLength) . $value; } $prefix = pack('CN', 2, $this->getTimestamp()); $data = $prefix . hash_hmac('sha3-256', $prefix . $data, self::$secretKey, true) . $data; if($base64) $data = UriBase64::encode($data); return $data; } public static function unpack(string $data, bool $base64 = true): self { $obj = new AuthToken; if(empty($data)) return $obj; if($base64) $data = UriBase64::decode($data); if(empty($data)) return $obj; $version = ord($data[0]); $data = substr($data, 1); if($version === 1) { $data = str_pad($data, 36, "\x00"); $data = unpack('Nuser/H*token', $data); $obj->props['u'] = (string)$data['user']; $obj->props['s'] = $data['token']; $obj->updateTimestamp(); } elseif($version === 2) { $timestamp = substr($data, 0, 4); $userHash = substr($data, 4, 32); $data = substr($data, 36); $realHash = hash_hmac('sha3-256', chr($version) . $timestamp . $data, self::$secretKey, true); if(!hash_equals($realHash, $userHash)) return $obj; $unpacked = unpack('Nts', $timestamp); $obj->timestamp = (int)$unpacked['ts']; $stream = MemoryStream::fromString($data); $stream->seek(0); for(;;) { $length = $stream->readChar(); if($length === null) break; $length = ord($length); if($length < 1) break; $name = $stream->read($length); $value = null; $length = $stream->readChar(); if($length !== null) { $length = ord($length); if($length > 0) $value = $stream->read($length); } $obj->props[$name] = $value; } } return $obj; } public static function timestamp(): int { return time() - self::EPOCH; } public static function create(UserInfo $userInfo, SessionInfo $sessionInfo): self { $token = new AuthToken; $token->setUserId($userInfo->getId()); $token->setSessionToken($sessionInfo->getToken()); return $token; } public static function cookieDomain(bool $compatible = true): string { $url = parse_url($_SERVER['HTTP_HOST'], PHP_URL_HOST); if(empty($url)) $url = $_SERVER['HTTP_HOST']; if(!filter_var($url, FILTER_VALIDATE_IP) && $compatible) $url = '.' . $url; return $url; } public function applyCookie(int $expires = 0): void { if($expires > 0) $this->cookieExpires = $expires; else $expires = $this->cookieExpires; setcookie('msz_auth', $this->pack(), $expires, '/', self::cookieDomain(), !empty($_SERVER['HTTPS']), true); } public static function nukeCookie(): void { setcookie('msz_auth', '', -9001, '/', self::cookieDomain(), !empty($_SERVER['HTTPS']), true); setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true); } public static function nukeCookieLegacy(): void { setcookie('msz_uid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true); setcookie('msz_sid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true); } }