presseportale/resources/views/livewire/auth/forgot-password.blade.php
Kevin Adametz 89550dac99 Security (R6.2): Haertung nach Audit
- Security-Header-Middleware (nosniff, X-Frame-Options, Referrer-/
  Permissions-Policy) global, proxy-unabhaengig; CSP bewusst separat.
- Firmenlogo-Upload im Create-Formular durch den ImageService-Pfad
  ersetzt (Mime-Whitelist ohne SVG, Re-Encode, UUID-Name) - vorher
  rohe store()-Ablage ohne Server-Validierung (SVG/XSS moeglich).
- Rate-Limits ergaenzt: Passwort-Reset (3/E-Mail + 15/IP pro Std.),
  Registrierung (10/IP pro Std.), throttle auf magic-login- und
  pm-vorschau-Consume-Routen (Defense-in-Depth).
- EnsureUserIsCustomer als persistente Livewire-Middleware
  (symmetrisch zum Admin-Gate, greift auch bei Folge-Requests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 13:43:59 +00:00

95 lines
3.5 KiB
PHP

<?php
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Passwort vergessen', 'eyebrow' => 'Passwort zurücksetzen', 'topRightLabel' => 'Doch erinnert?', 'topRightLinkText' => 'Zur Anmeldung', 'topRightLinkHref' => '/login'])] class extends Component {
public string $email = '';
/**
* Send a password reset link to the provided email address.
*/
public function sendPasswordResetLink(): void
{
$this->validate([
'email' => ['required', 'string', 'email'],
]);
// Drosseln wie beim Anmeldelink: pro E-Mail (Mail-Fluten eines
// Postfachs) und pro IP (Durchprobieren vieler Adressen). Der
// Password-Broker drosselt zusätzlich nur pro Nutzer (60s).
$this->ensureIsNotRateLimited();
RateLimiter::hit($this->throttleKey(), 3600);
RateLimiter::hit($this->ipThrottleKey(), 3600);
Password::sendResetLink($this->only('email'));
session()->flash('status', __('A reset link will be sent if the account exists.'));
}
protected function ensureIsNotRateLimited(): void
{
$withinEmail = ! RateLimiter::tooManyAttempts($this->throttleKey(), 3);
$withinIp = ! RateLimiter::tooManyAttempts($this->ipThrottleKey(), 15);
if ($withinEmail && $withinIp) {
return;
}
throw ValidationException::withMessages([
'email' => __('Zu viele Anfragen. Bitte versuchen Sie es später erneut.'),
]);
}
protected function throttleKey(): string
{
return 'password-reset|'.Str::transliterate(Str::lower($this->email));
}
protected function ipThrottleKey(): string
{
return 'password-reset-ip|'.request()->ip();
}
}; ?>
<div>
<p class="text-[13.5px] text-ink-2 leading-[1.6] !-mt-4 mb-6">
{{ __('Geben Sie die E-Mail-Adresse Ihres Kontos ein. Sie erhalten innerhalb weniger Minuten einen Link, mit dem Sie ein neues Passwort vergeben können.') }}
</p>
<x-session-status class="mb-4" />
<form wire:submit="sendPasswordResetLink" 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
autofocus
autocomplete="username"
class="field-input"
placeholder="redaktion@ihr-unternehmen.de"
@error('email') aria-invalid="true" @enderror
/>
@error('email')
<p class="field-error">{{ $message }}</p>
@enderror
</div>
<button type="submit" class="auth-btn-primary !mt-[22px]" wire:loading.attr="disabled" wire:target="sendPasswordResetLink">
<span wire:loading.remove wire:target="sendPasswordResetLink">{{ __('Reset-Link senden') }}</span>
<span wire:loading wire:target="sendPasswordResetLink">{{ __('Link wird gesendet …') }}</span>
</button>
<div class="flex items-center justify-center pt-2 text-[12.5px] text-ink-3">
<a href="{{ route('login') }}" class="link-hub" wire:navigate>← {{ __('Zurück zur Anmeldung') }}</a>
</div>
</form>
</div>