auth/security: API-Firmenzuordnung nur für Mitglieder oder Allowlist, Google-Vorabübernahme geschlossen, gemeinsamer Login-Abschluss (Umsetzungsaudit 29.09.: R-01, K-04, K-07, K-08, B-10)

- R-01: ApiNewsroomAssignment hängt an eine bestehende Firma nur noch Owner,
  Mitglieder oder Nutzer aus NEWSROOM_API_ASSIGNMENT_USER_IDS; Pivot mit
  granted_via = api; Befehl company-memberships:api-assignments (Liste,
  --remove mit Vorschau)
- K-04: OAuth-Verifikation eines offenen Kontos verwirft Passwort,
  remember_token und Sanctum-Tokens; SessionRevocation + Middleware
  EnsureSessionIsNotRevoked widerruft alte Sessions (Redis-tauglich)
- K-08: LoginCompletion für Passwort, Magic Link und Google — last_login_at
  und Anmeldemethode auch nach 2FA, Landeziel über die Challenge, Google
  ohne erzwungenes remember, magic-login ohne guest für eigene Links
- B-10: eigener Magic-Link-Zweck image_confirmation (Migration)
- K-07: legalAcceptanceVersions #[Locked], Versionswechsel beim Absenden
  wird abgelehnt, Gate-Abfragen je Request zwischengespeichert

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-02 13:09:00 +00:00
parent dfb0513e73
commit 1d417f87f7
29 changed files with 1287 additions and 73 deletions

View file

@ -1,6 +1,7 @@
<?php
use App\Models\User;
use App\Services\Auth\LoginCompletion;
use App\Support\LoginRedirect;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Support\Facades\Auth;
@ -49,10 +50,7 @@ new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Willkommen zu
// 2FA aktiv: nicht einloggen, sondern in Fortifys Challenge übergeben
// (Session-Vertrag login.id/login.remember wie RedirectIfTwoFactorAuthenticatable).
if ($user->hasEnabledTwoFactorAuthentication()) {
Session::put([
'login.id' => $user->getKey(),
'login.remember' => $this->remember,
]);
app(LoginCompletion::class)->handOffToTwoFactor(request(), $user, 'password', $this->remember);
$this->redirect(route('two-factor.challenge'));
@ -78,10 +76,7 @@ new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Willkommen zu
]);
}
$user->update([
'last_login_at' => now(),
'last_login_ip' => request()->ip(),
]);
app(LoginCompletion::class)->recordLogin($user, request());
Session::regenerate();