From ed406e7eb0dfa2e1c2c9028156628d8b733a4b6a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 16 Jun 2024 20:26:55 +0530 Subject: [PATCH 1/5] Sketch --- infra/workers/uploader/package.json | 10 ++++++++++ infra/workers/uploader/src/index.ts | 19 +++++++++++++++++++ infra/workers/uploader/tsconfig.json | 1 + infra/workers/uploader/wrangler.toml | 7 +++++++ 4 files changed, 37 insertions(+) create mode 100644 infra/workers/uploader/package.json create mode 100644 infra/workers/uploader/src/index.ts create mode 100644 infra/workers/uploader/tsconfig.json create mode 100644 infra/workers/uploader/wrangler.toml diff --git a/infra/workers/uploader/package.json b/infra/workers/uploader/package.json new file mode 100644 index 0000000000..e22b4eb1fc --- /dev/null +++ b/infra/workers/uploader/package.json @@ -0,0 +1,10 @@ +{ + "name": "uploader", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/uploader/src/index.ts b/infra/workers/uploader/src/index.ts new file mode 100644 index 0000000000..e1a90df9d8 --- /dev/null +++ b/infra/workers/uploader/src/index.ts @@ -0,0 +1,19 @@ +/** + * Proxy file uploads. + * + * See: https://ente.io/blog/tech/making-uploads-faster/ + */ + +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; diff --git a/infra/workers/uploader/tsconfig.json b/infra/workers/uploader/tsconfig.json new file mode 100644 index 0000000000..a65b752070 --- /dev/null +++ b/infra/workers/uploader/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../tsconfig.base.json", "include": ["src"] } diff --git a/infra/workers/uploader/wrangler.toml b/infra/workers/uploader/wrangler.toml new file mode 100644 index 0000000000..9a03d8c6d5 --- /dev/null +++ b/infra/workers/uploader/wrangler.toml @@ -0,0 +1,7 @@ +name = "uploader" +main = "src/index.ts" +compatibility_date = "2024-06-14" + +routes = [{ pattern = "uploader.ente.io", custom_domain = true }] + +tail_consumers = [{ service = "tail" }] From a4bc5fa0df89d6cf14204291b5fac1505828af4e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 16 Jun 2024 20:49:37 +0530 Subject: [PATCH 2/5] OPTIONS --- infra/workers/uploader/src/index.ts | 51 +++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/infra/workers/uploader/src/index.ts b/infra/workers/uploader/src/index.ts index e1a90df9d8..9dd2a073f6 100644 --- a/infra/workers/uploader/src/index.ts +++ b/infra/workers/uploader/src/index.ts @@ -7,8 +7,8 @@ export default { async fetch(request: Request) { switch (request.method) { - // case "OPTIONS": - // return handleOPTIONS(request); + case "OPTIONS": + return handleOPTIONS(request); // case "GET": // return handleGET(request); default: @@ -17,3 +17,50 @@ export default { } }, } 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": "POST, PUT, OPTIONS", + "Access-Control-Max-Age": "86400", + // "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package", + "Access-Control-Allow-Headers": "*", + "Access-Control-Expose-Headers": "X-Request-ID, CF-Ray", + }, + }); +}; + +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; +}; From bfcd84c9400018a551e51888a6727b817bd8144d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 16 Jun 2024 20:56:28 +0530 Subject: [PATCH 3/5] Whitelist the necessary one --- infra/workers/uploader/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/workers/uploader/src/index.ts b/infra/workers/uploader/src/index.ts index 9dd2a073f6..3cc2fe8b31 100644 --- a/infra/workers/uploader/src/index.ts +++ b/infra/workers/uploader/src/index.ts @@ -29,7 +29,7 @@ const handleOPTIONS = (request: Request) => { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, PUT, OPTIONS", "Access-Control-Max-Age": "86400", - // "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package", + // "Access-Control-Allow-Headers": "UPLOAD-URL", "Access-Control-Allow-Headers": "*", "Access-Control-Expose-Headers": "X-Request-ID, CF-Ray", }, @@ -56,7 +56,7 @@ const isAllowedOrigin = (origin: string | null) => { }; const areAllowedHeaders = (headers: string | null) => { - const allowed = ["x-auth-token", "x-client-package"]; + const allowed = ["UPLOAD-URL"]; if (!headers) return true; for (const header of headers.split(",")) { From d5a858615277f078ec4cc9cb2e7d23c66c58e095 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 17 Jun 2024 03:40:20 +0530 Subject: [PATCH 4/5] Import functionality Rewritten but referencing the code imported from dashboard --- infra/workers/uploader/src/index.ts | 63 +++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/infra/workers/uploader/src/index.ts b/infra/workers/uploader/src/index.ts index 3cc2fe8b31..3d413d4b3d 100644 --- a/infra/workers/uploader/src/index.ts +++ b/infra/workers/uploader/src/index.ts @@ -9,8 +9,10 @@ export default { switch (request.method) { case "OPTIONS": return handleOPTIONS(request); - // case "GET": - // return handleGET(request); + case "POST": + return handlePOSTOrPUT(request); + case "PUT": + return handlePOSTOrPUT(request); default: console.log(`Unsupported HTTP method ${request.method}`); return new Response(null, { status: 405 }); @@ -31,7 +33,7 @@ const handleOPTIONS = (request: Request) => { "Access-Control-Max-Age": "86400", // "Access-Control-Allow-Headers": "UPLOAD-URL", "Access-Control-Allow-Headers": "*", - "Access-Control-Expose-Headers": "X-Request-ID, CF-Ray", + "Access-Control-Expose-Headers": "X-Request-Id, CF-Ray", }, }); }; @@ -64,3 +66,58 @@ const areAllowedHeaders = (headers: string | null) => { } return true; }; + +const handlePOSTOrPUT = async (request: Request) => { + const url = new URL(request.url); + + const uploadURL = request.headers.get("UPLOAD-URL"); + if (!uploadURL) { + console.error("No uploadURL provided"); + return new Response(null, { status: 400 }); + } + + let response: Response; + switch (url.pathname) { + case "/file-upload": + response = await fetch(uploadURL, { + method: request.method, + body: request.body, + }); + break; + case "/multipart-upload": + response = await fetch(uploadURL, { + method: request.method, + body: request.body, + }); + if (response.ok) { + const etag = response.headers.get("etag"); + if (etag === null) { + console.log("No etag in response", response); + response = new Response(null, { status: 500 }); + } else { + response = new Response(JSON.stringify({ etag })); + } + } + break; + case "/multipart-complete": + response = await fetch(uploadURL, { + method: request.method, + body: request.body, + headers: { + "Content-Type": "text/xml", + }, + }); + break; + default: + response = new Response(null, { status: 404 }); + break; + } + + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + response.headers.set( + "Access-Control-Expose-Headers", + "X-Request-Id, CF-Ray" + ); + return response; +}; From 80b34f1aef8d10cf32ea31db70996205e268c680 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 17 Jun 2024 09:02:55 +0530 Subject: [PATCH 5/5] Reduce spurious logs for headers our clients send --- infra/workers/uploader/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/workers/uploader/src/index.ts b/infra/workers/uploader/src/index.ts index 3d413d4b3d..d94aeb048c 100644 --- a/infra/workers/uploader/src/index.ts +++ b/infra/workers/uploader/src/index.ts @@ -31,7 +31,7 @@ const handleOPTIONS = (request: Request) => { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, PUT, OPTIONS", "Access-Control-Max-Age": "86400", - // "Access-Control-Allow-Headers": "UPLOAD-URL", + // "Access-Control-Allow-Headers": "Content-Type", "UPLOAD-URL, X-Client-Package", "Access-Control-Allow-Headers": "*", "Access-Control-Expose-Headers": "X-Request-Id, CF-Ray", }, @@ -58,7 +58,7 @@ const isAllowedOrigin = (origin: string | null) => { }; const areAllowedHeaders = (headers: string | null) => { - const allowed = ["UPLOAD-URL"]; + const allowed = ["Content-Type", "UPLOAD-URL", "X-Client-Package"]; if (!headers) return true; for (const header of headers.split(",")) {