71 lines
1.9 KiB
PHP
71 lines
1.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
final class CsrfGuard
|
|
{
|
|
private string $secret;
|
|
private string $username;
|
|
private string $sessionId;
|
|
|
|
public function __construct(string $secret, string $username, string $sessionId)
|
|
{
|
|
$this->secret = $secret;
|
|
$this->username = $username;
|
|
$this->sessionId = $sessionId !== '' ? $sessionId : 'no_session';
|
|
}
|
|
|
|
/**
|
|
* @return array{ts:int,token:string}
|
|
*/
|
|
public function issue(string $intent): array
|
|
{
|
|
$ts = time();
|
|
$sid = $this->sessionId;
|
|
return [
|
|
'ts' => $ts,
|
|
'sid' => $sid,
|
|
'token' => $this->buildToken($intent, $ts, $sid),
|
|
];
|
|
}
|
|
|
|
public function validate(string $intent, string $timestamp, string $token, int $ttl = 3600, string $sessionHint = ''): bool
|
|
{
|
|
if (!preg_match('/^[0-9]{1,20}$/', $timestamp)) {
|
|
return false;
|
|
}
|
|
|
|
$ts = (int)$timestamp;
|
|
$now = time();
|
|
if ($ts <= 0 || $ts > ($now + 30) || ($now - $ts) > $ttl) {
|
|
return false;
|
|
}
|
|
|
|
$token = trim($token);
|
|
$sids = [];
|
|
|
|
$hint = trim($sessionHint);
|
|
if ($hint !== '') {
|
|
$sids[] = $hint;
|
|
}
|
|
$sids[] = $this->sessionId;
|
|
$sids[] = 'no_session';
|
|
$sids[] = '';
|
|
$sids = array_values(array_unique($sids));
|
|
|
|
foreach ($sids as $sid) {
|
|
$expected = $this->buildToken($intent, $ts, $sid);
|
|
if (hash_equals($expected, $token)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function buildToken(string $intent, int $timestamp, string $sid): string
|
|
{
|
|
$payload = implode('|', [$this->username, $sid !== '' ? $sid : 'no_session', $intent, (string)$timestamp]);
|
|
return hash_hmac('sha256', $payload, $this->secret);
|
|
}
|
|
}
|