forked from BassPago/leeds_backend
71 lines
1.5 KiB
PHP
71 lines
1.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Bass\Webclient\Middleware;
|
|
|
|
use Bass\Webclient\Libs\ResponseLib;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use React\Http\Message\Response;
|
|
|
|
class RateLimitMiddleware
|
|
{
|
|
private int $maxRequests;
|
|
private int $windowSeconds;
|
|
private array $limits = [];
|
|
|
|
public function __construct(int $maxRequests = 30, int $windowSeconds = 60)
|
|
{
|
|
$this->maxRequests = $maxRequests;
|
|
$this->windowSeconds = $windowSeconds;
|
|
}
|
|
|
|
public function __invoke(
|
|
ServerRequestInterface $request,
|
|
callable $next
|
|
): Response {
|
|
$apiKey = $request->getHeaderLine('X-API-KEY');
|
|
|
|
if (!$apiKey) {
|
|
return ResponseLib::sendFail(
|
|
'Missing API key',
|
|
401,
|
|
['code' => 'MISSING_API_KEY']
|
|
);
|
|
}
|
|
|
|
// 🔒 rate-limit por API key (em memória)
|
|
if (!$this->allowRequest($apiKey)) {
|
|
return ResponseLib::sendFail(
|
|
'Rate limit exceeded',
|
|
429,
|
|
[
|
|
'code' => 'RATE_LIMIT_EXCEEDED',
|
|
'retry_after' => $this->windowSeconds
|
|
]
|
|
);
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
|
|
/**
|
|
* In-memory rate-limit control
|
|
* (per process, per API key)
|
|
*/
|
|
private function allowRequest(string $key): bool
|
|
{
|
|
$now = time();
|
|
|
|
if (!isset($this->limits[$key]) || $now > $this->limits[$key]['reset']) {
|
|
$this->limits[$key] = [
|
|
'count' => 0,
|
|
'reset' => $now + $this->windowSeconds
|
|
];
|
|
}
|
|
|
|
$this->limits[$key]['count']++;
|
|
|
|
return $this->limits[$key]['count'] <= $this->maxRequests;
|
|
}
|
|
}
|