Files
portal/app/tests/Feature/Auth/IpLockoutTest.php
T

155 lines
5.1 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
uses(DatabaseTransactions::class);
beforeEach(function () {
$this->tenant = Tenant::factory()->create();
});
test('login_success пишет запись в auth_log', function () {
User::factory()->create([
'tenant_id' => $this->tenant->id,
'email' => 'log-success@example.ru',
'password_hash' => Hash::make('right-password'),
]);
$this->postJson('/api/auth/login', [
'email' => 'log-success@example.ru',
'password' => 'right-password',
])->assertOk();
$row = DB::table('auth_log')
->where('email', 'log-success@example.ru')
->where('event', 'login_success')
->first();
expect($row)->not->toBeNull();
expect($row->actor_type)->toBe('tenant_user');
expect($row->tenant_id)->toBe($this->tenant->id);
});
test('login_failed (wrong password) пишет в auth_log с failure_reason=invalid_password', function () {
User::factory()->create([
'tenant_id' => $this->tenant->id,
'email' => 'log-fail@example.ru',
'password_hash' => Hash::make('right-password'),
]);
$this->postJson('/api/auth/login', [
'email' => 'log-fail@example.ru',
'password' => 'wrong-pass-attempt',
])->assertStatus(422);
$row = DB::table('auth_log')
->where('email', 'log-fail@example.ru')
->where('event', 'login_failed')
->first();
expect($row)->not->toBeNull();
expect($row->failure_reason)->toBe('invalid_password');
});
test('login_failed для unknown email пишет с failure_reason=unknown_email', function () {
$this->postJson('/api/auth/login', [
'email' => 'nobody@example.ru',
'password' => 'wrong-pass-attempt',
])->assertStatus(422);
$row = DB::table('auth_log')
->where('email', 'nobody@example.ru')
->where('event', 'login_failed')
->first();
expect($row)->not->toBeNull();
expect($row->failure_reason)->toBe('unknown_email');
expect($row->user_id)->toBeNull();
});
test('IP-lockout: после 10 login_failed с одного IP за час — следующий → 429', function () {
User::factory()->create([
'tenant_id' => $this->tenant->id,
'email' => 'ip-lockout-victim@example.ru',
'password_hash' => Hash::make('right-password'),
]);
// Эмулируем 10 неудачных попыток с разных email с одного IP
// (через прямой INSERT в auth_log, чтобы не триггерить email-rate-limit).
for ($i = 0; $i < 10; $i++) {
DB::table('auth_log')->insert([
'actor_type' => 'tenant_user',
'event' => 'login_failed',
'email' => "victim{$i}@example.ru",
'ip_address' => '127.0.0.1',
'failure_reason' => 'unknown_email',
'created_at' => now()->subMinutes(5),
]);
}
// Следующий login (даже с правильным паролем) → 429.
$r = $this->postJson('/api/auth/login', [
'email' => 'ip-lockout-victim@example.ru',
'password' => 'right-password',
]);
$r->assertStatus(429);
expect($r->json('message'))->toContain('с этого IP');
expect($r->headers->get('Retry-After'))->toBe('3600');
});
test('IP-lockout не срабатывает на 9 неудач (под порогом)', function () {
User::factory()->create([
'tenant_id' => $this->tenant->id,
'email' => 'ok@example.ru',
'password_hash' => Hash::make('right-password'),
]);
for ($i = 0; $i < 9; $i++) {
DB::table('auth_log')->insert([
'actor_type' => 'tenant_user',
'event' => 'login_failed',
'email' => "any{$i}@example.ru",
'ip_address' => '127.0.0.1',
'failure_reason' => 'unknown_email',
'created_at' => now()->subMinutes(5),
]);
}
$this->postJson('/api/auth/login', [
'email' => 'ok@example.ru',
'password' => 'right-password',
])->assertOk();
});
test('IP-lockout: окно 1 час — старые записи (>1ч) не блокируют', function () {
User::factory()->create([
'tenant_id' => $this->tenant->id,
'email' => 'old-fails@example.ru',
'password_hash' => Hash::make('right-password'),
]);
// 15 неудач, но старше часа → не учитываются.
for ($i = 0; $i < 15; $i++) {
DB::table('auth_log')->insert([
'actor_type' => 'tenant_user',
'event' => 'login_failed',
'email' => "old{$i}@example.ru",
'ip_address' => '127.0.0.1',
'failure_reason' => 'unknown_email',
'created_at' => now()->subHours(2),
]);
}
$this->postJson('/api/auth/login', [
'email' => 'old-fails@example.ru',
'password' => 'right-password',
])->assertOk();
});