diff --git a/infra/workers/cast-albums/src/index.ts b/infra/workers/cast-albums/src/index.ts index 9f92fa9df6..921db01a54 100644 --- a/infra/workers/cast-albums/src/index.ts +++ b/infra/workers/cast-albums/src/index.ts @@ -28,12 +28,12 @@ const handleOPTIONS = (request: Request) => { }; const isAllowedOrigin = (origin: string | null) => { + const allowed = ["cast.ente.io", "cast.ente.sh", "localhost"]; + if (!origin) return false; try { const url = new URL(origin); - return ["cast.ente.io", "cast.ente.sh", "localhost"].includes( - url.hostname - ); + return allowed.includes(url.hostname); } catch { // origin is likely an invalid URL return false; @@ -50,14 +50,18 @@ const handleGET = async (request: Request) => { return new Response(null, { status: 400 }); } - const fileID = url.searchParams.get("fileID"); const pathname = url.pathname; + const fileID = url.searchParams.get("fileID"); + if (!fileID) { + console.error("No fileID provided"); + return new Response(null, { status: 400 }); + } const params = new URLSearchParams({ castToken }); + let response = await fetch( `https://api.ente.io/cast/files${pathname}${fileID}?${params.toString()}` ); - response = new Response(response.body, response); response.headers.set("Access-Control-Allow-Origin", "*"); return response; diff --git a/infra/workers/public-albums/package.json b/infra/workers/public-albums/package.json new file mode 100644 index 0000000000..946f42689f --- /dev/null +++ b/infra/workers/public-albums/package.json @@ -0,0 +1,10 @@ +{ + "name": "public-albums", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/public-albums/src/index.ts b/infra/workers/public-albums/src/index.ts new file mode 100644 index 0000000000..6946212f65 --- /dev/null +++ b/infra/workers/public-albums/src/index.ts @@ -0,0 +1,100 @@ +/** Proxy requests for files and thumbnails in public albums. */ + +export default { + async fetch(request: Request) { + switch (request.method) { + 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-Access-Token, X-Auth-Access-Token-JWT", + // "Access-Control-Allow-Headers": "X-Auth-Access-Token, X-Auth-Access-Token-JWT, x-client-package", + "Access-Control-Allow-Headers": "*", + }, + }); +}; + +const isAllowedOrigin = (origin: string | null) => { + const allowed = ["albums.ente.io", "albums.ente.sh", "localhost"]; + + if (!origin) return false; + try { + const url = new URL(origin); + return allowed.includes(url.hostname); + } catch { + // origin is likely an invalid URL + return false; + } +}; + +const areAllowedHeaders = (headers: string | null) => { + // TODO(MR): Stop sending "x-client-package" + const allowed = [ + "x-auth-access-token", + "x-auth-access-token-jwt", + "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); + + let accessToken = request.headers.get("X-Auth-Access-Token"); + if (accessToken === undefined) { + console.warn("Using deprecated accessToken query param"); + accessToken = url.searchParams.get("accessToken"); + } + + if (!accessToken) { + console.error("No accessToken provided"); + // return new Response(null, { status: 400 }); + } + + let accessTokenJWT = request.headers.get("X-Auth-Access-Token-JWT"); + if (accessTokenJWT === undefined) { + console.warn("Using deprecated accessTokenJWT query param"); + accessTokenJWT = url.searchParams.get("accessTokenJWT"); + } + + const pathname = url.pathname; + const fileID = url.searchParams.get("fileID"); + if (!fileID) { + console.error("No fileID provided"); + return new Response(null, { status: 400 }); + } + + const params = new URLSearchParams(); + if (accessToken) params.set("accessToken", accessToken); + if (accessTokenJWT) params.set("accessTokenJWT", accessTokenJWT); + + let response = await fetch( + `https://api.ente.io/public-collection/files${pathname}${fileID}?${params.toString()}` + ); + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + return response; +}; diff --git a/infra/workers/public-albums/tsconfig.json b/infra/workers/public-albums/tsconfig.json new file mode 100644 index 0000000000..a65b752070 --- /dev/null +++ b/infra/workers/public-albums/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../tsconfig.base.json", "include": ["src"] } diff --git a/infra/workers/public-albums/wrangler.toml b/infra/workers/public-albums/wrangler.toml new file mode 100644 index 0000000000..9adad20f04 --- /dev/null +++ b/infra/workers/public-albums/wrangler.toml @@ -0,0 +1,11 @@ +name = "public-albums" +main = "src/index.ts" +compatibility_date = "2024-06-14" + +routes = [ + { pattern = "public-albums.ente.io", custom_domain = true } +] + +tail_consumers = [ + { service = "tail" } +] diff --git a/infra/workers/tail/wrangler.toml b/infra/workers/tail/wrangler.toml index 5a4bce036e..1d6185c138 100644 --- a/infra/workers/tail/wrangler.toml +++ b/infra/workers/tail/wrangler.toml @@ -2,6 +2,9 @@ name = "tail" main = "src/index.ts" compatibility_date = "2024-06-14" +# Disable the default route, this worker does not handle fetch. +workers_dev = false + [vars] # Added as a secret via the Cloudflare dashboard # LOKI_PUSH_URL = "https://${loki_base_url}>/loki/api/v1/push"