File
Blob: src/worker/lib/origins.ts
| 1 | const ALLOWED_ORIGINS_ERROR = "ALLOWED_ORIGINS must be a comma-separated list of http(s) origins"; |
| 2 | |
| 3 | function normalizeConfiguredOrigin(rawOrigin: string): string { |
| 4 | let url: URL; |
| 5 | try { |
| 6 | url = new URL(rawOrigin); |
| 7 | } catch { |
| 8 | throw new Error(`${ALLOWED_ORIGINS_ERROR}: invalid origin "${rawOrigin}"`); |
| 9 | } |
| 10 | |
| 11 | if (url.protocol !== "http:" && url.protocol !== "https:") { |
| 12 | throw new Error(`${ALLOWED_ORIGINS_ERROR}: invalid protocol "${url.protocol}" in "${rawOrigin}"`); |
| 13 | } |
| 14 | |
| 15 | return url.origin; |
| 16 | } |
| 17 | |
| 18 | function normalizeRequestOrigin(origin: string): string | null { |
| 19 | try { |
| 20 | const url = new URL(origin); |
| 21 | return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null; |
| 22 | } catch { |
| 23 | return null; |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | export function getAllowedOrigins(env: Pick<Env, "ALLOWED_ORIGINS">): string[] { |
| 28 | const rawValue = env.ALLOWED_ORIGINS?.trim(); |
| 29 | if (!rawValue) { |
| 30 | throw new Error(`${ALLOWED_ORIGINS_ERROR}: value is missing`); |
| 31 | } |
| 32 | |
| 33 | const rawOrigins = rawValue |
| 34 | .split(",") |
| 35 | .map((origin) => origin.trim()) |
| 36 | .filter(Boolean); |
| 37 | if (rawOrigins.length === 0) { |
| 38 | throw new Error(`${ALLOWED_ORIGINS_ERROR}: value is empty`); |
| 39 | } |
| 40 | |
| 41 | return [...new Set(rawOrigins.map(normalizeConfiguredOrigin))]; |
| 42 | } |
| 43 | |
| 44 | export function isAllowedOrigin(origin: string | null | undefined, env: Pick<Env, "ALLOWED_ORIGINS">): boolean { |
| 45 | if (!origin) return false; |
| 46 | |
| 47 | const normalizedOrigin = normalizeRequestOrigin(origin); |
| 48 | if (!normalizedOrigin) return false; |
| 49 | |
| 50 | return getAllowedOrigins(env).includes(normalizedOrigin); |
| 51 | } |