WS-2: Magic-Link für Firmen & Pressekontakte vereinheitlicht + Schreibzugriff

Magic-Link und Pressekontakt-Zugang zu einer Seite (/anmeldelink) zusammengeführt;
altes Login-Modal entfernt, /pressekontakt-zugang leitet weiter.

- ContactAccessService deckt jetzt Firmen-E-Mail UND Pressekontakt-E-Mail ab,
  portalübergreifend (ohne PortalScope). Eine E-Mail mehrfach hinterlegt → genau
  ein Account, dem alle Firmen + Kontakte zugeordnet werden.
- Zugeordnete Firmen erhalten Pivot-Rolle 'responsible' (Schreibzugriff auf
  Stammdaten, Kontakte, Pressemitteilungen) statt nur 'member'; bestehende
  Lese-Pivots werden hochgestuft, Owner bleiben unangetastet.
- Neuer Login-Listener (SyncCompanyMembershipsOnLogin) frischt die Zuordnungen
  bei JEDEM Login (Magic-Link, Passwort, Google) auf – auch nachträglich (API)
  hinzugekommene Firmen/Kontakte mit gleicher E-Mail greifen.
- Auth-Bereich erzwingt Hellmodus: aus dem Portal übernommene .dark-Klasse wird
  am <html> entfernt (Login war im Dark Mode hängengeblieben).
- Tests: Firmen-E-Mail-Login, Multi-Firmen-Aggregation, Schreibzugriff/Upgrade,
  Per-Login-Re-Sync, Auth-Hellmodus. Sicherheits-Doku aktualisiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-16 12:55:49 +00:00
parent 068a5a4b49
commit 6c6b9e0f26
12 changed files with 587 additions and 327 deletions

View file

@ -8,13 +8,13 @@ use Illuminate\Support\Facades\Mail;
use Livewire\Volt\Volt as LivewireVolt;
use Tests\TestCase;
test('user can request a magic login link from login page', function () {
test('user can request a magic login link from the magic-link page', function () {
Mail::fake();
$user = User::factory()->create(['is_active' => true]);
LivewireVolt::test('auth.login')
->set('magicEmail', $user->email)
->call('sendMagicLink')
LivewireVolt::test('auth.magic-link')
->set('email', $user->email)
->call('requestLink')
->assertHasNoErrors();
Mail::assertSent(MagicLoginLink::class, function (MagicLoginLink $mail) use ($user) {
@ -27,25 +27,24 @@ test('user can request a magic login link from login page', function () {
expect($magicLink->token_hash)->toHaveLength(64);
});
test('the magic link modal validates its own email field and closes on success', function () {
test('the magic link form validates its email field and clears on success', function () {
Mail::fake();
$user = User::factory()->create(['is_active' => true]);
// Leere Eingabe → Validierungsfehler, keine Mail.
LivewireVolt::test('auth.login')
->set('magicEmail', '')
->call('sendMagicLink')
->assertHasErrors(['magicEmail']);
LivewireVolt::test('auth.magic-link')
->set('email', '')
->call('requestLink')
->assertHasErrors(['email']);
Mail::assertNothingSent();
// Gültige Eingabe → Feld wird geleert, Schließen-Event wird ausgelöst.
LivewireVolt::test('auth.login')
->set('magicEmail', $user->email)
->call('sendMagicLink')
// Gültige Eingabe → Feld wird geleert.
LivewireVolt::test('auth.magic-link')
->set('email', $user->email)
->call('requestLink')
->assertHasNoErrors()
->assertSet('magicEmail', '')
->assertDispatched('magic-link-sent');
->assertSet('email', '');
});
test('magic link requests are rate limited per email', function () {
@ -54,16 +53,16 @@ test('magic link requests are rate limited per email', function () {
$user = User::factory()->create(['is_active' => true]);
foreach (range(1, 3) as $ignored) {
LivewireVolt::test('auth.login')
->set('magicEmail', $user->email)
->call('sendMagicLink')
LivewireVolt::test('auth.magic-link')
->set('email', $user->email)
->call('requestLink')
->assertHasNoErrors();
}
LivewireVolt::test('auth.login')
->set('magicEmail', $user->email)
->call('sendMagicLink')
->assertHasErrors(['magicEmail']);
LivewireVolt::test('auth.magic-link')
->set('email', $user->email)
->call('requestLink')
->assertHasErrors(['email']);
Mail::assertSent(MagicLoginLink::class, 3);
});
@ -75,9 +74,9 @@ test('admin can login with a valid magic link and lands on admin dashboard', fun
$user = User::factory()->create(['is_active' => true]);
$user->assignRole('admin');
LivewireVolt::test('auth.login')
->set('magicEmail', $user->email)
->call('sendMagicLink');
LivewireVolt::test('auth.magic-link')
->set('email', $user->email)
->call('requestLink');
$sentMail = null;
@ -107,9 +106,9 @@ test('customer is redirected to me dashboard after magic link login', function (
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
LivewireVolt::test('auth.login')
->set('magicEmail', $customer->email)
->call('sendMagicLink');
LivewireVolt::test('auth.magic-link')
->set('email', $customer->email)
->call('requestLink');
$sentMail = null;
Mail::assertSent(MagicLoginLink::class, function (MagicLoginLink $mail) use (&$sentMail) {