From 7a9dc620226fb7b62174b6a6c5c95536f464adcc Mon Sep 17 00:00:00 2001 From: Kyrch Date: Mon, 10 Nov 2025 09:09:08 -0300 Subject: [PATCH] fix(graphql): missing rate limiting headers (#997) --- .env.example | 1 + .env.example-sail | 1 + .../Middleware/GraphQL/RateLimitPerQuery.php | 21 +++++++++++++------ config/graphql.php | 2 ++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 519ce7d0f..47874dc67 100644 --- a/.env.example +++ b/.env.example @@ -183,6 +183,7 @@ FORTIFY_URL=http://localhost # graphql GRAPHQL_DOMAIN_NAME= GRAPHQL_PATH=/graphql +GRAPHQL_RATE_LIMIT=80 GRAPHIQL_ENABLED=true # hashids diff --git a/.env.example-sail b/.env.example-sail index 5c389fa0e..cb353ddc6 100644 --- a/.env.example-sail +++ b/.env.example-sail @@ -181,6 +181,7 @@ FORTIFY_URL=http://localhost # graphql GRAPHQL_DOMAIN_NAME= GRAPHQL_PATH=/graphql +GRAPHQL_RATE_LIMIT=80 GRAPHIQL_ENABLED=true # hashids diff --git a/app/Http/Middleware/GraphQL/RateLimitPerQuery.php b/app/Http/Middleware/GraphQL/RateLimitPerQuery.php index 66a6066c8..53c63954b 100644 --- a/app/Http/Middleware/GraphQL/RateLimitPerQuery.php +++ b/app/Http/Middleware/GraphQL/RateLimitPerQuery.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace App\Http\Middleware\GraphQL; use App\Enums\Auth\SpecialPermission; -use App\Models\Auth\User; use Closure; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\Parser; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\RateLimiter; use JsonException; @@ -22,6 +22,8 @@ class RateLimitPerQuery */ public function handle(Request $request, Closure $next): mixed { + $limit = Config::get('graphql.rate_limit'); + $user = Auth::user(); $query = $request->input('query'); $ip = $request->ip(); @@ -58,10 +60,17 @@ class RateLimitPerQuery $key = sprintf('graphql:%s', Auth::id() ?? $ip); foreach (range(1, $hits) as $_) { - if (RateLimiter::tooManyAttempts($key, 80)) { + if (RateLimiter::tooManyAttempts($key, $limit)) { + $retryAfter = RateLimiter::availableIn($key); + return new JsonResponse([ - 'message' => 'Too many requests', - ], 429); + 'message' => 'Too Many Attempts.', + ], 429, [ + 'Retry-After' => $retryAfter, + 'X-RateLimit-Limit' => $limit, + 'X-RateLimit-Remaining' => RateLimiter::remaining($key, $limit), + 'X-RateLimit-Reset' => now()->addSeconds($retryAfter)->getTimestampMs(), + ]); } RateLimiter::hit($key); @@ -69,8 +78,8 @@ class RateLimitPerQuery return $next($request) ->withHeaders([ - 'X-RateLimit-Limit' => 80, - 'X-RateLimit-Remaining' => RateLimiter::remaining($key, 80), + 'X-RateLimit-Limit' => $limit, + 'X-RateLimit-Remaining' => RateLimiter::remaining($key, $limit), ]); } diff --git a/config/graphql.php b/config/graphql.php index cbbe029a0..82b778896 100644 --- a/config/graphql.php +++ b/config/graphql.php @@ -6,6 +6,8 @@ use App\GraphQL\Schema\Schemas\DefaultSchema; use App\GraphQL\Schema\Types\Base\PaginationInfoType; return [ + 'rate_limit' => env('GRAPHQL_RATE_LIMIT', 80), + 'route' => [ // The prefix for routes; do NOT use a leading slash! 'prefix' => env('GRAPHQL_PATH', '/'),