import { env } from "cloudflare:workers";
import { loadUserById, toViewer } from "./auth";
import { hkdfSha256, utf8 } from "./sync-hash";
import { isUserContentOrigin, userContentOriginFor } from "./usercontent-origin";
import { decodeGrant, encodeGrant, USERCONTENT_GRANT_PARAM, USERCONTENT_GRANT_TTL_MS } from "./usercontent-policy";
import type { Viewer } from "~/lib/types";
const GRANT_SALT = "gitfor-usercontent";
const GRANT_INFO = "grant-v1";
let cachedKey: { secret: string; key: Uint8Array } | null = null;
function grantKey(): Uint8Array | null {
const secret = env.SECRETS_KEY;
if (!secret) return null;
if (!cachedKey || cachedKey.secret !== secret) {
cachedKey = { secret, key: hkdfSha256(utf8(secret), GRANT_SALT, GRANT_INFO) };
}
return cachedKey.key;
}
/**
* The absolute user-content URL for a forge path, carrying a short-lived grant when a signed-in viewer needs
* it for private content. Returns the path unchanged when no separate origin is in use.
*/
export function userContentUrl(forgeUrl: URL, pathname: string, search: string, viewer: Viewer | null): string {
const origin = userContentOriginFor(forgeUrl);
if (!origin) return `${pathname}${search}`;
const target = new URL(`${origin}${pathname}${search}`);
const key = grantKey();
if (viewer && key) {
target.searchParams.set(
USERCONTENT_GRANT_PARAM,
encodeGrant(key, viewer.id, pathname, Date.now() + USERCONTENT_GRANT_TTL_MS),
);
}
return target.href;
}
/** The viewer a `?uc=` grant on this request vouches for, only when the request is on the user-content origin. */