// P4 Files (AK-P4a) §6 — web-side presigned download URL builder for the // auth-proxy media route. Bucket stays private (AD-23); the proxy redirects an // authorized request to a short-lived signed URL. Reuses the shared SigV4 // signer from @toolctrl/storage (no @aws-sdk/s3-request-presigner dependency). import { loadRequiredWebConfig } from "@toolctrl/config"; import { createS3SignedUrlAdapter, createSigV4DownloadSigner, StorageAdapterError } from "@toolctrl/storage"; import { resolveStorageBucket } from "./storageService"; export const MEDIA_SIGNED_URL_TTL_SECONDS = 300; function sanitizeFilename(filename: string): string { // Strip quotes/control chars so the value is safe inside a Content-Disposition header. return filename.replace(/["\\\r\n]/g, "").slice(0, 200); } /** * Create a short-lived (default 300s) presigned GET URL for an object in the * private files bucket. `filename` (optional) sets an inline Content-Disposition * so the browser shows a sensible name on download. */ export async function createMediaDownloadUrl({ key, filename, ttlSeconds = MEDIA_SIGNED_URL_TTL_SECONDS, }: { key: string; filename?: string; ttlSeconds?: number; }): Promise { const { storage } = loadRequiredWebConfig(); const bucket = resolveStorageBucket(); const adapter = createS3SignedUrlAdapter({ bucket, downloadSigner: createSigV4DownloadSigner({ endpoint: storage.endpoint ?? "", region: storage.region ?? "", bucket, accessKeyId: storage.accessKeyId ?? "", secretAccessKey: storage.secretAccessKey ?? "", sessionToken: storage.sessionToken, forcePathStyle: storage.forcePathStyle === undefined ? undefined : storage.forcePathStyle.toLowerCase() === "true", }), uploadSigner: () => { throw new StorageAdapterError("signed_upload_failed", "Signed upload URLs are not supported by the media proxy.", { operation: "createSignedUploadUrl", bucket, }); }, }); const result = await adapter.createSignedDownloadUrl({ key, expiresInSeconds: ttlSeconds, ...(filename ? { responseContentDisposition: `inline; filename="${sanitizeFilename(filename)}"` } : {}), }); return result.url; }