create(); $this->post('/login', [ 'email' => $user->email, 'password' => 'password', ])->assertRedirect(route('dashboard', absolute: false)); $log = AuthAuditLog::query()->latest('id')->first(); expect($log)->not->toBeNull() ->and($log->event_type)->toBe('login') ->and($log->status)->toBe('success') ->and($log->identifier)->toBe(strtolower($user->email)) ->and($log->user_id)->toBe($user->id); }); it('logs login validation failures', function (): void { config()->set('app.debug', false); $this->from('/login')->post('/login', [ 'email' => '', 'password' => '', ])->assertRedirect('/login') ->assertSessionHasErrors(['email', 'password']); $log = AuthAuditLog::query()->latest('id')->first(); expect($log)->not->toBeNull() ->and($log->event_type)->toBe('login') ->and($log->status)->toBe('failed') ->and($log->reason)->toBe('validation_failed') ->and($log->metadata)->toMatchArray(['fields' => ['email', 'password']]); }); it('logs successful registration attempts', function (): void { Queue::fake(); $this->post('/register', [ 'email' => 'audit-register@example.com', ])->assertRedirect(route('setup.password.create', absolute: false)); $log = AuthAuditLog::query()->where('event_type', 'register')->latest('id')->first(); expect($log)->not->toBeNull() ->and($log->status)->toBe('success') ->and($log->identifier)->toBe('audit-register@example.com') ->and($log->reason)->toBe('user_created'); }); it('logs forgot password attempts', function (): void { Notification::fake(); $user = User::factory()->create(); $this->post('/forgot-password', ['email' => $user->email]) ->assertSessionHas('status'); $log = AuthAuditLog::query()->where('event_type', 'forgot_password')->latest('id')->first(); expect($log)->not->toBeNull() ->and($log->status)->toBe('success') ->and($log->identifier)->toBe(strtolower($user->email)) ->and($log->user_id)->toBe($user->id); }); it('logs successful password resets', function (): void { Notification::fake(); $user = User::factory()->create(); $this->post('/forgot-password', ['email' => $user->email]); $token = null; Notification::assertSentTo($user, ResetPassword::class, function (ResetPassword $notification) use (&$token): bool { $token = $notification->token; return true; }); $this->post('/reset-password', [ 'token' => $token, 'email' => $user->email, 'password' => 'password', 'password_confirmation' => 'password', ])->assertRedirect(route('login')); $log = AuthAuditLog::query()->where('event_type', 'reset_password')->latest('id')->first(); expect($log)->not->toBeNull() ->and($log->status)->toBe('success') ->and($log->identifier)->toBe(strtolower($user->email)) ->and($log->user_id)->toBe($user->id); }); it('limits the auth audit moderation page to admins', function (): void { $admin = User::factory()->create(['role' => 'admin']); $manager = User::factory()->create(['role' => 'manager']); AuthAuditLog::query()->create([ 'event_type' => 'login', 'identifier' => 'audit@example.com', 'status' => 'failed', 'reason' => 'invalid_credentials', 'ip' => '127.0.0.1', 'metadata' => ['via' => 'email'], 'created_at' => now(), ]); $this->actingAs($admin) ->get('/moderation/auth-audit') ->assertOk() ->assertInertia(fn (AssertableInertia $page) => $page ->component('Admin/AuthAudit') ->where('logs.data.0.event_type', 'login') ->where('logs.data.0.reason', 'invalid_credentials') ); $this->actingAs($manager) ->get('/moderation/auth-audit') ->assertForbidden(); });