presseportale/resources/views/livewire/auth/reset-password.blade.php
Kevin Adametz bae8347f6c security: Sicherheitsaudit 07.10.2026 (S-01–S-06) behoben, Passkeys und Zweitfaktor-Pflicht für Admins
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>
2026-10-07 16:09:35 +00:00

113 lines
3.7 KiB
PHP

<?php
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Component;
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Neues Passwort vergeben', 'eyebrow' => 'Passwort zurücksetzen'])] class extends Component {
#[Locked]
public string $token = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
/**
* Mount the component.
*/
public function mount(string $token): void
{
$this->token = $token;
$this->email = request()->string('email');
}
/**
* Reset the password for the given user.
*/
public function resetPassword(): void
{
$this->validate([
'token' => ['required'],
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
]);
$status = Password::reset(
$this->only('email', 'password', 'password_confirmation', 'token'),
function ($user) {
$user->forceFill([
'password' => Hash::make($this->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
if ($status != Password::PasswordReset) {
$this->addError('email', __($status));
return;
}
// Alle offenen Sessions sind beendet (RevokeAccessAfterPasswordReset);
// API-Tokens bleiben bewusst gültig (Sicherheitsaudit 07.10.2026, S-04).
Session::flash('status', __('Ihr Passwort wurde zurückgesetzt. Alle angemeldeten Geräte wurden abgemeldet. Bitte prüfen Sie nach der Anmeldung unter „Sicherheit“ auch Ihre API-Tokens.'));
$this->redirectRoute('login', navigate: true);
}
}; ?>
<div>
<p class="text-[13.5px] text-ink-2 leading-[1.6] !-mt-4 mb-6">
{{ __('Vergeben Sie ein neues Passwort für Ihr Konto. Mindestens 8 Zeichen, idealerweise eine Kombination aus Buchstaben, Zahlen und Sonderzeichen.') }}
</p>
<form wire:submit="resetPassword" class="space-y-[18px]" novalidate>
<div>
<label class="field-label" for="auth-email">{{ __('E-Mail-Adresse') }}</label>
<input
id="auth-email"
type="email"
wire:model="email"
required
autocomplete="email"
class="field-input"
@error('email') aria-invalid="true" @enderror
/>
@error('email')
<p class="field-error">{{ $message }}</p>
@enderror
</div>
<x-password-input
name="password"
:label="__('Neues Passwort')"
autocomplete="new-password"
:placeholder="__('Mindestens 8 Zeichen')"
/>
<x-password-input
name="password_confirmation"
:label="__('Passwort bestätigen')"
autocomplete="new-password"
:placeholder="__('Neues Passwort wiederholen')"
:show-error="false"
/>
<button type="submit" class="auth-btn-primary !mt-[22px]" wire:loading.attr="disabled" wire:target="resetPassword">
<span wire:loading.remove wire:target="resetPassword">{{ __('Passwort zurücksetzen') }}</span>
<span wire:loading wire:target="resetPassword">{{ __('Passwort wird gespeichert …') }}</span>
</button>
</form>
</div>