From 1a65d6e7ca109d7f10625dfcd8b129bcf5406cae Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 02:51:05 +0100 Subject: [PATCH 01/11] feat(asset): add proxy for fetching assets of external resources --- __tests__/asset-proxy.ts | 25 +++++++++++++++++++++++++ src/asset-proxy.ts | 30 ++++++++++++++++++++++++++++++ src/index.ts | 2 ++ 3 files changed, 57 insertions(+) create mode 100644 __tests__/asset-proxy.ts create mode 100644 src/asset-proxy.ts diff --git a/__tests__/asset-proxy.ts b/__tests__/asset-proxy.ts new file mode 100644 index 00000000..1a29cef6 --- /dev/null +++ b/__tests__/asset-proxy.ts @@ -0,0 +1,25 @@ +import { http } from 'msw' + +import { currentTestEnvironment } from './__utils__' + +beforeEach(() => { + globalThis.server.use( + http.get('https://whatever.org/image', () => { + return new Response('', { + headers: { 'content-type': 'image/png' }, + }) + }), + ) +}) + +test('request to https://asset-proxy.serlo.org/src?url=* gets asset from url query parameter', async () => { + const env = currentTestEnvironment() + const response = await env.fetch({ + subdomain: 'asset-proxy', + pathname: '/src?url=https://whatever.org/image', + }) + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toBe('image/png') +}) + +// TODO: be sure the user IP is not sent diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts new file mode 100644 index 00000000..9858accb --- /dev/null +++ b/src/asset-proxy.ts @@ -0,0 +1,30 @@ +import { SentryFactory, responseToContext, Url } from './utils' + +export async function assetProxy( + request: Request, + sentryFactory: SentryFactory, +): Promise { + const url = Url.fromRequest(request) + + if (url.subdomain !== 'asset-proxy') return null + if (url.pathname !== '/src') return null + + const assetUrl = url.searchParams.get('url') + + if (!assetUrl) throw new Error('Missing url for the asset') + + const response = await fetch(assetUrl, { cf: { cacheTtl: 24 * 60 * 60 } }) + + if (response.ok) { + return response + } else { + const sentry = sentryFactory.createReporter('asset-proxy') + sentry.setContext( + 'response', + responseToContext({ response, text: await response.text() }), + ) + sentry.captureMessage(`Illegal response of ${assetUrl}`, 'warning') + } + + return null +} diff --git a/src/index.ts b/src/index.ts index fb266f61..01e1b247 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { api } from './api' +import { assetProxy } from './asset-proxy' import { semanticFileNames } from './assets' import { auth } from './auth' import { cloudflareWorkerDev } from './cloudflare-worker-dev' @@ -38,6 +39,7 @@ export default { (await semanticFileNames(request)) || (await api(request, env)) || (await frontendProxy(request, sentryFactory, env)) || + (await assetProxy(request, sentryFactory)) || (await fetch(request)) ) } catch (e) { From c79ba7136da2e2c0b3c5729c1c018f67df65c6c1 Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 03:17:11 +0100 Subject: [PATCH 02/11] feat(asset): do not set cookies from other sites --- __tests__/asset-proxy.ts | 9 ++++++--- src/asset-proxy.ts | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/__tests__/asset-proxy.ts b/__tests__/asset-proxy.ts index 1a29cef6..2fc2ff4a 100644 --- a/__tests__/asset-proxy.ts +++ b/__tests__/asset-proxy.ts @@ -6,7 +6,11 @@ beforeEach(() => { globalThis.server.use( http.get('https://whatever.org/image', () => { return new Response('', { - headers: { 'content-type': 'image/png' }, + headers: { + 'content-type': 'image/png', + 'Set-Cookie': + 'sessionId=abc123; Expires=Wed, 09 Nov 2024 07:28:00 GMT; Path=/; Domain=whatever.org; Secure; HttpOnly; SameSite=None', + }, }) }), ) @@ -20,6 +24,5 @@ test('request to https://asset-proxy.serlo.org/src?url=* gets asset from url que }) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe('image/png') + expect(response.headers.get('Set-Cookie')).toBeNull() }) - -// TODO: be sure the user IP is not sent diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index 9858accb..b971bf5e 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -15,7 +15,9 @@ export async function assetProxy( const response = await fetch(assetUrl, { cf: { cacheTtl: 24 * 60 * 60 } }) + // Maybe add other validations? if (response.ok) { + response.headers.delete('Set-Cookie') return response } else { const sentry = sentryFactory.createReporter('asset-proxy') From 59888d76c3467227534482ab78ab654a8cf52180 Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:00:39 +0100 Subject: [PATCH 03/11] refactor(asset-proxy): build url first using Url class --- src/asset-proxy.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index b971bf5e..a8733564 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -9,11 +9,11 @@ export async function assetProxy( if (url.subdomain !== 'asset-proxy') return null if (url.pathname !== '/src') return null - const assetUrl = url.searchParams.get('url') + const urlParam = url.searchParams.get('url') - if (!assetUrl) throw new Error('Missing url for the asset') + if (!urlParam) throw new Error('Missing url for the asset') - const response = await fetch(assetUrl, { cf: { cacheTtl: 24 * 60 * 60 } }) + const assetUrl = new Url(urlParam) // Maybe add other validations? if (response.ok) { From 681d4cdb26a7447261abf1e99a539760da3d0d60 Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:02:03 +0100 Subject: [PATCH 04/11] refactor(asset-proxy): build a new Response to modify it since, depending on the response, the set-cookie is immutable and deleting it throws error --- src/asset-proxy.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index a8733564..dd1e72ae 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -15,17 +15,19 @@ export async function assetProxy( const assetUrl = new Url(urlParam) - // Maybe add other validations? - if (response.ok) { - response.headers.delete('Set-Cookie') + const originalResponse = await fetch(assetUrl, { cf: { cacheTtl: 24 * 60 * 60 } }) + + if (originalResponse.ok) { + const response = new Response(originalResponse.body, originalResponse) + response.headers.delete('set-cookie') return response } else { const sentry = sentryFactory.createReporter('asset-proxy') sentry.setContext( 'response', - responseToContext({ response, text: await response.text() }), + responseToContext({ response: originalResponse, text: await originalResponse.text() }), ) - sentry.captureMessage(`Illegal response of ${assetUrl}`, 'warning') + sentry.captureMessage(`Illegal response of ${assetUrl.toString()}`, 'warning') } return null From e6eb7e64c0d609e3813582305fbed6e5383d2b6e Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:43:55 +0100 Subject: [PATCH 05/11] test(asset-proxy): add negative tests --- __tests__/__utils__/expect-helper.ts | 6 ++++ __tests__/asset-proxy.ts | 49 +++++++++++++++++++++++++++- __tests__/embed.ts | 7 +--- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/__tests__/__utils__/expect-helper.ts b/__tests__/__utils__/expect-helper.ts index 3f9c789c..f8b6e11e 100644 --- a/__tests__/__utils__/expect-helper.ts +++ b/__tests__/__utils__/expect-helper.ts @@ -80,3 +80,9 @@ export function expectSentryEvent({ export function expectNoSentryError() { expect(globalThis.sentryEvents).toHaveLength(0) } + +export function expectIsPlaceholderResponse(response: Response) { + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toBe('image/png') + expect(response.headers.get('content-length')).toBe('135') +} diff --git a/__tests__/asset-proxy.ts b/__tests__/asset-proxy.ts index 2fc2ff4a..71888d35 100644 --- a/__tests__/asset-proxy.ts +++ b/__tests__/asset-proxy.ts @@ -1,6 +1,9 @@ import { http } from 'msw' -import { currentTestEnvironment } from './__utils__' +import { + currentTestEnvironment, + expectIsPlaceholderResponse, +} from './__utils__' beforeEach(() => { globalThis.server.use( @@ -13,6 +16,11 @@ beforeEach(() => { }, }) }), + http.get('https://whatever.org/notimage', () => { + return new Response('', { + headers: { 'content-type': 'application/json' }, + }) + }), ) }) @@ -26,3 +34,42 @@ test('request to https://asset-proxy.serlo.org/src?url=* gets asset from url que expect(response.headers.get('content-type')).toBe('image/png') expect(response.headers.get('Set-Cookie')).toBeNull() }) + +describe('returns placeholder', () => { + test('when url parameter is empty', async () => { + const response = await requestAsset('') + + expectIsPlaceholderResponse(response) + }) + + test('when url is invalid', async () => { + const response = await requestAsset('42') + + expectIsPlaceholderResponse(response) + }) + + test('when url query parameter is missing', async () => { + const response = await currentTestEnvironment().fetch({ + subdomain: 'asset-proxy', + pathname: '/src', + }) + + expectIsPlaceholderResponse(response) + }) + + test('when response is not image', async () => { + const response = await requestAsset('https://whatever.org/notimage') + + expectIsPlaceholderResponse(response) + }) +}) + +async function requestAsset( + url: string, + env = currentTestEnvironment(), +): Promise { + return await env.fetch({ + subdomain: 'asset-proxy', + pathname: '/src?url=' + encodeURIComponent(url), + }) +} diff --git a/__tests__/embed.ts b/__tests__/embed.ts index 079b1444..b1036e9e 100644 --- a/__tests__/embed.ts +++ b/__tests__/embed.ts @@ -10,6 +10,7 @@ import { localTestEnvironment, expectSentryEvent, expectNoSentryError, + expectIsPlaceholderResponse, } from './__utils__' describe('embed.serlo.org/thumbnail?url=...', () => { @@ -651,12 +652,6 @@ describe('embed.serlo.org/thumbnail?url=...', () => { }) }) -function expectIsPlaceholderResponse(response: Response) { - expect(response.status).toBe(200) - expect(response.headers.get('content-type')).toBe('image/png') - expect(response.headers.get('content-length')).toBe('135') -} - async function requestThumbnail( url: string, env = currentTestEnvironment(), From 23ade3c3fa9a5f8570ad15085529a52d6f633195 Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:45:12 +0100 Subject: [PATCH 06/11] feat(asset-proxy): handle errors with placeholder and remove sentry logging --- src/asset-proxy.ts | 33 ++++++++++++++++++++------------- src/embed.ts | 30 ++++++++---------------------- src/utils/image.ts | 20 ++++++++++++++++++++ src/utils/index.tsx | 1 + 4 files changed, 49 insertions(+), 35 deletions(-) create mode 100644 src/utils/image.ts diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index dd1e72ae..7752b115 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -1,4 +1,10 @@ -import { SentryFactory, responseToContext, Url } from './utils' +import { + SentryFactory, + responseToContext, + Url, + getPlaceholder, + isImageResponse, +} from './utils' export async function assetProxy( request: Request, @@ -11,24 +17,25 @@ export async function assetProxy( const urlParam = url.searchParams.get('url') - if (!urlParam) throw new Error('Missing url for the asset') + if (!urlParam) return getPlaceholder() - const assetUrl = new Url(urlParam) + let assetUrl: Url - const originalResponse = await fetch(assetUrl, { cf: { cacheTtl: 24 * 60 * 60 } }) + try { + assetUrl = new Url(urlParam) + } catch { + return getPlaceholder() + } + + const originalResponse = await fetch(assetUrl, { + cf: { cacheTtl: 24 * 60 * 60 }, + }) - if (originalResponse.ok) { + if (originalResponse.ok && isImageResponse(originalResponse)) { const response = new Response(originalResponse.body, originalResponse) response.headers.delete('set-cookie') return response - } else { - const sentry = sentryFactory.createReporter('asset-proxy') - sentry.setContext( - 'response', - responseToContext({ response: originalResponse, text: await originalResponse.text() }), - ) - sentry.captureMessage(`Illegal response of ${assetUrl.toString()}`, 'warning') } - return null + return getPlaceholder() } diff --git a/src/embed.ts b/src/embed.ts index 2f35d280..0e138674 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -1,6 +1,13 @@ import * as t from 'io-ts' -import { SentryFactory, SentryReporter, responseToContext, Url } from './utils' +import { + SentryFactory, + SentryReporter, + responseToContext, + Url, + getPlaceholder, + isImageResponse, +} from './utils' export async function embed( request: Request, @@ -271,24 +278,3 @@ async function getWikimediaThumbnail(url: URL) { return getPlaceholder() } - -function getPlaceholder() { - const placeholderBase64 = - 'iVBORw0KGgoAAAANSUhEUgAAAwAAAAGwAQMAAAAkGpCRAAAAA1BMVEXv9/t0VvapAAAAP0lEQVR42u3BMQEAAADCIPuntsUuYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOqOwAAHrgHqAAAAAAElFTkSuQmCC' - const placeholder = Uint8Array.from(atob(placeholderBase64), (c) => - c.charCodeAt(0), - ) - return new Response(placeholder, { - status: 200, - statusText: 'OK', - headers: { - 'Content-Type': 'image/png', - 'Content-Length': placeholder.length.toString(), - }, - }) -} - -function isImageResponse(res: Response): boolean { - const contentType = res.headers.get('content-type') ?? '' - return res.status === 200 && contentType.startsWith('image/') -} diff --git a/src/utils/image.ts b/src/utils/image.ts new file mode 100644 index 00000000..db8785a9 --- /dev/null +++ b/src/utils/image.ts @@ -0,0 +1,20 @@ +export function getPlaceholder() { + const placeholderBase64 = + 'iVBORw0KGgoAAAANSUhEUgAAAwAAAAGwAQMAAAAkGpCRAAAAA1BMVEXv9/t0VvapAAAAP0lEQVR42u3BMQEAAADCIPuntsUuYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOqOwAAHrgHqAAAAAAElFTkSuQmCC' + const placeholder = Uint8Array.from(atob(placeholderBase64), (c) => + c.charCodeAt(0), + ) + return new Response(placeholder, { + status: 200, + statusText: 'OK', + headers: { + 'Content-Type': 'image/png', + 'Content-Length': placeholder.length.toString(), + }, + }) +} + +export function isImageResponse(res: Response): boolean { + const contentType = res.headers.get('content-type') ?? '' + return res.status === 200 && contentType.startsWith('image/') +} diff --git a/src/utils/index.tsx b/src/utils/index.tsx index 5f81a8be..bb4160e0 100644 --- a/src/utils/index.tsx +++ b/src/utils/index.tsx @@ -4,3 +4,4 @@ export * from './cf-environment' export * from './url' export * from './ui' export * from './cache' +export * from './image' From 5659d9e4878b616d25950f5547b3d1bd658efceb Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:13:49 +0100 Subject: [PATCH 07/11] fix lint errors --- src/asset-proxy.ts | 15 +++------------ src/index.ts | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index 7752b115..a712e7ba 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -1,15 +1,6 @@ -import { - SentryFactory, - responseToContext, - Url, - getPlaceholder, - isImageResponse, -} from './utils' - -export async function assetProxy( - request: Request, - sentryFactory: SentryFactory, -): Promise { +import { Url, getPlaceholder, isImageResponse } from './utils' + +export async function assetProxy(request: Request): Promise { const url = Url.fromRequest(request) if (url.subdomain !== 'asset-proxy') return null diff --git a/src/index.ts b/src/index.ts index 01e1b247..5dea7d7f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,7 +39,7 @@ export default { (await semanticFileNames(request)) || (await api(request, env)) || (await frontendProxy(request, sentryFactory, env)) || - (await assetProxy(request, sentryFactory)) || + (await assetProxy(request)) || (await fetch(request)) ) } catch (e) { From ffb79ba10c92c77ec5fcbf4728a9dc841391104d Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:45:13 +0100 Subject: [PATCH 08/11] refactor(asset-proxy): encode url request to external source --- __tests__/asset-proxy.ts | 2 +- src/asset-proxy.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/__tests__/asset-proxy.ts b/__tests__/asset-proxy.ts index 71888d35..64b60c1a 100644 --- a/__tests__/asset-proxy.ts +++ b/__tests__/asset-proxy.ts @@ -28,7 +28,7 @@ test('request to https://asset-proxy.serlo.org/src?url=* gets asset from url que const env = currentTestEnvironment() const response = await env.fetch({ subdomain: 'asset-proxy', - pathname: '/src?url=https://whatever.org/image', + pathname: `/src?url=${encodeURIComponent('https://whatever.org/image')}`, }) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe('image/png') diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index a712e7ba..400b4353 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -18,9 +18,12 @@ export async function assetProxy(request: Request): Promise { return getPlaceholder() } - const originalResponse = await fetch(assetUrl, { - cf: { cacheTtl: 24 * 60 * 60 }, - }) + const originalResponse = await fetch( + encodeURIComponent(assetUrl.toString()), + { + cf: { cacheTtl: 24 * 60 * 60 }, + }, + ) if (originalResponse.ok && isImageResponse(originalResponse)) { const response = new Response(originalResponse.body, originalResponse) From 08167665f6ac6775f21f98fe6c037843e6f5a90b Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:56:21 +0100 Subject: [PATCH 09/11] Revert "refactor(asset-proxy): encode url request to external source" This reverts commit ffb79ba10c92c77ec5fcbf4728a9dc841391104d. --- __tests__/asset-proxy.ts | 2 +- src/asset-proxy.ts | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/__tests__/asset-proxy.ts b/__tests__/asset-proxy.ts index 64b60c1a..71888d35 100644 --- a/__tests__/asset-proxy.ts +++ b/__tests__/asset-proxy.ts @@ -28,7 +28,7 @@ test('request to https://asset-proxy.serlo.org/src?url=* gets asset from url que const env = currentTestEnvironment() const response = await env.fetch({ subdomain: 'asset-proxy', - pathname: `/src?url=${encodeURIComponent('https://whatever.org/image')}`, + pathname: '/src?url=https://whatever.org/image', }) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe('image/png') diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index 400b4353..a712e7ba 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -18,12 +18,9 @@ export async function assetProxy(request: Request): Promise { return getPlaceholder() } - const originalResponse = await fetch( - encodeURIComponent(assetUrl.toString()), - { - cf: { cacheTtl: 24 * 60 * 60 }, - }, - ) + const originalResponse = await fetch(assetUrl, { + cf: { cacheTtl: 24 * 60 * 60 }, + }) if (originalResponse.ok && isImageResponse(originalResponse)) { const response = new Response(originalResponse.body, originalResponse) From 69abfbe39b7dd319624cf779b6159ad88a733fa2 Mon Sep 17 00:00:00 2001 From: Botho <1258870+elbotho@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:13:33 +0100 Subject: [PATCH 10/11] refactor(asset-proxy): use `/image` instead of `/src` as path --- __tests__/asset-proxy.ts | 8 ++++---- src/asset-proxy.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/__tests__/asset-proxy.ts b/__tests__/asset-proxy.ts index 71888d35..a7d748d3 100644 --- a/__tests__/asset-proxy.ts +++ b/__tests__/asset-proxy.ts @@ -24,11 +24,11 @@ beforeEach(() => { ) }) -test('request to https://asset-proxy.serlo.org/src?url=* gets asset from url query parameter', async () => { +test('request to https://asset-proxy.serlo.org/image?url=* gets asset from url query parameter', async () => { const env = currentTestEnvironment() const response = await env.fetch({ subdomain: 'asset-proxy', - pathname: '/src?url=https://whatever.org/image', + pathname: '/image?url=https://whatever.org/image', }) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe('image/png') @@ -51,7 +51,7 @@ describe('returns placeholder', () => { test('when url query parameter is missing', async () => { const response = await currentTestEnvironment().fetch({ subdomain: 'asset-proxy', - pathname: '/src', + pathname: '/image', }) expectIsPlaceholderResponse(response) @@ -70,6 +70,6 @@ async function requestAsset( ): Promise { return await env.fetch({ subdomain: 'asset-proxy', - pathname: '/src?url=' + encodeURIComponent(url), + pathname: '/image?url=' + encodeURIComponent(url), }) } diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index a712e7ba..37552def 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -4,7 +4,7 @@ export async function assetProxy(request: Request): Promise { const url = Url.fromRequest(request) if (url.subdomain !== 'asset-proxy') return null - if (url.pathname !== '/src') return null + if (url.pathname !== '/image') return null const urlParam = url.searchParams.get('url') From c2030cee792c7c02fe1b0e1852d9c8cd6be67453 Mon Sep 17 00:00:00 2001 From: Hugo Tiburtino <45924645+hugotiburtino@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:07:02 +0100 Subject: [PATCH 11/11] refactor(asset-proxy): increase cache of cf edge and set it for browser --- __tests__/asset-proxy.ts | 3 +++ src/asset-proxy.ts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/__tests__/asset-proxy.ts b/__tests__/asset-proxy.ts index a7d748d3..4c745961 100644 --- a/__tests__/asset-proxy.ts +++ b/__tests__/asset-proxy.ts @@ -33,6 +33,9 @@ test('request to https://asset-proxy.serlo.org/image?url=* gets asset from url q expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe('image/png') expect(response.headers.get('Set-Cookie')).toBeNull() + expect(response.headers.get('cache-control')).toBe( + 'public, max-age=31536000, immutable', + ) }) describe('returns placeholder', () => { diff --git a/src/asset-proxy.ts b/src/asset-proxy.ts index 37552def..496269fb 100644 --- a/src/asset-proxy.ts +++ b/src/asset-proxy.ts @@ -19,12 +19,13 @@ export async function assetProxy(request: Request): Promise { } const originalResponse = await fetch(assetUrl, { - cf: { cacheTtl: 24 * 60 * 60 }, + cf: { cacheTtl: 24 * 60 * 60 * 30 }, }) if (originalResponse.ok && isImageResponse(originalResponse)) { const response = new Response(originalResponse.body, originalResponse) response.headers.delete('set-cookie') + response.headers.set('cache-control', 'public, max-age=31536000, immutable') return response }