auth: automatische Firmenrechte mit Herkunft und Entzug, Nachweis vor Sicherheitsaktionen (Audit Block 1, Deploy C)
F10: company_user.granted_via unterscheidet automatisch vergebene Rechte (E-Mail-Sync) von manuellen und aus dem Altbestand. Beim Login senkt der Sync automatische responsible-Rechte ohne aktuellen E-Mail- Treffer auf member (E2) — Mitarbeiterwechsel nehmen den Schreibzugriff mit, Lesezugriff bleibt. Das Admin-Formular markiert nur neue oder geänderte Zeilen als manual, unveränderte behalten ihre Herkunft. Bestandsprüfung: php artisan company-memberships:audit listet responsible-Zuordnungen ohne E-Mail-Grundlage (Tabelle ab Go-Live, CSV vollständig) und markiert mit --classify die erklärbaren als auto. Auf Live nach der Migration einmal mit --classify laufen lassen; nicht erklärbare Zeilen bleiben manual und damit geschützt. F03 (E3): Zwei-Faktor deaktivieren, Wiederherstellungs-Codes erneuern und die E-Mail-Änderung verlangen einen aktuellen App-Code oder einen Wiederherstellungs-Code (der dabei verbraucht wird); ohne 2FA das Passwort. Reine Magic-Link-Konten ohne Passwort (der Altbestand) können die Adresse weiter ändern — die neue Adresse muss ihren Bestätigungslink ohnehin einlösen. Fünf Fehlversuche pro Minute. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a7f991f01e
commit
62202a4b13
10 changed files with 596 additions and 24 deletions
|
|
@ -9,6 +9,7 @@ use App\Models\Contact;
|
|||
use App\Models\User;
|
||||
use App\Scopes\PortalScope;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
/**
|
||||
|
|
@ -106,6 +107,11 @@ class ContactAccessService
|
|||
* 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.
|
||||
*
|
||||
* Gegenrichtung (Audit F10, E2): automatisch vergebene `responsible`-Rechte,
|
||||
* deren E-Mail-Grundlage weggefallen ist (Adresse an Firma oder Kontakt
|
||||
* getauscht, Kontakt gelöscht), fallen auf `member` zurück. Manuell oder
|
||||
* aus dem Altbestand vergebene Rechte (`granted_via = manual`) bleiben.
|
||||
*/
|
||||
public function syncMembershipsForEmail(User $user): void
|
||||
{
|
||||
|
|
@ -121,12 +127,31 @@ class ContactAccessService
|
|||
|
||||
$contacts = $this->contactsFor($email);
|
||||
$companies = $this->companiesFor($email);
|
||||
$companyIds = $this->companyIdsFrom($contacts, $companies);
|
||||
|
||||
if ($contacts->isEmpty() && $companies->isEmpty()) {
|
||||
$this->revokeStaleAutomaticGrants($user, $companyIds);
|
||||
|
||||
if ($companyIds->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->linkMemberships($user, $this->companyIdsFrom($contacts, $companies), $contacts);
|
||||
$this->linkMemberships($user, $companyIds, $contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Senkt automatisch vergebene `responsible`-Pivots ohne aktuellen
|
||||
* E-Mail-Treffer auf `member`. Lesezugriff bleibt, Schreibzugriff geht.
|
||||
*
|
||||
* @param Collection<int, int> $currentCompanyIds
|
||||
*/
|
||||
private function revokeStaleAutomaticGrants(User $user, Collection $currentCompanyIds): void
|
||||
{
|
||||
DB::table('company_user')
|
||||
->where('user_id', $user->id)
|
||||
->where('role', 'responsible')
|
||||
->where('granted_via', 'auto')
|
||||
->whereNotIn('company_id', $currentCompanyIds->all())
|
||||
->update(['role' => 'member', 'updated_at' => now()]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -256,10 +281,11 @@ class ContactAccessService
|
|||
$linked = $user->companies()->withoutGlobalScope(PortalScope::class)->whereKey($companyId)->first();
|
||||
|
||||
if ($linked === null) {
|
||||
$user->companies()->attach($companyId, ['role' => 'responsible']);
|
||||
$user->companies()->attach($companyId, ['role' => 'responsible', 'granted_via' => 'auto']);
|
||||
} elseif ($linked->pivot->role === 'member') {
|
||||
// Nur den Read-only-Default hochstufen; owner/responsible bleiben.
|
||||
$user->companies()->updateExistingPivot($companyId, ['role' => 'responsible']);
|
||||
// Die Hochstufung ist automatisch und damit wieder rücknehmbar.
|
||||
$user->companies()->updateExistingPivot($companyId, ['role' => 'responsible', 'granted_via' => 'auto']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue