branding-logo-corp.test.ts49 lines · main
| 1 | /** |
| 2 | * Branding-logo Cross-Origin-Resource-Policy override (bug: logo renders as a |
| 3 | * broken-image icon on the dashboard). |
| 4 | * |
| 5 | * The public logo route serves 200/image-png fine, but secureHeaders() stamps |
| 6 | * `Cross-Origin-Resource-Policy: same-origin` on every response — so a browser |
| 7 | * refuses to render the image when it's embedded cross-origin (dashboard at |
| 8 | * briven.tech, logo at api.briven.tech). index.ts fixes this with a path-scoped |
| 9 | * middleware registered BEFORE secureHeaders, whose post-`next()` write is the |
| 10 | * OUTERMOST and therefore wins, flipping ONLY the logo route to `cross-origin`. |
| 11 | * |
| 12 | * This test pins that ordering invariant in isolation (no DB/env): the override |
| 13 | * must win on the logo path, and every other path must stay same-origin. |
| 14 | */ |
| 15 | import { describe, expect, test } from 'bun:test'; |
| 16 | import { Hono } from 'hono'; |
| 17 | import { secureHeaders } from 'hono/secure-headers'; |
| 18 | |
| 19 | const LOGO_PATH = '/v1/projects/:id/auth/branding/logo'; |
| 20 | |
| 21 | function buildApp() { |
| 22 | const app = new Hono(); |
| 23 | // Mirror index.ts ordering EXACTLY: the override is registered BEFORE |
| 24 | // secureHeaders so it wraps it and its post-next() set runs last. |
| 25 | app.use(LOGO_PATH, async (c, next) => { |
| 26 | await next(); |
| 27 | c.res.headers.set('Cross-Origin-Resource-Policy', 'cross-origin'); |
| 28 | }); |
| 29 | app.use('*', secureHeaders()); |
| 30 | app.get(LOGO_PATH, () => new Response('PNGBYTES', { headers: { 'content-type': 'image/png' } })); |
| 31 | app.get('/v1/other', (c) => c.json({ ok: true })); |
| 32 | return app; |
| 33 | } |
| 34 | |
| 35 | describe('branding logo CORP override', () => { |
| 36 | test('logo route serves Cross-Origin-Resource-Policy: cross-origin (override wins)', async () => { |
| 37 | const app = buildApp(); |
| 38 | const res = await app.request('/v1/projects/p_abc123/auth/branding/logo'); |
| 39 | expect(res.status).toBe(200); |
| 40 | expect(res.headers.get('content-type')).toBe('image/png'); |
| 41 | expect(res.headers.get('cross-origin-resource-policy')).toBe('cross-origin'); |
| 42 | }); |
| 43 | |
| 44 | test('every other route keeps the secure default (same-origin)', async () => { |
| 45 | const app = buildApp(); |
| 46 | const res = await app.request('/v1/other'); |
| 47 | expect(res.headers.get('cross-origin-resource-policy')).toBe('same-origin'); |
| 48 | }); |
| 49 | }); |