misuzu/src/Auth/AuthTokenPacker.php

100 lines
3.2 KiB
PHP
Raw Normal View History

<?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);
}
}