12-05-2026 Frontend dev
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run

This commit is contained in:
Kevin Adametz 2026-05-12 18:32:33 +02:00
parent 405df0a122
commit 5b8bdf4182
779 changed files with 480564 additions and 6241 deletions

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\Response;
class EnsureApiTokenRateLimit
{
private const MAX_ATTEMPTS = 60;
private const DECAY_SECONDS = 60;
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$key = $this->rateLimitKey($request);
if (RateLimiter::tooManyAttempts($key, self::MAX_ATTEMPTS)) {
$retryAfter = RateLimiter::availableIn($key);
return response()->json([
'message' => 'API rate limit exceeded.',
], 429, [
'Retry-After' => (string) $retryAfter,
'X-RateLimit-Limit' => (string) self::MAX_ATTEMPTS,
'X-RateLimit-Remaining' => '0',
]);
}
RateLimiter::hit($key, self::DECAY_SECONDS);
$response = $next($request);
$response->headers->set('X-RateLimit-Limit', (string) self::MAX_ATTEMPTS);
$response->headers->set('X-RateLimit-Remaining', (string) RateLimiter::remaining($key, self::MAX_ATTEMPTS));
return $response;
}
private function rateLimitKey(Request $request): string
{
$bearerToken = $request->bearerToken();
if ($bearerToken !== null && str_contains($bearerToken, '|')) {
[$tokenId] = explode('|', $bearerToken, 2);
if (ctype_digit($tokenId)) {
return 'api-v1:token:'.$tokenId;
}
}
if ($bearerToken !== null) {
return 'api-v1:bearer:'.hash('sha256', $bearerToken);
}
$token = $request->user()?->currentAccessToken();
if (is_object($token) && method_exists($token, 'getKey') && $token->getKey() !== null) {
return 'api-v1:token:'.$token->getKey();
}
return 'api-v1:user:'.($request->user()?->getAuthIdentifier() ?? $request->ip());
}
}