Fix /api/overpass 403 behind Caddy reverse proxy

The same-origin check compared the browser's Origin header against
new URL(request.url), but inside the Planner container request.url
is http://planner:3001/... while the browser sends Origin
https://planner.trails.cool — so production always 403'd.

Trust Caddy's X-Forwarded-Host and X-Forwarded-Proto headers when
present (both are set by Caddy's reverse_proxy directive by default)
to reconstruct the external origin the browser actually connected to.
Falls back to request.url for dev and any direct (non-proxied) access.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-18 02:21:31 +02:00
parent 97ad15db98
commit e11c9ab58b
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9

View file

@ -50,9 +50,16 @@ export async function action({ request }: Route.ActionArgs) {
return new Response("Method not allowed", { status: 405 });
}
// Same-origin check. Trust Caddy's X-Forwarded-* headers when present so
// the check works behind the reverse proxy (request.url inside the container
// is http://planner:3001/..., but the browser Origin is https://planner.trails.cool).
const origin = request.headers.get("origin");
const requestUrl = new URL(request.url);
const expectedOrigin = `${requestUrl.protocol}//${requestUrl.host}`;
const forwardedHost = request.headers.get("x-forwarded-host");
const forwardedProto = request.headers.get("x-forwarded-proto");
const host = forwardedHost ?? requestUrl.host;
const proto = forwardedProto ?? requestUrl.protocol.replace(":", "");
const expectedOrigin = `${proto}://${host}`;
if (!origin || origin !== expectedOrigin) {
return new Response("Forbidden", { status: 403 });
}