Audit: dev/sicherheit/SICHERHEITSAUDIT-2026-10-07.md, Plan und Entscheidungen: dev/sicherheit/ENTWICKLUNGSPLAN-SICHERHEIT-2026-10-07.md. - S-01 Legacy-Rechnungen fremder Kunden nur mit billing:manage (auch Kundenroute). - S-02 Kontaktzuordnung auf der Firmenanzeige verlangt companies:write, $id gesperrt. - S-03 CompanyMembershipObserver: automatische Firmenrechte werden bei jeder Adress-/Zuordnungsänderung sofort neu bewertet; memberships:resync-auto. - S-04 Passwortwechsel beendet alle anderen Sessions (PasswordChanger), Reset alle Sessions und offene Login-Links; Fortify PUT /user/password abgeschaltet. API-Tokens bleiben bestehen. - S-05 Google: email_verified Pflicht, feste Bindung über die Provider-ID; Erstverknüpfung bestehender Konten außerhalb gmail/hd nur per Bestätigungslink in derselben Browser-Session. - S-06 Frischer Identitätsnachweis (Passwort, App-Code, E-Mail-Code oder Passkey) vor E-Mail-Wechsel, erster 2FA-Einrichtung und Token-Erzeugung; Hinweis-Mails an die (bisherige) Kontoadresse. - Entscheidung 5: Passkeys (Fortify, @laravel/passkeys) für alle; Super-Admins und Admins bestätigen die Session für den Admin-Bereich per Passkey oder App-Code (EnsureStrongAdminAuthentication, Notfallschalter AUTH_REQUIRE_STRONG_ADMIN); Reset des zweiten Faktors durch Super-Admins. Neue Migration: create_passkeys_table. Suite 2.403 grün (1 übersprungen), Larastan ohne Meldung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
1.6 KiB
PHP
56 lines
1.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Auth;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Session;
|
|
|
|
/**
|
|
* Starke Anmeldung für den Admin-Bereich (Entscheidung 5, 07.10.2026).
|
|
*
|
|
* Super-Admins und Admins brauchen eine Session, die mit einem zweiten Faktor
|
|
* bestätigt wurde: Passkey (Login oder Bestätigung, Benutzerverifizierung per
|
|
* Biometrie/PIN ist Pflicht) oder Authenticator-Code (2FA-Challenge bzw.
|
|
* Bestätigung). Magic Link, Google und Passwort allein genügen nicht — sonst
|
|
* ließe sich die Pflicht über den bequemsten Anmeldeweg umgehen. Die Marke
|
|
* gilt für die Lebensdauer der Session.
|
|
*/
|
|
class StrongAuthentication
|
|
{
|
|
public const SESSION_KEY = 'auth.strong';
|
|
|
|
public const INTENDED_KEY = 'auth.strong_intended';
|
|
|
|
public function isRequiredFor(User $user): bool
|
|
{
|
|
if (! config('auth.require_strong_admin_authentication', true)) {
|
|
return false;
|
|
}
|
|
|
|
return $user->is_super_admin || $user->hasRole('admin');
|
|
}
|
|
|
|
public function isSatisfiedBy(User $user): bool
|
|
{
|
|
$strong = Session::get(self::SESSION_KEY);
|
|
|
|
return is_array($strong) && (int) ($strong['user_id'] ?? 0) === (int) $user->getKey();
|
|
}
|
|
|
|
/**
|
|
* @param string $method passkey oder totp
|
|
*/
|
|
public function mark(User $user, string $method): void
|
|
{
|
|
Session::put(self::SESSION_KEY, [
|
|
'user_id' => $user->getKey(),
|
|
'method' => $method,
|
|
'at' => now()->getTimestamp(),
|
|
]);
|
|
}
|
|
|
|
public function hasPasskeys(User $user): bool
|
|
{
|
|
return $user->passkeys()->exists();
|
|
}
|
|
}
|