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