Skip to content

Commit

Permalink
Merge pull request #20 from l3aro/main
Browse files Browse the repository at this point in the history
Key class customizable
  • Loading branch information
danharrin authored Oct 27, 2023
2 parents 26fa198 + 765eb82 commit 027a44c
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 14 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"illuminate/support": "^9.0|^10.0"
},
"require-dev": {
"livewire/livewire": "^2.3",
"livewire/livewire": "^3.0",
"livewire/volt": "^1.3",
"orchestra/testbench": "^7.0|^8.0",
"phpunit/phpunit": "^9.0|^10.0"
},
Expand Down
2 changes: 1 addition & 1 deletion src/Exceptions/TooManyRequestsException.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public function __construct(
public $component,
public $method,
public $ip,
public $secondsUntilAvailable
public $secondsUntilAvailable,
) {
$this->minutesUntilAvailable = ceil($this->secondsUntilAvailable / 60);

Expand Down
27 changes: 17 additions & 10 deletions src/WithRateLimiting.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,52 @@

trait WithRateLimiting
{
protected function clearRateLimiter($method = null)
protected function clearRateLimiter($method = null, $component = null)
{
$method ??= debug_backtrace(limit: 2)[1]['function'];

$key = $this->getRateLimitKey($method);
$component ??= static::class;

$key = $this->getRateLimitKey($method, $component);

RateLimiter::clear($key);
}

protected function getRateLimitKey($method)
protected function getRateLimitKey($method, $component)
{
$method ??= debug_backtrace(limit: 2)[1]['function'];

return sha1(static::class.'|'.$method.'|'.request()->ip());
$component ??= static::class;

return sha1($component.'|'.$method.'|'.request()->ip());
}

protected function hitRateLimiter($method = null, $decaySeconds = 60)
protected function hitRateLimiter($method = null, $decaySeconds = 60, $component = null)
{
$method ??= debug_backtrace(limit: 2)[1]['function'];

$key = $this->getRateLimitKey($method);
$component ??= static::class;

$key = $this->getRateLimitKey($method, $component);

RateLimiter::hit($key, $decaySeconds);
}

protected function rateLimit($maxAttempts, $decaySeconds = 60, $method = null)
protected function rateLimit($maxAttempts, $decaySeconds = 60, $method = null, $component = null)
{
$method ??= debug_backtrace(limit: 2)[1]['function'];

$key = $this->getRateLimitKey($method);
$component ??= static::class;

$key = $this->getRateLimitKey($method, $component);

if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
$component = static::class;
$ip = request()->ip();
$secondsUntilAvailable = RateLimiter::availableIn($key);

throw new TooManyRequestsException($component, $method, $ip, $secondsUntilAvailable);
}

$this->hitRateLimiter($method, $decaySeconds);
$this->hitRateLimiter($method, $decaySeconds, $component);
}
}
48 changes: 47 additions & 1 deletion tests/RateLimitingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Livewire\Livewire;
use Livewire\Volt\Volt;

class RateLimitingTest extends TestCase
{
Expand Down Expand Up @@ -42,6 +43,51 @@ public function can_hit_and_clear_rate_limiter()
->call('limit')
->assertSet('secondsUntilAvailable', 0);
}

/** @test */
public function can_rate_limit_volt()
{
$this->mountVolt();
$component = Volt::test('volt-component');

$component
->call('limit')
->assertSet('secondsUntilAvailable', 0)
->call('limit')
->assertSet('secondsUntilAvailable', 0)
->call('limit')
->assertSet('secondsUntilAvailable', 0)
->call('limit')
->assertNotSet('secondsUntilAvailable', 0);

sleep(1);

$component
->call('limit')
->assertSet('secondsUntilAvailable', 0);
}

/** @test */
public function can_hit_and_clear_rate_limiter_volt()
{
$this->mountVolt();
Volt::test('volt-component')
->call('hit')
->call('hit')
->call('hit')
->call('limit')
->assertNotSet('secondsUntilAvailable', 0)
->call('clear')
->call('limit')
->assertSet('secondsUntilAvailable', 0);
}

protected function mountVolt()
{
Volt::mount([
__DIR__ . '/views',
]);
}
}

class Component extends \Livewire\Component
Expand Down Expand Up @@ -75,4 +121,4 @@ public function render()
{
return view('component');
}
}
}
5 changes: 4 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace DanHarrin\LivewireRateLimiting\Tests;

use Livewire\LivewireServiceProvider;
use Livewire\Volt\Volt;
use Livewire\Volt\VoltServiceProvider;

class TestCase extends \Orchestra\Testbench\TestCase
{
Expand All @@ -20,6 +22,7 @@ protected function getPackageProviders($app)
{
return [
LivewireServiceProvider::class,
VoltServiceProvider::class,
];
}
}
}
36 changes: 36 additions & 0 deletions tests/views/volt-component.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
use Livewire\Volt\Component;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
new class extends Component {
use \DanHarrin\LivewireRateLimiting\WithRateLimiting;
public $secondsUntilAvailable;
public function clear()
{
$this->clearRateLimiter('limit', component: 'VoltComponent');
}
public function hit()
{
$this->hitRateLimiter('limit', 1, component: 'VoltComponent');
}
public function limit()
{
try {
$this->rateLimit(3, 1, component: 'VoltComponent');
} catch (TooManyRequestsException $exception) {
return $this->secondsUntilAvailable = $exception->secondsUntilAvailable;
}
$this->secondsUntilAvailable = 0;
}
}; ?>

<div>
//
</div>

0 comments on commit 027a44c

Please sign in to comment.