agent/php/Mod/Api/Services/WebhookSignature.php
Snider 5385385314 feat(agent/api): RFC foundation — API keys, webhooks, rate limiting, docs split
Foundation slice for Mantis #844 php/Mod/Api RFC implementation:

* New php/Mod/Api/ package: Boot, Controllers, Documentation, Jobs,
  Middleware, Models, RateLimit, Routes, Services
* Models: ApiKey, WebhookEndpoint, WebhookDelivery
* WebhookService::dispatch() with DB::transaction + afterCommit
* DeliverWebhookJob with retry/backoff
* WebhookSignature with timing-safe verification + 5-minute tolerance +
  dual-secret rotation support
* Sliding-window rate limiter in RateLimit/RateLimitService.php
* AuthenticateApiKey middleware: hk_ prefix + Sanctum fallback
* DocsController / DocumentationController split
* 3 root migrations: api_keys, webhook_endpoints, webhook_deliveries
* Foundation tests under php/tests/Feature/Mod/Api/
* FOLLOWUP.md tracks remaining RFC scope

php -l clean across 21 PHP files. Pest unrunnable in sandbox (no vendor/).

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=844
2026-04-25 21:01:54 +01:00

52 lines
1.2 KiB
PHP

<?php
// SPDX-License-Identifier: EUPL-1.2
declare(strict_types=1);
namespace Core\Mod\Agentic\Mod\Api\Services;
use Illuminate\Support\Str;
class WebhookSignature
{
public const DEFAULT_TOLERANCE = 300;
public function generateSecret(): string
{
return Str::random(64);
}
public function sign(string $payload, string $secret, int $timestamp): string
{
return hash_hmac('sha256', $timestamp.'.'.$payload, $secret);
}
public function verify(
string $payload,
string $signature,
string $secret,
int $timestamp,
int $tolerance = self::DEFAULT_TOLERANCE
): bool {
if (! $this->isTimestampValid($timestamp, $tolerance)) {
return false;
}
return hash_equals($this->sign($payload, $secret, $timestamp), $signature);
}
public function verifySignatureOnly(
string $payload,
string $signature,
string $secret,
int $timestamp
): bool {
return hash_equals($this->sign($payload, $secret, $timestamp), $signature);
}
public function isTimestampValid(int $timestamp, int $tolerance = self::DEFAULT_TOLERANCE): bool
{
return abs(time() - $timestamp) <= $tolerance;
}
}