From f4bab262cafd6efe3299e68df758347f43f41178 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 16 Jun 2024 14:46:11 +0530 Subject: [PATCH] Import --- infra/workers/files/src/index.ts | 89 +++++++++++++++++++++++++-- infra/workers/thumbnails/src/index.ts | 14 ++--- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/infra/workers/files/src/index.ts b/infra/workers/files/src/index.ts index 0f6532e0e2..cf5aa34123 100644 --- a/infra/workers/files/src/index.ts +++ b/infra/workers/files/src/index.ts @@ -3,13 +3,94 @@ export default { async fetch(request: Request) { switch (request.method) { - // case "OPTIONS": - // return handleOPTIONS(request); - // case "GET": - // return handleGET(request); + case "OPTIONS": + return handleOPTIONS(request); + case "GET": + return handleGET(request); default: console.log(`Unsupported HTTP method ${request.method}`); return new Response(null, { status: 405 }); } }, } satisfies ExportedHandler; + +const handleOPTIONS = (request: Request) => { + const origin = request.headers.get("Origin"); + if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin); + const headers = request.headers.get("Access-Control-Request-Headers"); + if (!areAllowedHeaders(headers)) + console.warn("Unknown header in list", headers); + return new Response("", { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Max-Age": "86400", + // "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package", + "Access-Control-Allow-Headers": "*", + }, + }); +}; + +const isAllowedOrigin = (origin: string | null) => { + const desktopApp = "ente://app"; + const allowedHostnames = [ + "web.ente.io", + "photos.ente.io", + "photos.ente.sh", + "localhost", + ]; + + if (!origin) return false; + try { + const url = new URL(origin); + return origin == desktopApp || allowedHostnames.includes(url.hostname); + } catch { + // origin is likely an invalid URL + return false; + } +}; + +const areAllowedHeaders = (headers: string | null) => { + const allowed = ["x-auth-token", "x-client-package"]; + + if (!headers) return true; + for (const header of headers.split(",")) { + if (!allowed.includes(header.trim().toLowerCase())) return false; + } + return true; +}; + +const handleGET = async (request: Request) => { + const url = new URL(request.url); + + // Random bots keep trying to pentest causing noise in the logs. If the + // request doesn't have a fileID, we can just safely ignore it thereafter. + const fileID = url.searchParams.get("fileID"); + if (!fileID) return new Response(null, { status: 400 }); + + let token = request.headers.get("X-Auth-Token"); + if (!token) { + console.warn("Using deprecated token query param"); + token = url.searchParams.get("token"); + } + + if (!token) { + console.error("No token provided"); + // return new Response(null, { status: 400 }); + } + + // We forward the auth token as a query parameter to museum. This is so that + // it does not get preserved when museum does a redirect to the presigned S3 + // URL that serves the actual thumbnail. + // + // See: [Note: Passing credentials for self-hosted file fetches] + const params = new URLSearchParams(); + if (token) params.set("token", token); + + let response = await fetch( + `https://api.ente.io/files/download/${fileID}?${params.toString()}` + ); + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + return response; +}; diff --git a/infra/workers/thumbnails/src/index.ts b/infra/workers/thumbnails/src/index.ts index 395786bccf..52060532ef 100644 --- a/infra/workers/thumbnails/src/index.ts +++ b/infra/workers/thumbnails/src/index.ts @@ -63,8 +63,6 @@ const areAllowedHeaders = (headers: string | null) => { const handleGET = async (request: Request) => { const url = new URL(request.url); - // Random bots keep trying to pentest causing noise in the logs. If the - // request doesn't have a fileID, we can just safely ignore it thereafter. const fileID = url.searchParams.get("fileID"); if (!fileID) return new Response(null, { status: 400 }); @@ -79,16 +77,16 @@ const handleGET = async (request: Request) => { // return new Response(null, { status: 400 }); } - // We forward the auth token as a query parameter to museum. This is so that - // it does not get preserved when museum does a redirect to the presigned S3 - // URL that serves the actual thumbnail. - // - // See: [Note: Passing credentials for self-hosted file fetches] const params = new URLSearchParams(); if (token) params.set("token", token); let response = await fetch( - `https://api.ente.io/files/preview/${fileID}?${params.toString()}` + `https://api.ente.io/files/preview/${fileID}?${params.toString()}`, + { + headers: { + "User-Agent": request.headers.get("User-Agent") ?? "", + }, + } ); response = new Response(response.body, response); response.headers.set("Access-Control-Allow-Origin", "*");