File
Blob: src/worker/api/config.ts
| 1 | import type { AppContext } from "@/worker/hono"; |
| 2 | import { HttpError } from "@/worker/http"; |
| 3 | |
| 4 | export type SocialProvider = "github" | "google"; |
| 5 | |
| 6 | interface ClientConfigResponse { |
| 7 | turnstileSiteKey: string; |
| 8 | issuer: string; |
| 9 | socialProviders: SocialProvider[]; |
| 10 | operatorName: string; |
| 11 | operatorContactEmail: string; |
| 12 | } |
| 13 | |
| 14 | // /api/config gates the public Turnstile site key the React app needs to |
| 15 | // render the widget. A missing TURNSTILE_SITE_KEY is a deployment error, |
| 16 | // not a runtime feature toggle — the verifier already fails closed on a |
| 17 | // missing TURNSTILE_SECRET_KEY (services/turnstile.ts), and surfacing 503 |
| 18 | // here lets the sign-in / invite-accept pages show an actionable error |
| 19 | // instead of an indefinite "Loading verification..." state. |
| 20 | // |
| 21 | // Also advertises the OIDC issuer (so the landing-page metadata reflects |
| 22 | // the actual deployment instead of hardcoding a host), the list of |
| 23 | // configured social providers (so the UI only renders buttons that can |
| 24 | // successfully complete the flow), and the operator identity rendered on |
| 25 | // /privacy and /terms (so the same React build can serve different |
| 26 | // deployments without hardcoding a brand name). Providers require BOTH |
| 27 | // client id AND client secret to count as configured; a half-configured |
| 28 | // provider would fail at the IdP round-trip. |
| 29 | export const handleConfig = (c: AppContext): Response => { |
| 30 | const siteKey = c.env.TURNSTILE_SITE_KEY?.trim(); |
| 31 | if (!siteKey) { |
| 32 | throw new HttpError(503, "turnstile_unavailable", "Human verification is not configured on this server."); |
| 33 | } |
| 34 | const operatorName = c.env.OPERATOR_NAME?.trim(); |
| 35 | const operatorContactEmail = c.env.OPERATOR_CONTACT_EMAIL?.trim(); |
| 36 | if (!operatorName || !operatorContactEmail) { |
| 37 | throw new HttpError(503, "operator_unconfigured", "Operator identity is not configured on this server."); |
| 38 | } |
| 39 | const socialProviders: SocialProvider[] = []; |
| 40 | if (c.env.GITHUB_OAUTH_CLIENT_ID?.trim() && c.env.GITHUB_OAUTH_CLIENT_SECRET?.trim()) { |
| 41 | socialProviders.push("github"); |
| 42 | } |
| 43 | if (c.env.GOOGLE_OAUTH_CLIENT_ID?.trim() && c.env.GOOGLE_OAUTH_CLIENT_SECRET?.trim()) { |
| 44 | socialProviders.push("google"); |
| 45 | } |
| 46 | const response: ClientConfigResponse = { |
| 47 | turnstileSiteKey: siteKey, |
| 48 | issuer: c.var.issuer, |
| 49 | socialProviders, |
| 50 | operatorName, |
| 51 | operatorContactEmail, |
| 52 | }; |
| 53 | return c.json(response); |
| 54 | }; |