File
Blob: src/worker/config.ts
| 1 | import { isLoopbackHost } from "@/worker/services/url"; |
| 2 | |
| 3 | // Canonicalize a configured URL down to its origin. Rejects values that |
| 4 | // would split discovery, ID-token `iss`, cookie domain, or invite URLs: |
| 5 | // non-http(s) schemes, non-loopback http, embedded credentials, query, |
| 6 | // hash, or any non-root pathname. Throw shape never echoes the offending |
| 7 | // value because env vars may carry username/password components. |
| 8 | const canonicalOrigin = (value: string, envName: string): string => { |
| 9 | let url: URL; |
| 10 | try { |
| 11 | url = new URL(value.trim()); |
| 12 | } catch { |
| 13 | throw new Error(`${envName} is not a valid URL.`); |
| 14 | } |
| 15 | if (url.protocol !== "https:" && url.protocol !== "http:") { |
| 16 | throw new Error(`${envName} must use http or https.`); |
| 17 | } |
| 18 | if (url.protocol === "http:" && !isLoopbackHost(url.hostname)) { |
| 19 | throw new Error(`${envName} with http: is allowed only on loopback hosts.`); |
| 20 | } |
| 21 | if (url.username || url.password || url.search || url.hash) { |
| 22 | throw new Error(`${envName} must not include path, query, hash, or credentials.`); |
| 23 | } |
| 24 | if (url.pathname !== "/" && url.pathname !== "") { |
| 25 | throw new Error(`${envName} must not include path, query, hash, or credentials.`); |
| 26 | } |
| 27 | return url.origin; |
| 28 | }; |
| 29 | |
| 30 | /** |
| 31 | * Resolve the public base URL the worker should advertise (OIDC `iss`, |
| 32 | * Better Auth `baseURL`, cookie domain). |
| 33 | * |
| 34 | * Resolution order: |
| 35 | * 1. `BETTER_AUTH_URL` env var if set and non-empty. |
| 36 | * 2. The request's own origin. |
| 37 | * |
| 38 | * Production always sets `BETTER_AUTH_URL` (wrangler.jsonc `vars`), so the |
| 39 | * fallback only fires in dev/test where the worker should reflect whatever |
| 40 | * hostname the request was made to. |
| 41 | */ |
| 42 | export const resolveBaseUrl = (env: Env, requestUrl: string): string => { |
| 43 | const configured = env.BETTER_AUTH_URL?.trim(); |
| 44 | if (configured) { |
| 45 | return canonicalOrigin(configured, "BETTER_AUTH_URL"); |
| 46 | } |
| 47 | return new URL(requestUrl).origin; |
| 48 | }; |
| 49 | |
| 50 | export const resolveIssuer = (env: Env, requestUrl: string): string => { |
| 51 | const configured = env.OIDC_ISSUER?.trim(); |
| 52 | if (configured) { |
| 53 | return canonicalOrigin(configured, "OIDC_ISSUER"); |
| 54 | } |
| 55 | return resolveBaseUrl(env, requestUrl); |
| 56 | }; |