fix(GRO-867): proxy logo download through API server — no mixed content

All logo S3 interactions are now server-proxied:
- GET /api/admin/settings/logo streams image bytes directly
  instead of returning a presigned S3 URL to the browser
- Upload already went through POST /api/admin/settings/logo/upload
- Frontend uses relative /api/admin/settings/logo path as img src,
  never a raw S3 URL

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Flea Flicker
2026-04-21 21:22:07 +00:00
parent 03bd2d0235
commit 660d3e0741
3 changed files with 25 additions and 26 deletions
+7 -4
View File
@@ -2,7 +2,7 @@ import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3";
import { eq, getDb, businessSettings } from "@groombook/db";
import { getPresignedUploadUrl, getPresignedGetUrl, deleteObject, putObject } from "../lib/s3.js";
import { getPresignedUploadUrl, getPresignedGetUrl, deleteObject, putObject, getObject } from "../lib/s3.js";
import { requireSuperUser } from "../middleware/rbac.js";
export const settingsRouter = new Hono();
@@ -215,7 +215,8 @@ settingsRouter.post(
/**
* GET /api/admin/settings/logo
* Returns a presigned GET URL for the logo.
* Proxies the logo from S3 so the browser never sees an S3 URL.
* Returns the image bytes with proper Content-Type.
*/
settingsRouter.get("/logo", async (c) => {
const db = getDb();
@@ -224,8 +225,10 @@ settingsRouter.get("/logo", async (c) => {
if (!row) return c.json({ error: "Settings not found" }, 404);
if (!row.logoKey) return c.json({ error: "No logo on file" }, 404);
const url = await getPresignedGetUrl(row.logoKey);
return c.json({ url, logoKey: row.logoKey });
const { body, contentType } = await getObject(row.logoKey);
c.header("Content-Type", contentType);
c.header("Cache-Control", "public, max-age=86400");
return c.body(body);
});
/**