type OriginRequest = Pick; type OriginConfiguration = { websiteUrl?: string; additionalOrigins?: string; allowAnyOrigin?: boolean; }; function parseOrigin(value: string): URL { let parsed: URL; try { parsed = new URL(value); } catch { throw new Error("Invalid request origin"); } if ( !["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password ) { throw new Error("Invalid request origin"); } return parsed; } const originOf = (value: string) => parseOrigin(value).origin; /** Next may build request.url using the listening address rather than the LAN host. */ export function requestOrigin( request: Pick, websiteUrl?: string, ): string { const url = new URL(request.url); const host = request.headers.get("host"); if (!host) return originOf(url.origin); // Use the direct request's authority, including its port. Forwarded hosts are // intentionally excluded; proxy deployments can configure their public URL. if (/[\s/\\?#@,]/.test(host)) throw new Error("Invalid request origin"); const direct = parseOrigin(`${url.protocol}//${host}`); // Preserve HTTPS when a configured public host terminates TLS at a proxy. // A different configured host must not redirect a LAN login to localhost. if (websiteUrl) { const configured = parseOrigin(websiteUrl); if (configured.host === direct.host) return configured.origin; } return direct.origin; } export function verifyOrigin( request: OriginRequest, configuration: OriginConfiguration = { websiteUrl: process.env.MINEGEO_URL, additionalOrigins: process.env.MINEGEO_ALLOWED_ORIGINS, allowAnyOrigin: process.env.MINEGEO_ALLOW_ANY_ORIGIN === "true", }, ) { if (request.method === "GET" || request.method === "HEAD") return; const origin = request.headers.get("origin"); if (origin !== null) { if (configuration.allowAnyOrigin) return; const parsed = parseOrigin(origin); if (parsed.pathname !== "/" || parsed.search || parsed.hash) { throw new Error("Invalid request origin"); } const allowed = new Set([requestOrigin(request)]); for (const value of [ configuration.websiteUrl, ...(configuration.additionalOrigins?.split(",") ?? []), ]) { if (value?.trim()) allowed.add(originOf(value.trim())); } if (!allowed.has(originOf(origin))) throw new Error("Invalid request origin"); } else if ( !request.headers.get("authorization") && !request.headers.get("content-type")?.includes("application/json") ) { throw new Error("JSON request required"); } }