import type { Env } from "./config.ts"; import { opaque } from "./crypto.ts"; import { OAuthError } from "./errors.ts"; import { accounts, type CloudflareFetch } from "./cloudflare.ts"; import type { Principal } from "./vault.ts"; export const SESSION_COOKIE = "__Host-flarebot-control-session"; export const TRANSACTION_COOKIE = "__Host-flarebot-oauth"; export const cookie = (name: string, value: string, maxAge: number) => `${name}=${value}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`; export function readCookie(request: Request, name: string): string | null { const values = (request.headers.get("Cookie") ?? "") .split(";") .map((s) => s.trim()) .filter((s) => s.startsWith(`${name}=`)); if (values.length !== 1) return null; const value = values[0].slice(name.length + 1); return opaque(value) ? value : null; } export const vault = ( env: Env, kind: "transaction" | "session" | "grant" | "continuation" | "code" | "operation", ref: string, ) => env.AUTH_VAULT.get(env.AUTH_VAULT.idFromName(`${kind}:${ref}`)); export async function authenticatedPrincipal( request: Request, env: Env, ): Promise { const ref = readCookie(request, SESSION_COOKIE); const principal = ref ? await vault(env, "session", ref).session() : null; if (!principal) throw new OAuthError("reauthorization_required"); return principal; } export async function authorizedGrant(env: Env, principal: Principal) { const grant = await vault(env, "grant", principal.grantRef).grant( principal.subject, ); if (!grant) throw new OAuthError("reauthorization_required"); return grant; } export async function grantedAccounts( env: Env, principal: Principal, network: CloudflareFetch = fetch, ) { const grant = await authorizedGrant(env, principal); try { return await accounts(grant.accessToken, network); } catch (error) { if ( error instanceof OAuthError && error.code === "reauthorization_required" ) await vault(env, "grant", principal.grantRef).destroy(); throw error; } } // Server-only handoff for FLA11/9. Resolve the principal from a real browser // session, then ownership metadata; never accept a browser-supplied principal. // Revalidate account membership on every new privileged operation, and keep the // returned token within its trusted call stack (never Workflow inputs/results). export async function selectedDeploymentGrant( request: Request, env: Env, network: CloudflareFetch = fetch, ) { const principal = await authenticatedPrincipal(request, env); if (!principal.selectedAccountId) throw new OAuthError("account_denied"); const available = await grantedAccounts(env, principal, network); if (!available.some((account) => account.id === principal.selectedAccountId)) throw new OAuthError("account_denied"); return { principal, grant: await authorizedGrant(env, principal) }; }