import * as Effect from "effect/Effect"; import { accounts, type CloudflareFetch } from "./cloudflare.ts"; import type { Env } from "./config.ts"; import { opaque } from "./crypto.ts"; import { OAuthError } from "./errors.ts"; import { vaultClient } from "./vault-client.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, ) => vaultClient(env.AUTH_VAULT.get(env.AUTH_VAULT.idFromName(`${kind}:${ref}`))); export function authenticatedPrincipal(request: Request, env: Env) { return Effect.gen(function* () { const ref = readCookie(request, SESSION_COOKIE); const principal = ref ? yield* vault(env, "session", ref).session() : null; if (!principal) return yield* new OAuthError("reauthorization_required"); return principal; }); } export function authorizedGrant(env: Env, principal: Principal) { return Effect.gen(function* () { const grant = yield* vault(env, "grant", principal.grantRef).grant( principal.subject, ); if (!grant) return yield* new OAuthError("reauthorization_required"); return grant; }); } export function grantedAccounts( env: Env, principal: Principal, network: CloudflareFetch = fetch, ) { return authorizedGrant(env, principal).pipe( Effect.flatMap((grant) => accounts(grant.accessToken, network)), Effect.tapError((error) => error.code === "reauthorization_required" ? vault(env, "grant", principal.grantRef).destroy() : Effect.void, ), ); } // 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 function selectedDeploymentGrant( request: Request, env: Env, network: CloudflareFetch = fetch, ) { return Effect.gen(function* () { const principal = yield* authenticatedPrincipal(request, env); if (!principal.selectedAccountId) return yield* new OAuthError("account_denied"); const available = yield* grantedAccounts(env, principal, network); if ( !available.some((account) => account.id === principal.selectedAccountId) ) return yield* new OAuthError("account_denied"); return { principal, grant: yield* authorizedGrant(env, principal) }; }); }