36 lines
1.5 KiB
TypeScript
36 lines
1.5 KiB
TypeScript
/** Hosts the API already trusts as browser origins: loopback and private networks. */
|
|
export function isPrivateOrLocalHost(host: string): boolean {
|
|
if (isLoopbackHost(host)) return true;
|
|
if (host.endsWith(".local")) return true;
|
|
if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(host)) return true;
|
|
if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host)) return true;
|
|
if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(host)) return true;
|
|
if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d{1,3}\.\d{1,3}$/.test(host)) return true;
|
|
return false;
|
|
}
|
|
|
|
export function isLoopbackHost(host: string): boolean {
|
|
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
}
|
|
|
|
/** The web origin that browser-facing URLs (OAuth returns, screen links) should
|
|
* point at for this request. A browser opened over a loopback, private-network,
|
|
* or explicitly trusted origin gets its own origin back so a phone on the LAN
|
|
* or Tailscale is not sent to 127.0.0.1; any other caller gets WEB_ORIGIN. */
|
|
export function requestWebOrigin(
|
|
origin: string | undefined,
|
|
webOrigin: string,
|
|
extraTrustedOrigins: readonly string[] = [],
|
|
): string {
|
|
if (!origin) return webOrigin;
|
|
try {
|
|
const url = new URL(origin);
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") return webOrigin;
|
|
if (url.origin === new URL(webOrigin).origin) return url.origin;
|
|
if (extraTrustedOrigins.includes(url.origin)) return url.origin;
|
|
return isPrivateOrLocalHost(url.hostname) ? url.origin : webOrigin;
|
|
} catch {
|
|
return webOrigin;
|
|
}
|
|
}
|