import { createWriteStream } from "node:fs"; import { mkdir, rename, rm } from "node:fs/promises"; import { join } from "node:path"; import { randomUUID } from "node:crypto"; import { ChiaPartnerClient, downloadAndVerifyCatalogAsset, redactSensitiveText, type CatalogItem, type PartnerEnvironment, } from "@chiahealth/partner"; import { boundedAssetCacheSeconds, catalogRefreshDelayMs, catalogResponseFreshUntilMs, earliestPublicationExpiryMs, isCatalogItemMirrorable, isCatalogSnapshotFreshAt, isPublicationActiveAt, partitionActivePublications, removeMirroredFilesSafely, } from "./publication-cache.js"; import { createOfficialCatalogProjection, prepareOfficialShareSessionRequest, } from "./renderer-contract.js"; import { RENDERER_CONTENT_SECURITY_POLICY, RENDERER_CSS, RENDERER_HTML, RENDERER_JAVASCRIPT, RENDERER_SECURITY_HEADERS, } from "./renderer-assets.js"; assertAbsent( ["CHIA_FINANCE_KEY", "CHIA_WEBHOOK_SECRET_CURRENT", "CHIA_WEBHOOK_SECRET_RETIRING"], "catalog serving role", ); const environment = requireEnvironment(); const apiOrigin = requireEnv("CHIA_API_ORIGIN"); const assetOrigin = requireEnv("CHIA_ASSET_ORIGIN"); const runtimeKey = requireEnv("CHIA_RUNTIME_KEY"); const careOrigin = requireEnv("CHIA_CARE_ORIGIN"); const stateDir = process.env.PARTNER_STATE_DIR ?? "./.state"; const assetDir = join(stateDir, "assets"); await mkdir(assetDir, { recursive: true }); const client = new ChiaPartnerClient({ environment, apiOrigin, assetOrigin, runtimeKey, careOrigin, }); interface PublishedAsset { readonly item: CatalogItem; readonly path: string; readonly file: string; readonly contentType: string; readonly expiresAtMs: number; } let catalogEtag: string | undefined; let catalogSnapshotFreshUntilMs = Number.NaN; let catalogSnapshotPresent = false; let published: PublishedAsset[] = []; let refreshInFlight: Promise | undefined; let expiryTimer: ReturnType | undefined; function removePublishedFiles(entries: readonly PublishedAsset[]): Promise { return removeMirroredFilesSafely( entries.map(({ file }) => file), (file) => rm(file, { force: true }), (failureCount) => reportSafeError( new Error(`Failed to remove ${failureCount} inaccessible mirrored asset file(s)`), ), ); } function scheduleNextExpiry(): void { if (expiryTimer) clearTimeout(expiryTimer); expiryTimer = undefined; const nextPublicationExpiryMs = published.reduce( (earliest, { expiresAtMs }) => Math.min(earliest, expiresAtMs), Number.POSITIVE_INFINITY, ); const nextSnapshotExpiryMs = catalogSnapshotPresent ? catalogSnapshotFreshUntilMs : Number.POSITIVE_INFINITY; const nextExpiryMs = Math.min(nextPublicationExpiryMs, nextSnapshotExpiryMs); const delayMs = catalogRefreshDelayMs(nextExpiryMs, Date.now()); if (delayMs === null) return; expiryTimer = setTimeout(() => { expiryTimer = undefined; // Refresh exactly at the response/publication boundary. refreshCatalog // evicts first, so a failed refresh remains fail-closed, while a successful // refresh immediately retains every other still-valid publication. void refreshCatalog().catch(reportSafeError); }, delayMs); expiryTimer.unref(); } function evictExpiredPublications(nowMs = Date.now()): void { if ( catalogSnapshotPresent && !isCatalogSnapshotFreshAt(catalogSnapshotFreshUntilMs, nowMs) ) { const expired = published; published = []; catalogEtag = undefined; catalogSnapshotFreshUntilMs = Number.NaN; catalogSnapshotPresent = false; scheduleNextExpiry(); void removePublishedFiles(expired); return; } const { active, expired } = partitionActivePublications(published, nowMs); if (expired.length === 0) { if (!expiryTimer) scheduleNextExpiry(); return; } published = active; // A locally filtered representation must never receive a 304 on its next refresh. catalogEtag = undefined; scheduleNextExpiry(); void removePublishedFiles(expired); } async function refreshCatalog(): Promise { if (refreshInFlight) return refreshInFlight; refreshInFlight = (async () => { evictExpiredPublications(); const conditionalEtag = catalogSnapshotPresent ? catalogEtag : undefined; // Anchor before network/retry time so transport latency can only shorten, // never accidentally extend, the server-authorized freshness window. const freshnessAnchorMs = Date.now(); const result = await client.listCatalog( conditionalEtag ? { ifNoneMatch: conditionalEtag } : {}, ); if (result.notModified) { if (!conditionalEtag || result.etag !== conditionalEtag) { throw new Error("Catalog returned 304 without the exact cached representation ETag"); } const freshUntilMs = catalogResponseFreshUntilMs( result.cacheControl, freshnessAnchorMs, published.map(({ expiresAtMs }) => expiresAtMs), ); if (!isCatalogSnapshotFreshAt(freshUntilMs, Date.now())) { const previous = published; published = []; catalogEtag = undefined; catalogSnapshotFreshUntilMs = Number.NaN; catalogSnapshotPresent = false; scheduleNextExpiry(); await removePublishedFiles(previous); return; } catalogSnapshotFreshUntilMs = freshUntilMs; scheduleNextExpiry(); return; } const candidates = result.data.data .filter(isCatalogItemMirrorable) .map((item) => ({ item, expiresAtMs: earliestPublicationExpiryMs(item) })); const initialFreshUntilMs = catalogResponseFreshUntilMs( result.cacheControl, freshnessAnchorMs, candidates.map(({ expiresAtMs }) => expiresAtMs), ); if (!isCatalogSnapshotFreshAt(initialFreshUntilMs, Date.now())) { const previous = published; published = []; catalogEtag = undefined; catalogSnapshotFreshUntilMs = Number.NaN; catalogSnapshotPresent = false; scheduleNextExpiry(); await removePublishedFiles(previous); return; } const next: PublishedAsset[] = []; try { for (const { item, expiresAtMs } of candidates) { if (!isPublicationActiveAt({ expiresAtMs }, Date.now())) continue; const token = randomUUID().replaceAll("-", ""); const path = `/partner-assets/${token}`; const file = join(assetDir, token); const temporary = `${file}.partial`; const writable = createWriteStream(temporary, { flags: "wx", mode: 0o600 }); const verified = await downloadAndVerifyCatalogAsset({ environment, runtimeKey, assetOrigin, url: item.presentation.image.backend_download_url, expectedSha256: item.presentation.image.sha256, sink: { writable, async commit() { await rename(temporary, file); }, async abort() { writable.destroy(); await rm(temporary, { force: true }); }, }, }); next.push({ item, path, file, contentType: verified.contentType, expiresAtMs }); } } catch (error) { await removePublishedFiles(next); throw error; } const freshUntilMs = initialFreshUntilMs; const nowMs = Date.now(); const { active, expired } = partitionActivePublications(next, nowMs); const previous = published; if (!isCatalogSnapshotFreshAt(freshUntilMs, nowMs)) { published = []; catalogEtag = undefined; catalogSnapshotFreshUntilMs = Number.NaN; catalogSnapshotPresent = false; scheduleNextExpiry(); await removePublishedFiles([...previous, ...next]); return; } published = active; catalogEtag = result.etag; catalogSnapshotFreshUntilMs = freshUntilMs; catalogSnapshotPresent = true; scheduleNextExpiry(); const activeFiles = new Set(active.map(({ file }) => file)); await removePublishedFiles( [...previous, ...expired].filter(({ file }) => !activeFiles.has(file)), ); })().finally(() => { refreshInFlight = undefined; }); return refreshInFlight; } await refreshCatalog(); // Scheduled/backend fetch only; never fetch Chia once per user. setInterval(() => void refreshCatalog().catch(reportSafeError), 240_000).unref(); const server = Bun.serve({ port: Number(process.env.PORT ?? 8787), async fetch(request) { evictExpiredPublications(); const url = new URL(request.url); if (request.method === "GET" && environment === "sandbox") { if (url.pathname === "/demo") { return rendererAsset(RENDERER_HTML, "text/html; charset=utf-8", true); } if (url.pathname === "/demo/renderer.css") { return rendererAsset(RENDERER_CSS, "text/css; charset=utf-8"); } if (url.pathname === "/demo/renderer.js") { return rendererAsset(RENDERER_JAVASCRIPT, "text/javascript; charset=utf-8"); } } if (request.method === "GET" && url.pathname === "/healthz") { return Response.json({ ok: true, environment, catalog_items: published.length }); } if (request.method === "GET" && url.pathname === "/catalog") { // In production derive this evidence from the partner's authenticated account, not caller headers. const stateCode = request.headers.get("X-Demo-Service-State"); const observedAt = request.headers.get("X-Demo-State-Observed-At"); const evidence = stateCode && observedAt ? { stateCode, evidenceBasis: "partner_account_service_state" as const, observedAt, } : null; const now = new Date(); const data = published.flatMap(({ item, path }) => { const projection = createOfficialCatalogProjection(item, path, evidence, now); return projection ? [projection] : []; }); return Response.json({ object: "list", data }, { headers: { "Cache-Control": "private, no-store" } }); } if (request.method === "POST" && url.pathname === "/handoff-session") { // Demo input only. Production derives state evidence from the partner's authenticated // account context but must never forward an account/user/device/contact ID. if (request.headers.get("Content-Type") !== "application/json") { return safeJsonError(415, "invalid_content_type"); } const declaredLength = request.headers.get("Content-Length"); if (declaredLength && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > 4096)) { return safeJsonError(413, "request_too_large"); } const raw = await request.text(); if (new TextEncoder().encode(raw).byteLength > 4096) { return safeJsonError(413, "request_too_large"); } let body: unknown; try { body = JSON.parse(raw); } catch { return safeJsonError(400, "invalid_request"); } if (!isExactHandoffBody(body)) return safeJsonError(400, "invalid_request"); const idempotencyKey = request.headers.get("Idempotency-Key") ?? ""; const stateCode = request.headers.get("X-Demo-Service-State"); const observedAt = request.headers.get("X-Demo-State-Observed-At"); const entry = published.find(({ item }) => item.id === body.catalog_item_id); if (!entry || !stateCode || !observedAt) return safeJsonError(403, "handoff_not_allowed"); const projection = createOfficialCatalogProjection(entry.item, entry.path, { stateCode, evidenceBasis: "partner_account_service_state", observedAt, }); if ( !projection || projection.handoff.mode !== "share_or_neutral" || body.disclosure_version !== projection.handoff.primary.disclosureVersion ) { return safeJsonError(403, "handoff_not_allowed"); } let sessionRequest; try { sessionRequest = prepareOfficialShareSessionRequest(projection, { affirmedAt: body.consent_attestation.affirmed_at, idempotencyKey, }); } catch { return safeJsonError(403, "handoff_not_allowed"); } const created = await client.createHandoffSession(sessionRequest); return Response.json( { url: created.data.url, expires_at: created.data.expires_at }, { status: 201, headers: { "Cache-Control": "no-store" } }, ); } const asset = published.find(({ path }) => path === url.pathname); if (request.method === "GET" && asset) { const maxAge = boundedAssetCacheSeconds( { expiresAtMs: Math.min( asset.expiresAtMs, catalogSnapshotFreshUntilMs, ), }, Date.now(), ); if (maxAge === null) { evictExpiredPublications(); return new Response("Not found", { status: 404 }); } return new Response(Bun.file(asset.file), { headers: { "Content-Type": asset.contentType, "Cache-Control": `private, max-age=${maxAge}, must-revalidate`, "X-Content-Type-Options": "nosniff", }, }); } return new Response("Not found", { status: 404 }); }, }); console.log(`Partner reference backend listening on http://127.0.0.1:${server.port}`); function requireEnvironment(): PartnerEnvironment { const value = process.env.CHIA_ENVIRONMENT; if (value !== "sandbox" && value !== "production") throw new Error("CHIA_ENVIRONMENT must be sandbox or production"); return value; } function requireEnv(name: string): string { const value = process.env[name]; if (!value) throw new Error(`${name} is required`); return value; } function assertAbsent(names: readonly string[], role: string): void { const present = names.filter((name) => Boolean(process.env[name])); if (present.length) { throw new Error(`${role} refuses credentials for another role: ${present.join(", ")}`); } } function reportSafeError(error: unknown): void { console.error(redactSensitiveText(error instanceof Error ? error.message : "Partner integration error")); } function safeJsonError(status: number, code: string): Response { return Response.json( { error: { code } }, { status, headers: { "Cache-Control": "no-store" } }, ); } function rendererAsset( value: string, contentType: string, document = false, ): Response { const headers = new Headers(RENDERER_SECURITY_HEADERS); headers.set("Content-Type", contentType); if (document) { headers.set("Content-Security-Policy", RENDERER_CONTENT_SECURITY_POLICY); } return new Response(value, { headers }); } function isExactHandoffBody(value: unknown): value is { catalog_item_id: string; disclosure_version: string; consent_attestation: { affirmed: true; affirmed_at: string }; } { if (typeof value !== "object" || value === null || Array.isArray(value)) return false; const body = value as Record; if (Object.keys(body).sort().join(",") !== "catalog_item_id,consent_attestation,disclosure_version") return false; const consent = body.consent_attestation; if (typeof consent !== "object" || consent === null || Array.isArray(consent)) return false; const attestation = consent as Record; return ( Object.keys(attestation).sort().join(",") === "affirmed,affirmed_at" && attestation.affirmed === true && typeof attestation.affirmed_at === "string" && typeof body.catalog_item_id === "string" && typeof body.disclosure_version === "string" ); }