Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* livewire/update is rate limited (RouteServiceProvider: 'livewire'):
|
||||
* 120 a minute per IP for guests, 500 a minute per logged-in user.
|
||||
*
|
||||
* On 2026-10-08 one bot posted tampered payloads there for 9 hours, up to
|
||||
* ~850 a minute.
|
||||
*/
|
||||
class LivewireRateLimitTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Keep the limiter's counters out of the dev site's Redis (see
|
||||
// PdfRateLimitTest for why the limiter is rebuilt).
|
||||
config(['cache.default' => 'array', 'cache.limiter' => 'array']);
|
||||
$this->app->forgetInstance(\Illuminate\Cache\RateLimiter::class);
|
||||
RateLimiter::clearResolvedInstances();
|
||||
$this->app->getProvider(\App\Providers\RouteServiceProvider::class)->configureRateLimiting();
|
||||
}
|
||||
|
||||
private function update(): \Illuminate\Testing\TestResponse
|
||||
{
|
||||
// A valid CSRF token, as the bot had: CSRF is checked before the
|
||||
// limiter, and a request without one is refused (419) uncounted.
|
||||
// An empty payload: the limit counts requests, not successes.
|
||||
return $this->withSession(['_token' => 'test-token'])
|
||||
->withHeader('X-CSRF-TOKEN', 'test-token')
|
||||
->postJson(route('default.livewire.update'), ['components' => []]);
|
||||
}
|
||||
|
||||
public function test_the_update_route_is_throttled(): void
|
||||
{
|
||||
$route = app('router')->getRoutes()->getByName('default.livewire.update');
|
||||
|
||||
$this->assertContains('throttle:livewire', $route->gatherMiddleware());
|
||||
}
|
||||
|
||||
public function test_a_guest_is_refused_after_120_a_minute(): void
|
||||
{
|
||||
for ($i = 1; $i <= 120; $i++) {
|
||||
$this->assertNotSame(429, $this->update()->status(), "Request $i is within the limit.");
|
||||
}
|
||||
|
||||
$refused = $this->update();
|
||||
|
||||
$refused->assertStatus(429);
|
||||
$this->assertSame('120', (string) $refused->headers->get('X-RateLimit-Limit'));
|
||||
}
|
||||
|
||||
public function test_a_logged_in_user_gets_500_a_minute_of_their_own(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
// A guest on the same IP has used up the guest allowance...
|
||||
for ($i = 0; $i < 121; $i++) {
|
||||
$this->update();
|
||||
}
|
||||
$this->update()->assertStatus(429);
|
||||
|
||||
// ...which does not touch the user's.
|
||||
$response = $this->actingAs($user)->update();
|
||||
|
||||
$this->assertNotSame(429, $response->status());
|
||||
$this->assertSame('500', (string) $response->headers->get('X-RateLimit-Limit'));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user