auth: Firmenrechte nur aus nachgewiesener Adresse, 2FA auf allen Login-Wegen (Audit Block 1, Deploy A)

Audit vom 11.09. gegen HEAD geprüft, Entwicklungsplan dazu unter
dev/2026-09-11-audit/ENTWICKLUNGSPLAN.md. Dieser Commit ist Deploy A:
die sechs kleinen Punkte aus Block 1 ohne Schemaänderung.

F01: Der Fortify-Endpunkt PUT /user/profile-information tauschte die
E-Mail sofort und setzte die Verifizierung zurück; der Login-Sync vergab
danach Firmenrechte allein nach E-Mail-Treffer. Feature abgeschaltet
(die Kunden-UI hat den pending_email-Prozess), und der Sync verlangt
jetzt ein verifiziertes, aktives Konto – für alle Aufrufer.

F02: Google-Login übergibt Konten mit bestätigtem TOTP in die Challenge,
wie Passwort- und Magic-Link-Login.

F03: showRecoveryCodes ist gesperrt; ein Client-Update konnte die
Recovery-Codes entschlüsseln lassen.

F08: Nutzer-Detailroute unter can:users:manage (persistente Livewire-
Middleware), Gutschriften autorisieren zusätzlich in der Action.

F09: Gate::before greift nur für aktive Super-Admins; deaktivierte
fallen auf die Gates mit is_active-Prüfung zurück.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-18 10:08:55 +00:00
parent 24c1bbf500
commit 0ab53bfa5e
20 changed files with 2229 additions and 69 deletions

View file

@ -101,9 +101,18 @@ class ContactAccessService
* (Magic-Link, Passwort, Google) – so greifen auch nachträglich (z. B. per
* API) hinzugekommene Firmen/Kontakte mit derselben E-Mail. Rein additiv,
* legt kein Konto an und verschickt keine Mail.
*
* Grundlage ist ausschließlich eine NACHGEWIESENE Adresse (Audit F01): ein
* Konto, das seine E-Mail gerade auf eine fremde Firmen-Adresse gewechselt
* hat, ist unverifiziert und erhält nichts – der Login-Event feuert auch
* vor der Verifizierungs-Weiche. Deaktivierte Konten ebenfalls nicht.
*/
public function syncMembershipsForEmail(User $user): void
{
if (! $user->is_active || ! $user->hasVerifiedEmail()) {
return;
}
$email = mb_strtolower(trim((string) $user->email));
if ($email === '') {