58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
// 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<string> {
|
|
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;
|
|
}
|