100 lines
3.2 KiB
PHP
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);
|
||
|
}
|
||
|
}
|