126 lines
4.4 KiB
PHP
126 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use Database\Seeders\RolesAndPermissionsSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class RateLimitingTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_login_is_throttled_after_five_attempts(): void
|
|
{
|
|
$user = User::factory()->approved()->create(['password' => bcrypt('correct-password')]);
|
|
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->postJson('/web-api/login', [
|
|
'email' => $user->email,
|
|
'password' => 'wrong-password',
|
|
])->assertUnprocessable();
|
|
}
|
|
|
|
$this->postJson('/web-api/login', [
|
|
'email' => $user->email,
|
|
'password' => 'wrong-password',
|
|
])->assertStatus(429);
|
|
|
|
// El throttle es por (email, ip): otro email desde la misma IP no está afectado.
|
|
$other = User::factory()->approved()->create(['password' => bcrypt('correct-password')]);
|
|
$this->postJson('/web-api/login', [
|
|
'email' => $other->email,
|
|
'password' => 'correct-password',
|
|
])->assertOk();
|
|
}
|
|
|
|
public function test_register_is_throttled_after_five_attempts(): void
|
|
{
|
|
// El primer registro de una instalación vacía toma un camino distinto
|
|
// (bootstrap admin, 200 en vez de 201, y siembra roles/permisos) — se
|
|
// crea un usuario previo y se siembran los roles a mano para que
|
|
// todos los intentos del loop pasen por el camino normal.
|
|
(new RolesAndPermissionsSeeder)->run();
|
|
User::factory()->approved()->create();
|
|
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->postJson('/web-api/register', [
|
|
'name' => "User {$i}",
|
|
'email' => "user{$i}@example.com",
|
|
'password' => 'password123',
|
|
'password_confirmation' => 'password123',
|
|
])->assertCreated();
|
|
}
|
|
|
|
$this->postJson('/web-api/register', [
|
|
'name' => 'One Too Many',
|
|
'email' => 'onetoomany@example.com',
|
|
'password' => 'password123',
|
|
'password_confirmation' => 'password123',
|
|
])->assertStatus(429);
|
|
}
|
|
|
|
public function test_oauth_token_endpoint_is_throttled_per_client(): void
|
|
{
|
|
$owner = User::factory()->approved()->create();
|
|
$response = $this->actingAs($owner)->postJson('/web-api/clients', [
|
|
'name' => 'Throttle Test Client',
|
|
'grant_type' => 'client_credentials',
|
|
]);
|
|
$response->assertCreated();
|
|
$clientId = $response->json('client.id');
|
|
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$this->postJson('/oauth/token', [
|
|
'grant_type' => 'client_credentials',
|
|
'client_id' => $clientId,
|
|
'client_secret' => 'wrong-secret',
|
|
])->assertStatus(401);
|
|
}
|
|
|
|
$this->postJson('/oauth/token', [
|
|
'grant_type' => 'client_credentials',
|
|
'client_id' => $clientId,
|
|
'client_secret' => 'wrong-secret',
|
|
])->assertStatus(429);
|
|
}
|
|
|
|
public function test_oauth_token_throttle_is_scoped_per_client_not_globally(): void
|
|
{
|
|
$owner = User::factory()->approved()->create();
|
|
|
|
$clientA = $this->actingAs($owner)->postJson('/web-api/clients', [
|
|
'name' => 'Client A',
|
|
'grant_type' => 'client_credentials',
|
|
])->json('client.id');
|
|
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$this->postJson('/oauth/token', [
|
|
'grant_type' => 'client_credentials',
|
|
'client_id' => $clientA,
|
|
'client_secret' => 'wrong-secret',
|
|
]);
|
|
}
|
|
$this->postJson('/oauth/token', [
|
|
'grant_type' => 'client_credentials',
|
|
'client_id' => $clientA,
|
|
'client_secret' => 'wrong-secret',
|
|
])->assertStatus(429);
|
|
|
|
$clientBResponse = $this->actingAs($owner)->postJson('/web-api/clients', [
|
|
'name' => 'Client B',
|
|
'grant_type' => 'client_credentials',
|
|
]);
|
|
$clientBSecret = $clientBResponse->json('plain_secret');
|
|
$clientBId = $clientBResponse->json('client.id');
|
|
|
|
$this->postJson('/oauth/token', [
|
|
'grant_type' => 'client_credentials',
|
|
'client_id' => $clientBId,
|
|
'client_secret' => $clientBSecret,
|
|
'scope' => 'users:read',
|
|
])->assertOk();
|
|
}
|
|
}
|