79 lines
2.5 KiB
PHP
79 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
|
|
beforeEach(function () {
|
|
// Pest.php disables ThrottleRequests globally — re-enable for these tests.
|
|
$this->withMiddleware(ThrottleRequests::class);
|
|
RateLimiter::clear('login');
|
|
});
|
|
|
|
test('API login throttle eventually blocks after enough failed attempts', function () {
|
|
// The login endpoint stacks two protections: middleware throttle:10,1 and a
|
|
// controller-level RateLimiter keyed by IP+email (configurable via
|
|
// security_auth.login_max_attempts, default 5). After enough hits, one of
|
|
// them must kick in and return 429.
|
|
$sawBlock = false;
|
|
for ($i = 0; $i < 15; $i++) {
|
|
$r = $this->postJson('/api/v1/login', ['email' => 'x@x.com', 'password' => 'wrong']);
|
|
if ($r->getStatusCode() === 429) {
|
|
$sawBlock = true;
|
|
break;
|
|
}
|
|
}
|
|
expect($sawBlock)->toBeTrue();
|
|
});
|
|
|
|
test('API forgot-password throttle blocks after 5 requests', function () {
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->postJson('/api/v1/forgot-password', ['email' => 'x@x.com']);
|
|
}
|
|
|
|
$this->postJson('/api/v1/forgot-password', ['email' => 'x@x.com'])
|
|
->assertStatus(429);
|
|
});
|
|
|
|
test('API OTP send throttle blocks after 5 requests', function () {
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->postJson('/api/v1/otp/send', ['email' => 'x@x.com']);
|
|
}
|
|
|
|
$this->postJson('/api/v1/otp/send', ['email' => 'x@x.com'])
|
|
->assertStatus(429);
|
|
});
|
|
|
|
test('API register throttle blocks after 5 requests', function () {
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->postJson('/api/v1/register', []);
|
|
}
|
|
|
|
$this->postJson('/api/v1/register', [])->assertStatus(429);
|
|
});
|
|
|
|
test('2FA verify throttle blocks after 5 requests', function () {
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->post('/2fa', ['code' => '123456']);
|
|
}
|
|
|
|
$this->post('/2fa', ['code' => '123456'])->assertStatus(429);
|
|
});
|
|
|
|
test('different IPs do not share the same rate-limit bucket', function () {
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->call('POST', '/api/v1/forgot-password',
|
|
parameters: ['email' => 'x@x.com'],
|
|
server: ['REMOTE_ADDR' => '10.0.0.1']
|
|
);
|
|
}
|
|
|
|
// Same email, different IP — should be fine
|
|
$r = $this->call('POST', '/api/v1/forgot-password',
|
|
parameters: ['email' => 'x@x.com'],
|
|
server: ['REMOTE_ADDR' => '10.0.0.2']
|
|
);
|
|
expect($r->getStatusCode())->not->toBe(429);
|
|
});
|