withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', channels: __DIR__.'/../routes/channels.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { $middleware->alias([ 'admin' => \App\Http\Middleware\EnsureAdmin::class, 'admin.host' => \App\Http\Middleware\RestrictAdminHost::class, 'customer.active' => \App\Http\Middleware\EnsureCustomerActive::class, 'operator.2fa' => \App\Http\Middleware\RequireOperatorTwoFactor::class, ]); // Runs before the whole route stack (incl. `auth`), so /admin on a public // hostname 404s instead of redirecting to /login and thereby revealing // that an operator console is hosted here. Self-scoped to /admin*. $middleware->prependToGroup('web', \App\Http\Middleware\RestrictAdminHost::class); // And WHO may reach it, by network address — the VPN plus whatever the // owner has listed in the console. The host guard above cannot answer // that question: a Host header is chosen by the caller. $middleware->prependToGroup('web', \App\Http\Middleware\RestrictConsoleNetwork::class); // Runs after the host guard: the console must stay reachable even while // the public site is switched off. $middleware->appendToGroup('web', \App\Http\Middleware\PublicSiteGate::class); // TLS is terminated by the reverse proxy (Zoraxy on the private LAN), so // without this Laravel sees plain http and builds http:// URLs into an // https page — every asset and redirect breaks as mixed content. // // X_FORWARDED_HOST is deliberately NOT trusted: the console is gated on // the request host (ADMIN_HOSTS), and trusting that header would let // anyone reach /admin through a public domain by sending // "X-Forwarded-Host: admin.…". Zoraxy passes the real Host through, so // nothing needs it. $middleware->trustProxies( at: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.1'], headers: Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO, ); // Stripe posts server-to-server with its own signature (no CSRF token). $middleware->validateCsrfTokens(except: ['webhooks/stripe']); }) ->withExceptions(function (Exceptions $exceptions): void { $exceptions->shouldRenderJsonWhen( fn (Request $request) => $request->is('api/*'), ); })->create();