/** * Resolves the dump whose media a route under `/api/dumps/:dumpId/` serves: * its file (`routes/files.ts`), its thumbnail (`routes/thumbnails.ts`) or its * waveform (`routes/waveforms.ts`). Media elements and ``s can't send an * Authorization header, so these routes take the viewer's media token from * the query string, and are cached by the file's version. * * @module */ import type { Context } from "@oak/oak"; import { APIException } from "./api-exception.ts"; import { authenticateMedia } from "./auth.ts"; import { type Dump, dumpFileVersion } from "../../shared/dump.ts"; import { UUID_RE } from "../../shared/validation.ts"; import { getDump } from "../services/dump-service.ts"; /** * Returns dump `dumpId` as the viewer of `ctx`'s `?token=` may see it, and the * Cache-Control its media get: kept for good under a URL naming the file's * current version (`?v=`, as `dumpFileVersion` gives), revalidated under any * other, and never in a shared cache for a private dump. It also keeps the * token out of the Referer of whatever the response links to. * * @throws {APIException} 400 when `dumpId` isn't an id, which names files on * disk, and 404 when the viewer can't see the dump. */ export async function resolveMediaDump( ctx: Context, dumpId: string, ): Promise<{ dump: Dump; cacheControl: string }> { if (!UUID_RE.test(dumpId)) throw new APIException(400, "Invalid dump ID"); const token = ctx.request.url.searchParams.get("token"); const viewerId = token ? await authenticateMedia(token) : null; const dump = getDump(dumpId, viewerId ?? undefined); ctx.response.headers.set("Referrer-Policy", "no-referrer"); const scope = dump.isPrivate ? "private" : "public"; const current = ctx.request.url.searchParams.get("v") === dumpFileVersion(dump); return { dump, cacheControl: current ? `${scope}, max-age=31536000, immutable` : `${scope}, no-cache`, }; }