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(); } }