import { env } from "cloudflare:workers"; import { viewerFromSessionCookie } from "../auth"; import { first } from "../db"; import { randomToken, sha256Hex } from "../crypto"; import { HttpError } from "../errors"; import { limitWrite } from "../limits"; import { hasPermission, loadRepoById, repoPermission } from "../repos"; import { codeKey, labelUrl, siteOrigin } from "./config"; import { CALLBACK_PATH } from "./access"; import { resolveSiteHost } from "./dispatch"; import { claimScratchSite } from "./scratch"; import { html } from "./static"; import { claimPage } from "./claim-page"; import type { SiteMeta } from "./config"; import type { Viewer } from "~/lib/types"; const CODE_TTL_SECONDS = 60; function page(status: number, title: string, body: string): Response { return html( status, `
Sign in to ${escapeHtml(env.FORGE_NAME)} in this browser, then return to the site and try again.
`, ); } async function allowed(site: SiteMeta, viewer: Viewer): PromiseMissing sign-in state.
"); const resolved = await resolveSiteHost(host.replace(/:\d+$/, "")); if (!resolved || resolved.meta.id !== siteId || resolved.meta.access !== "gitfor") { return page(400, "Invalid request", "That site does not use gitfor sign-in.
"); } if (!(await allowed(resolved.meta, viewer))) { return page(403, "Access denied", `@${escapeHtml(viewer.name)} is not allowed to view this site.
`); } if (request.method !== "POST") { const fields = [ ["site", siteId], ["host", host], ["state", state], ] .map(([key, value]) => ``) .join(""); return page( 200, "Continue to site", `${escapeHtml(host)} wants to know you are @${escapeHtml(viewer.name)} on ${escapeHtml(env.FORGE_NAME)}. It will see your username, and nothing else.
`, ); } const code = randomToken("sc"); await env.KV.put(codeKey(await sha256Hex(code)), JSON.stringify({ siteId, host, subject: viewer.name }), { expirationTtl: CODE_TTL_SECONDS, }); const target = new URL(CALLBACK_PATH, siteOrigin(host.replace(/:\d+$/, ""))); target.searchParams.set("code", code); target.searchParams.set("state", state); return new Response(null, { status: 303, headers: { location: target.toString(), "cache-control": "no-store" } }); } async function claim(request: Request, url: URL): PromiseYour site now lives at ${escapeHtml(link)}.
`, ); } catch (error) { if (error instanceof HttpError) return page(error.status, "Could not claim site", `${escapeHtml(error.message)}
`); throw error; } } /** Forge-origin routes for sites: the IdP authorize endpoint and the scratch claim page. */ export async function handleSitesForgeRoutes(request: Request, url: URL): Promise