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>
This commit is contained in:
Kevin Adametz 2026-07-03 13:43:59 +00:00
parent acb2a19aff
commit 89550dac99
9 changed files with 212 additions and 9 deletions

View file

@ -1,6 +1,9 @@
<?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;
@ -16,10 +19,41 @@ new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Passwort verg
'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>