diff --git a/app/Models/BannedIp.php b/app/Models/BannedIp.php new file mode 100644 index 0000000..b8f310c --- /dev/null +++ b/app/Models/BannedIp.php @@ -0,0 +1,19 @@ + 'datetime']; + + /** Non-expired bans only. */ + public function scopeActive(Builder $query): Builder + { + return $query->where('banned_until', '>', now()); + } +} diff --git a/database/migrations/2026_06_20_000001_create_banned_ips_table.php b/database/migrations/2026_06_20_000001_create_banned_ips_table.php new file mode 100644 index 0000000..9b07bf3 --- /dev/null +++ b/database/migrations/2026_06_20_000001_create_banned_ips_table.php @@ -0,0 +1,25 @@ +id(); + $table->string('ip', 45)->unique(); + $table->timestamp('banned_until'); + $table->string('reason')->nullable(); + $table->unsignedInteger('attempts')->default(0); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('banned_ips'); + } +}; diff --git a/tests/Feature/BruteforceBannedIpTest.php b/tests/Feature/BruteforceBannedIpTest.php new file mode 100644 index 0000000..150da64 --- /dev/null +++ b/tests/Feature/BruteforceBannedIpTest.php @@ -0,0 +1,22 @@ + '1.2.3.4', 'banned_until' => now()->addHour(), 'attempts' => 10]); + BannedIp::create(['ip' => '5.6.7.8', 'banned_until' => now()->subHour(), 'attempts' => 10]); + + $active = BannedIp::active()->pluck('ip')->all(); + + $this->assertSame(['1.2.3.4'], $active); + } +}