From 0e4cd86119adf80c6758df670067cb94cdd1eb01 Mon Sep 17 00:00:00 2001 From: Kenneth Obsequio Date: Tue, 7 Jul 2026 17:46:06 +0800 Subject: [PATCH] s3 service adjustment quickly Signed-off-by: Kenneth Obsequio --- controllers/client/media.controller.js | 2 +- services/mediaToken.service.js | 2 +- services/s3.service.js | 32 ++++++++++++++++++++------ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/controllers/client/media.controller.js b/controllers/client/media.controller.js index 1fc37ef..3d42bba 100644 --- a/controllers/client/media.controller.js +++ b/controllers/client/media.controller.js @@ -180,7 +180,7 @@ exports.issueToken = async (req, res) => { let thumbnail_url = null; if (asset.thumbnail_storage_key) { try { - thumbnail_url = await s3.getSignedDownloadUrl(asset.thumbnail_storage_key, TOKEN_TTL_SEC); + thumbnail_url = await s3.getPublicUrl(asset.thumbnail_storage_key); } catch { // Non-fatal — thumbnail is cosmetic } diff --git a/services/mediaToken.service.js b/services/mediaToken.service.js index fe46a37..3ed6183 100644 --- a/services/mediaToken.service.js +++ b/services/mediaToken.service.js @@ -99,7 +99,7 @@ async function issueForAsset(asset, userId, ip) { let thumbnail_url = null; if (asset.thumbnail_storage_key) { try { - thumbnail_url = await s3.getSignedDownloadUrl(asset.thumbnail_storage_key, TOKEN_TTL_SEC); + thumbnail_url = await s3.getPublicUrl(asset.thumbnail_storage_key); } catch { // Non-fatal — thumbnail is cosmetic } diff --git a/services/s3.service.js b/services/s3.service.js index 5968d29..ff47e32 100644 --- a/services/s3.service.js +++ b/services/s3.service.js @@ -154,20 +154,38 @@ async function deleteFile(key) { // ─── getSignedDownloadUrl ───────────────────────────────────────────────────── // -// Generates a short-lived pre-signed GET URL against whichever host -// resolvePublicHost() picks, so the URL is resolvable from wherever the -// request is served (browser or proxy server), not just the one running -// Garage locally. +// Generates a short-lived pre-signed GET URL against S3_ENDPOINT (internal — +// this backend is always co-located with Garage, see docker-compose.yml). +// For server-side reads only (e.g. media.controller.js streamAsset piping +// bytes to the browser itself) — never hand this URL to a browser directly. +// +// Must NOT be signed against S3_PUBLIC_URL: that host is fronted by +// garage-anon-proxy, which re-signs every request itself (header-based SigV4, +// real credentials) regardless of any query-string signature already present. +// A presigned URL arriving there collides with the proxy's own signature and +// Garage rejects the request (400 "Header `x-amz-date` should be signed"). +// See getPublicUrl() below for the browser-facing equivalent. // async function getSignedDownloadUrl(key, expiresInSeconds = 3600) { - const client = await getPublicClient(); return getSignedUrl( - client, + s3, new GetObjectCommand({ Bucket: DEFAULT_BUCKET, Key: key }), { expiresIn: expiresInSeconds } ); } +// ─── getPublicUrl ───────────────────────────────────────────────────────────── +// +// Browser-facing object URL, unsigned. garage-anon-proxy (fronting +// S3_PUBLIC_URL) authenticates every request server-side with real +// credentials, so a client-side presign adds nothing but a broken request — +// see getSignedDownloadUrl() above. Use this wherever a URL is handed +// directly to the browser (e.g. thumbnail previews). +// +async function getPublicUrl(key, bucket = DEFAULT_BUCKET) { + return buildPublicUrl(key, bucket); +} + // ─── getObjectStream ──────────────────────────────────────────────────────── // // Fetches an S3 object and returns its readable stream + metadata, for use @@ -198,4 +216,4 @@ async function ping() { await client.send(new HeadBucketCommand({ Bucket: DEFAULT_BUCKET })); } -module.exports = { uploadFile, deleteFile, getSignedDownloadUrl, getObjectStream, ping }; \ No newline at end of file +module.exports = { uploadFile, deleteFile, getSignedDownloadUrl, getPublicUrl, getObjectStream, ping }; \ No newline at end of file