diff --git a/app/Http/Middleware/RedirectWwwToApex.php b/app/Http/Middleware/RedirectWwwToApex.php new file mode 100644 index 00000000..d72a5016 --- /dev/null +++ b/app/Http/Middleware/RedirectWwwToApex.php @@ -0,0 +1,37 @@ +getHost()); + + if (! str_starts_with($host, 'www.') || ! $request->isMethodSafe()) { + return $next($request); + } + + return redirect()->away( + $request->getScheme().'://'.substr($host, 4).$request->getRequestUri(), + 301 + ); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index efd30e35..240c54e2 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -2,6 +2,7 @@ use App\Http\Middleware\BasicAuthMiddleware; use App\Http\Middleware\LogApiUsage; +use App\Http\Middleware\RedirectWwwToApex; use App\Http\Middleware\RejectLegacyApiKeys; use App\Http\Middleware\SecurityHeaders; use App\Http\Middleware\SetCurrentPortal; @@ -28,6 +29,11 @@ return Application::configure(basePath: dirname(__DIR__)) ) ->withMiddleware(function (Middleware $middleware) { + // Kanonische Hosts: www. → 301 auf die Apex-Domain, so früh + // wie möglich (vor Session/Theme), damit www-Requests gar nicht erst + // gerendert werden. + $middleware->prepend(RedirectWwwToApex::class); + // Security-Header auf allen Antworten (Web + API), Proxy-unabhängig. $middleware->append(SecurityHeaders::class); diff --git a/tests/Feature/Web/WwwRedirectTest.php b/tests/Feature/Web/WwwRedirectTest.php new file mode 100644 index 00000000..fa5af879 --- /dev/null +++ b/tests/Feature/Web/WwwRedirectTest.php @@ -0,0 +1,32 @@ +get('https://www.presseecho.test/de/impressum?foo=bar') + ->assertStatus(301) + ->assertRedirect('https://presseecho.test/de/impressum?foo=bar'); + + $this->get('https://www.businessportal24.test/de') + ->assertStatus(301) + ->assertRedirect('https://businessportal24.test/de'); +}); + +test('apex hosts are not redirected', function () { + /** @var TestCase $this */ + $this->get('https://presseecho.test/de/impressum')->assertSuccessful(); +}); + +test('unsafe methods on www hosts are answered instead of redirected', function () { + /** @var TestCase $this */ + // POST darf nicht 301en (Body ginge verloren) — die App beantwortet den + // Request dank Host-Normalisierung auch auf dem www-Host selbst. + $this->post('https://www.presseecho.test/de/kontakt') + ->assertStatus(302); // Validierungs-Redirect der Kontakt-Route, kein 301 +});