feat(security): Sicherheitsseite zeigt dieses Gerät und die letzten Anmeldungen

Die Liste „Aktive Sessions“ las die sessions-Tabelle, Live speichert Sessions
in Redis — sie war immer leer. Jetzt: „Dieses Gerät“ (seit wann angemeldet)
und die letzten fünf Anmeldungen mit Anmeldeweg aus dem Aktivitätsverlauf,
ohne IP; Kachel „Anmeldungen in 30 Tagen“ (Roadmap 4.1).

Changelog: Verbessert
Public: Die Sicherheitsseite zeigt Ihre letzten Anmeldungen und wie Sie sich jeweils angemeldet haben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-09 13:35:54 +00:00
parent 53e3b270e0
commit 29e68330ca
2 changed files with 93 additions and 26 deletions

View file

@ -1,11 +1,13 @@
<?php
use App\Enums\ActivityType;
use App\Enums\Portal;
use App\Enums\PressReleaseStatus;
use App\Enums\SecurityChange;
use App\Mail\EmailChangeConfirmation;
use App\Mail\SecurityChangeNotice;
use App\Mail\SecurityConfirmationCode;
use App\Models\ActivityEvent;
use App\Models\Category;
use App\Models\Company;
use App\Models\Contact;
@ -185,12 +187,46 @@ test('customer security page renders password and email forms', function () {
Livewire::test('customer.security')
->assertSee('Konto-Sicherheit')
->assertSee('Letzter Login')
->assertSee('Aktive Sessions')
->assertSee('Geräte und Anmeldungen')
->assertSee('Passwort ändern')
->assertSee('E-Mail-Adresse ändern')
->assertSee('Zwei-Faktor-Authentifizierung');
});
test('the security page lists the own recent logins with their method, not those of others', function () {
/** @var TestCase $this */
$customer = User::factory()->create(['is_active' => true]);
$other = User::factory()->create(['is_active' => true]);
foreach ([['magic-link', 2], ['google', 3], ['passkey', 40]] as [$method, $daysAgo]) {
ActivityEvent::create([
'type' => ActivityType::UserLoggedIn,
'occurred_at' => now()->subDays($daysAgo),
'user_id' => $customer->id,
'description' => 'Anmeldung',
'meta' => ['method' => $method],
]);
}
ActivityEvent::create([
'type' => ActivityType::UserLoggedIn,
'occurred_at' => now()->subDay(),
'user_id' => $other->id,
'description' => 'Anmeldung',
'meta' => ['method' => 'impersonation'],
]);
$this->actingAs($customer);
Livewire::test('customer.security')
->assertSee('Dieses Gerät')
->assertSee('Anmeldung per Magic-Link')
->assertSee('Anmeldung mit Google')
->assertSee('Anmeldung mit Passkey')
->assertDontSee('Zugriff durch den Support')
->assertViewHas('loginsLast30Days', 2)
->assertViewHas('recentLogins', fn ($logins) => $logins->count() === 3);
});
test('customer can change own password through security page', function () {
/** @var TestCase $this */
$customer = User::factory()->create([