Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class ThrottleRequestsWithRedis extends ThrottleRequests
*/
public $remaining = [];

/**
* The key prefix for Redis throttle keys.
*
* @var string
*/
protected $keyPrefix = '';

/**
* Create a new request throttler.
*
Expand Down Expand Up @@ -93,7 +100,7 @@ protected function handleRequest($request, Closure $next, array $limits)
protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
{
$limiter = new DurationLimiter(
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
$this->getRedisConnection(), $this->getKeyPrefix().$key, $maxAttempts, $decaySeconds
);

return tap($limiter->tooManyAttempts(), function () use ($key, $limiter) {
Expand All @@ -114,7 +121,7 @@ protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
protected function hit($key, $maxAttempts, $decaySeconds)
{
$limiter = new DurationLimiter(
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
$this->getRedisConnection(), $this->getKeyPrefix().$key, $maxAttempts, $decaySeconds
);

$limiter->acquire();
Expand Down Expand Up @@ -148,6 +155,33 @@ protected function getTimeUntilNextRetry($key)
return $this->decaysAt[$key] - $this->currentTime();
}

/**
* Get the prefix for Redis throttle keys.
*
* Override this method to provide a stable namespace for Redis keys,
* which is required in Redis environments where ACLs restrict access
* by key pattern.
*
* @return string
*/
protected function getKeyPrefix()
{
return $this->keyPrefix;
}

/**
* Set the prefix for Redis throttle keys.
*
* @param string $prefix
* @return $this
*/
public function setKeyPrefix($prefix)
{
$this->keyPrefix = $prefix;

return $this;
}

/**
* Get the Redis connection that should be used for throttling.
*
Expand Down
33 changes: 33 additions & 0 deletions tests/Integration/Http/ThrottleRequestsWithRedisTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,37 @@ public function testItReturnsConfiguredResponseWhenUsingAfterLimit(): void
$this->get('/')->assertTooManyRequests()->assertContent('ah ah ah');
});
}

public function testKeyPrefixIsPrependedToRedisKeys()
{
$this->ifRedisAvailable(function () {
Route::get('/', function () {
return 'yes';
})->middleware(ThrottleRequestsWithRedis::class.':2,1');

// Set a key prefix on the middleware
$this->app->resolving(ThrottleRequestsWithRedis::class, function ($middleware) {
$middleware->setKeyPrefix('throttle:');
});

$response = $this->withoutExceptionHandling()->get('/');
$this->assertSame('yes', $response->getContent());

// Verify the middleware works correctly with the prefix
$response = $this->withoutExceptionHandling()->get('/');
$this->assertSame('yes', $response->getContent());
$this->assertEquals(0, $response->headers->get('X-RateLimit-Remaining'));
});
}

public function testSetKeyPrefixReturnsSelf()
{
$limiter = $this->app->make(\Illuminate\Cache\RateLimiter::class);
$redis = $this->app->make(\Illuminate\Contracts\Redis\Factory::class);
$middleware = new ThrottleRequestsWithRedis($limiter, $redis);

$result = $middleware->setKeyPrefix('throttle:');

$this->assertSame($middleware, $result);
}
}
Loading