diff --git a/.env.example b/.env.example index b37762ab2..2c6c06b87 100644 --- a/.env.example +++ b/.env.example @@ -175,7 +175,7 @@ FORTIFY_URL=http://localhost # graphql GRAPHQL_URL= GRAPHQL_PATH=/graphql -GRAPHQL_RATE_LIMIT=90 +GRAPHQL_RATE_LIMIT=135 GRAPHIQL_ENABLED=true # hashids diff --git a/.env.example-sail b/.env.example-sail index 2f3e33cc8..47656c2c0 100644 --- a/.env.example-sail +++ b/.env.example-sail @@ -173,7 +173,7 @@ FORTIFY_URL=http://localhost # graphql GRAPHQL_URL= GRAPHQL_PATH=/graphql -GRAPHQL_RATE_LIMIT=90 +GRAPHQL_RATE_LIMIT=135 GRAPHIQL_ENABLED=true # hashids diff --git a/app/Http/Middleware/GraphQL/RateLimitPerQuery.php b/app/Http/Middleware/GraphQL/RateLimitPerQuery.php index 880f1eda2..18bd98e49 100644 --- a/app/Http/Middleware/GraphQL/RateLimitPerQuery.php +++ b/app/Http/Middleware/GraphQL/RateLimitPerQuery.php @@ -41,49 +41,49 @@ class RateLimitPerQuery $ip = $forwardedIp; } - if ($query) { - $rootFields = 0; - - try { - $ast = Parser::parse($query); - - foreach ($ast->definitions as $definition) { - if ($definition instanceof OperationDefinitionNode) { - $rootFields += count($definition->selectionSet->selections ?? []); - } - } - } catch (JsonException|SyntaxError) { - // Do nothing - } - - $hits = max(1, $rootFields); - - $key = sprintf('graphql:%s', Auth::id() ?? $ip); - - foreach (range(1, $hits) as $_) { - if (RateLimiter::tooManyAttempts($key, $limit)) { - $retryAfter = RateLimiter::availableIn($key); - - return new JsonResponse([ - '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); - } - - return $next($request) - ->withHeaders([ - 'X-RateLimit-Limit' => $limit, - 'X-RateLimit-Remaining' => RateLimiter::remaining($key, $limit), - ]); + if (! $query) { + return $next($request); } - return $next($request); + $rootFields = 0; + + try { + $ast = Parser::parse($query); + + foreach ($ast->definitions as $definition) { + if ($definition instanceof OperationDefinitionNode) { + $rootFields += count($definition->selectionSet->selections ?? []); + } + } + } catch (JsonException|SyntaxError) { + // Do nothing + } + + $hits = max(1, $rootFields); + + $key = sprintf('graphql:%s', Auth::id() ?? $ip); + + $current = RateLimiter::attempts($key); + + if (($current + $hits) > $limit) { + $retryAfter = RateLimiter::availableIn($key); + + return new JsonResponse([ + '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::increment($key, amount: $hits); + + return $next($request) + ->withHeaders([ + 'X-RateLimit-Limit' => $limit, + 'X-RateLimit-Remaining' => RateLimiter::remaining($key, $limit), + ]); } } diff --git a/config/graphql.php b/config/graphql.php index 4b0e21f0f..21a79d8df 100644 --- a/config/graphql.php +++ b/config/graphql.php @@ -8,7 +8,7 @@ use App\GraphQL\Schema\Types\Base\PaginationInfoType; return [ 'domain' => env('GRAPHQL_URL', env('APP_URL')), - 'rate_limit' => (int) env('GRAPHQL_RATE_LIMIT', 90), + 'rate_limit' => (int) env('GRAPHQL_RATE_LIMIT', 135), 'route' => [ // The prefix for routes; do NOT use a leading slash!