create(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => 'logout-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, ]); $this->postJson('/api/auth/login', [ 'email' => 'logout-log@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); $this->postJson('/api/auth/logout')->assertOk(); $row = DB::table('auth_log') ->where('event', 'logout') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->tenant_id)->toBe($tenant->id); }); it('register writes auth_log event=register_success', function () { Tenant::factory()->create(); $response = $this->postJson('/api/auth/register', [ 'email' => 'reg-log-test@example.ru', 'password' => 'fresh-pass-123', 'accept_offer' => true, 'accept_pdn' => true, ]); $response->assertStatus(201); $user = User::where('email', 'reg-log-test@example.ru')->first(); $row = DB::table('auth_log') ->where('event', 'register_success') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and($row->email)->toBe('reg-log-test@example.ru'); }); it('2fa verify success writes auth_log event=2fa_verify_success', function () { $tenant = Tenant::factory()->create(); $google2fa = new Google2FA; $secret = $google2fa->generateSecretKey(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-log-success@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => true, 'totp_secret' => $secret, ]); // Step 1: login to set pending_user_id in session. $this->postJson('/api/auth/login', [ 'email' => '2fa-log-success@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); // Step 2: verify with valid code. $validCode = $google2fa->getCurrentOtp($secret); $this->postJson('/api/auth/2fa/verify', [ 'code' => $validCode, ])->assertOk(); $row = DB::table('auth_log') ->where('event', '2fa_verify_success') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->tenant_id)->toBe($tenant->id); }); it('2fa verify failed writes auth_log event=2fa_verify_failed', function () { $tenant = Tenant::factory()->create(); $google2fa = new Google2FA; $secret = $google2fa->generateSecretKey(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-log-fail@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => true, 'totp_secret' => $secret, ]); // Step 1: login to set pending_user_id in session. $this->postJson('/api/auth/login', [ 'email' => '2fa-log-fail@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); // Step 2: verify with wrong code. $this->postJson('/api/auth/2fa/verify', [ 'code' => '000000', ])->assertStatus(422); $row = DB::table('auth_log') ->where('event', '2fa_verify_failed') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and($row->failure_reason)->toBe('invalid_code'); }); it('2fa recovery used writes auth_log event=2fa_recovery_used', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-recovery-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => true, 'totp_secret' => 'JBSWY3DPEHPK3PXP', ]); DB::table('user_recovery_codes')->insert([ 'user_id' => $user->id, 'code_hash' => Hash::make('abcd1234'), 'used_at' => null, ]); // Login to set pending_user_id. $this->postJson('/api/auth/login', [ 'email' => '2fa-recovery-log@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); $this->postJson('/api/auth/2fa/recovery-use', [ 'code' => 'ABCD-1234', ])->assertOk(); $row = DB::table('auth_log') ->where('event', '2fa_recovery_used') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->tenant_id)->toBe($tenant->id); }); it('2fa recovery failed writes auth_log event=2fa_recovery_failed', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-recovery-fail-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => true, 'totp_secret' => 'JBSWY3DPEHPK3PXP', ]); DB::table('user_recovery_codes')->insert([ 'user_id' => $user->id, 'code_hash' => Hash::make('abcd1234'), 'used_at' => null, ]); // Login to set pending_user_id. $this->postJson('/api/auth/login', [ 'email' => '2fa-recovery-fail-log@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); $this->postJson('/api/auth/2fa/recovery-use', [ 'code' => 'WRONG-9999', ])->assertStatus(422); $row = DB::table('auth_log') ->where('event', '2fa_recovery_failed') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and($row->failure_reason)->toBe('invalid_or_used'); }); it('2fa setup init writes auth_log event=2fa_setup_init', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-init-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => false, 'totp_secret' => null, ]); $this->actingAs($user); $this->postJson('/api/2fa/init')->assertOk(); $row = DB::table('auth_log') ->where('event', '2fa_setup_init') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->tenant_id)->toBe($tenant->id); }); it('2fa setup confirm writes auth_log event=2fa_setup_confirmed', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-confirm-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => false, 'totp_secret' => null, ]); $this->actingAs($user); $this->postJson('/api/2fa/init')->assertOk(); $secret = session('auth.pending_totp_secret'); $google2fa = new Google2FA; $code = $google2fa->getCurrentOtp($secret); $this->postJson('/api/2fa/confirm', ['code' => $code])->assertOk(); $row = DB::table('auth_log') ->where('event', '2fa_setup_confirmed') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->tenant_id)->toBe($tenant->id); }); it('2fa disable success writes auth_log event=2fa_disabled', function () { $tenant = Tenant::factory()->create(); $google2fa = new Google2FA; $secret = $google2fa->generateSecretKey(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-disabled-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => true, 'totp_secret' => $secret, ]); $this->actingAs($user); $this->postJson('/api/2fa/disable', ['password' => 'secret-pass-123'])->assertOk(); $row = DB::table('auth_log') ->where('event', '2fa_disabled') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->tenant_id)->toBe($tenant->id); }); it('2fa disable wrong password writes auth_log event=2fa_disable_failed', function () { $tenant = Tenant::factory()->create(); $google2fa = new Google2FA; $secret = $google2fa->generateSecretKey(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-disable-fail-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => true, 'totp_secret' => $secret, ]); $this->actingAs($user); $this->postJson('/api/2fa/disable', ['password' => 'wrong-password'])->assertStatus(422); $row = DB::table('auth_log') ->where('event', '2fa_disable_failed') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and($row->failure_reason)->toBe('invalid_password'); }); it('2fa regenerate recovery codes writes auth_log event=2fa_recovery_regenerated', function () { $tenant = Tenant::factory()->create(); $google2fa = new Google2FA; $secret = $google2fa->generateSecretKey(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => '2fa-regen-log@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'is_active' => true, 'totp_enabled' => true, 'totp_secret' => $secret, ]); $this->actingAs($user); $this->postJson('/api/2fa/regenerate-recovery-codes', ['password' => 'secret-pass-123'])->assertOk(); $row = DB::table('auth_log') ->where('event', '2fa_recovery_regenerated') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->tenant_id)->toBe($tenant->id); }); it('password_reset_requested writes auth_log with user_id for known email', function () { Notification::fake(); $tenant = Tenant::factory()->create(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => 'pr-known-log@example.ru', 'password_hash' => Hash::make('old-pass-1234'), 'is_active' => true, ]); $this->postJson('/api/auth/forgot', [ 'email' => 'pr-known-log@example.ru', ])->assertOk(); $row = DB::table('auth_log') ->where('event', 'password_reset_requested') ->where('email', 'pr-known-log@example.ru') ->latest('id') ->first(); expect($row)->not->toBeNull() ->and((int) $row->user_id)->toBe($user->id) ->and($row->failure_reason)->toBeNull(); }); it('password_reset_requested writes auth_log with unknown_email failure_reason for unknown email', function () { Notification::fake(); $this->postJson('/api/auth/forgot', [ 'email' => 'no-such-pr-log@example.ru', ])->assertOk(); $row = DB::table('auth_log') ->where('event', 'password_reset_requested') ->where('email', 'no-such-pr-log@example.ru') ->latest('id') ->first(); expect($row)->not->toBeNull() ->and($row->user_id)->toBeNull() ->and($row->failure_reason)->toBe('unknown_email'); }); it('password_reset_completed writes auth_log on successful token reset', function () { $tenant = Tenant::factory()->create(); $user = User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => 'pr-completed-log@example.ru', 'password_hash' => Hash::make('old-pass-1234'), 'is_active' => true, ]); $token = Password::createToken($user); $this->postJson('/api/auth/reset-password', [ 'token' => $token, 'email' => 'pr-completed-log@example.ru', 'password' => 'new-strong-pass-1234', 'password_confirmation' => 'new-strong-pass-1234', ])->assertOk(); $row = DB::table('auth_log') ->where('event', 'password_reset_completed') ->where('user_id', $user->id) ->latest('id') ->first(); expect($row)->not->toBeNull() ->and($row->email)->toBe('pr-completed-log@example.ru'); }); it('password_reset_failed writes auth_log on invalid token', function () { $tenant = Tenant::factory()->create(); User::factory()->create([ 'tenant_id' => $tenant->id, 'email' => 'pr-failed-log@example.ru', 'password_hash' => Hash::make('old-pass-1234'), 'is_active' => true, ]); $this->postJson('/api/auth/reset-password', [ 'token' => 'invalid-token-zzz', 'email' => 'pr-failed-log@example.ru', 'password' => 'new-strong-pass-1234', 'password_confirmation' => 'new-strong-pass-1234', ])->assertStatus(422); $row = DB::table('auth_log') ->where('event', 'password_reset_failed') ->where('email', 'pr-failed-log@example.ru') ->latest('id') ->first(); expect($row)->not->toBeNull() ->and($row->failure_reason)->not->toBeNull(); });