diff --git a/app/Http/Middleware/ThrottleRequestsWithPredis.php b/app/Http/Middleware/ThrottleRequestsWithPredis.php new file mode 100644 index 000000000000..05a4a122d5b1 --- /dev/null +++ b/app/Http/Middleware/ThrottleRequestsWithPredis.php @@ -0,0 +1,125 @@ +redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache'); + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @param array $limits + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Illuminate\Http\Exceptions\ThrottleRequestsException + */ + protected function handleRequest($request, Closure $next, array $limits) + { + foreach ($limits as $limit) { + if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) { + throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback); + } + } + + $response = $next($request); + + foreach ($limits as $limit) { + $response = $this->addHeaders( + $response, + $limit->maxAttempts, + $this->calculateRemainingAttempts($limit->key, $limit->maxAttempts) + ); + } + + return $response; + } + + /** + * Determine if the given key has been "accessed" too many times. + * + * @param string $key + * @param int $maxAttempts + * @param int $decayMinutes + * @return mixed + */ + protected function tooManyAttempts($key, $maxAttempts, $decayMinutes) + { + $limiter = new DurationLimiter( + $this->redis, + $key, + $maxAttempts, + $decayMinutes * 60 + ); + + return tap(! $limiter->acquire(), function () use ($key, $limiter) { + [$this->decaysAt[$key], $this->remaining[$key]] = [ + $limiter->decaysAt, $limiter->remaining, + ]; + }); + } + + /** + * Calculate the number of remaining attempts. + * + * @param string $key + * @param int $maxAttempts + * @param int|null $retryAfter + * @return int + */ + protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null) + { + return is_null($retryAfter) ? $this->remaining[$key] : 0; + } + + /** + * Get the number of seconds until the lock is released. + * + * @param string $key + * @return int + */ + protected function getTimeUntilNextRetry($key) + { + return $this->decaysAt[$key] - $this->currentTime(); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index adc66e8fe479..8ebcbbc81fbc 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -18,6 +18,7 @@ use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Route; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; +use App\Http\Middleware\ThrottleRequestsWithPredis; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Routing\Middleware\ThrottleRequestsWithRedis; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; @@ -38,7 +39,7 @@ class RouteServiceProvider extends ServiceProvider if (Ninja::isHosted()) { - app('router')->aliasMiddleware('throttle', ThrottleRequestsWithRedis::class); + app('router')->aliasMiddleware('throttle', ThrottleRequestsWithPredis::class); // app('router')->aliasMiddleware('throttle', ThrottleRequests::class); } else {