misuzu/src/Auth/AuthTokenPacker.php
flash 00d1d2922d Changed the way msz_auth is handled.
Going forward msz_auth is always assumed to be present, even while the user is not logged in.
If the cookie is not present a default, empty value will be used.
The msz_uid and msz_sid cookies are also still upconverted for some reason but are no longer removed even though there's no active sessions that can possibly have those anymore.
As with the previous change, shit may be broken so report any Anomalies you come across, through flashii-issues@flash.moe if necessary.
2023-08-03 01:35:08 +00:00

100 lines
3.2 KiB
PHP

<?php
namespace Misuzu\Auth;
use RuntimeException;
use Index\IO\MemoryStream;
use Index\Serialisation\UriBase64;
class AuthTokenPacker {
private const EPOCH_V2 = 1682985600;
public function __construct(private string $secretKey) {}
public function pack(AuthTokenBuilder|AuthTokenInfo $tokenInfo): string {
$props = $tokenInfo->getProperties();
$timestamp = $tokenInfo instanceof AuthTokenInfo ? $tokenInfo->getTimestamp() : time();
$data = '';
foreach($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, $timestamp - self::EPOCH_V2);
$data = $prefix . hash_hmac('sha3-256', $prefix . $data, $this->secretKey, true) . $data;
return UriBase64::encode($data);
}
public function unpack(?string $token): AuthTokenInfo {
if($token === null || $token === '')
return AuthTokenInfo::empty();
$data = UriBase64::decode($token);
if($data === false || $data === '')
return AuthTokenInfo::empty();
$builder = new AuthTokenBuilder;
$version = ord($data[0]);
$data = str_pad(substr($data, 1), 36, "\x00");
$timestamp = null;
if($version === 1) {
$data = unpack('Nuser/H*token', $data);
if($data === false)
return AuthTokenInfo::empty();
$builder->setUserId((string)$data['user']);
$builder->setSessionToken($data['token']);
} 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, $this->secretKey, true);
if(!hash_equals($realHash, $userHash))
return AuthTokenInfo::empty();
$unpackTime = unpack('Nts', $timestamp);
if($unpackTime === false)
throw new RuntimeException('$token does not contain a valid timestamp.');
$timestamp = $unpackTime['ts'] + self::EPOCH_V2;
$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);
}
$builder->setProperty($name, $value);
}
} else
return AuthTokenInfo::empty();
return $builder->toInfo($timestamp);
}
}