import { randomUUID } from "node:crypto";
import { getCookie, getHeader, setCookie, type H3Event } from "h3";
const PUBLIC_VIEWER_COOKIE = "content_public_viewer";
const PUBLIC_VIEWER_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
export const PUBLIC_DOCUMENT_CONTEXT_EXCERPT_CHARS = 2_400;
type PublicDocumentPromptInput = {
id: string;
title: string;
description?: string;
content: string;
updatedAt: string | Date;
};
function publicDocumentExcerpt(content: string): {
excerpt: string;
truncated: boolean;
} {
const normalized = content.trim();
if (normalized.length <= PUBLIC_DOCUMENT_CONTEXT_EXCERPT_CHARS) {
return { excerpt: normalized, truncated: false };
}
const tailChars = Math.floor(PUBLIC_DOCUMENT_CONTEXT_EXCERPT_CHARS / 3);
const headChars = PUBLIC_DOCUMENT_CONTEXT_EXCERPT_CHARS - tailChars;
return {
excerpt: `${normalized.slice(0, headChars)}\n\n... [middle omitted from initial context] ...\n\n${normalized.slice(-tailChars)}`,
truncated: true,
};
}
export function buildPublicDocumentPromptContext(
doc: PublicDocumentPromptInput,
): string {
const { excerpt, truncated } = publicDocumentExcerpt(doc.content);
const fullDocumentGuidance = truncated
? `This is a bounded excerpt of the document. Before answering a question that may depend on omitted content, call \`get-document\` with \`id: "${doc.id}"\` to read the complete document.`
: `The complete document fits in this context. Call \`get-document\` with \`id: "${doc.id}"\` if you need a fresh structured copy.`;
return `
The user is viewing a public, read-only shared Content document. Answer questions using this document as the primary context. Do not create, edit, delete, comment on, share, or otherwise mutate document data for public viewers.
${fullDocumentGuidance}
Document ID: ${doc.id}
Title: ${doc.title}
Description: ${doc.description || "(none)"}
Updated at: ${doc.updatedAt}
Markdown excerpt:
${excerpt}
`;
}
function getAppOrigin(event: H3Event): string | null {
const proto =
getHeader(event, "x-forwarded-proto") ??
(getHeader(event, "origin")?.startsWith("https://") ? "https" : "http");
const host = getHeader(event, "x-forwarded-host") ?? getHeader(event, "host");
if (!host) return null;
return `${proto}://${host}`;
}
function publicDocumentIdFromEvent(event: H3Event): string | null {
const referrer = getHeader(event, "referer");
if (!referrer) return null;
try {
const url = new URL(referrer);
// Reject off-origin referers — without this an attacker hosting a
// page at evil.com/p/ could trick same-site requests into
// minting an anonymous-viewer identity scoped to a doc the user
// never opened. The lax-cookie protections we rely on assume the
// referer-derived doc context is same-origin.
const appOrigin = getAppOrigin(event);
if (appOrigin && url.origin !== appOrigin) return null;
const match = url.pathname.match(/(?:^|\/)p\/([^/?#]+)/);
return match?.[1] ? decodeURIComponent(match[1]) : null;
} catch {
return null;
}
}
async function getPublicDocumentForEvent(event: H3Event) {
const id = publicDocumentIdFromEvent(event);
if (!id) return null;
const { getDb } = await import("../db/index.js");
const { documents } = await import("../db/schema.js");
const { and, eq, isNull } = await import("drizzle-orm");
const [doc] = await getDb()
.select({
id: documents.id,
title: documents.title,
description: documents.description,
content: documents.content,
updatedAt: documents.updatedAt,
visibility: documents.visibility,
})
.from(documents)
.where(
and(
eq(documents.id, id),
eq(documents.visibility, "public"),
isNull(documents.trashedAt),
),
)
.limit(1);
return doc ?? null;
}
export async function resolvePublicViewerOwner(
event: H3Event,
): Promise {
const doc = await getPublicDocumentForEvent(event);
let viewerId = getCookie(event, PUBLIC_VIEWER_COOKIE);
if (!doc) {
// OAuth callbacks return with Referer set to the OAuth provider, not
// /p/. To still resolve an anonymous owner for the callback we
// accept the viewer cookie when the request path is exactly the
// builder callback. The pending-connect row written by /builder/connect
// (which DID require a /p/ Referer) is the gate that prevents
// arbitrary callback hits from completing.
const rawPath = event.node?.req?.url ?? event.path ?? "";
const pathOnly = rawPath.split("?")[0]?.split("#")[0] ?? "";
const isBuilderCallback =
pathOnly === "/_agent-native/builder/callback" ||
pathOnly.endsWith("/_agent-native/builder/callback");
if (isBuilderCallback && viewerId && /^[0-9a-f-]{36}$/i.test(viewerId)) {
return `public-${viewerId}@agent-native.local`;
}
return null;
}
if (!viewerId || !/^[0-9a-f-]{36}$/i.test(viewerId)) {
viewerId = randomUUID();
const proto =
getHeader(event, "x-forwarded-proto") ??
(getHeader(event, "origin")?.startsWith("https://") ? "https" : "http");
setCookie(event, PUBLIC_VIEWER_COOKIE, viewerId, {
httpOnly: true,
sameSite: "lax",
secure: proto === "https",
path: "/",
maxAge: PUBLIC_VIEWER_COOKIE_MAX_AGE,
});
}
return `public-${viewerId}@agent-native.local`;
}
export async function publicDocumentExtraContext(event: H3Event) {
const doc = await getPublicDocumentForEvent(event);
if (!doc) return null;
return buildPublicDocumentPromptContext(doc);
}