From a39ebcf9d11bb4e11382dec7530e6bb3f1ac67a9 Mon Sep 17 00:00:00 2001 From: anonsaber Date: Mon, 28 Sep 2026 10:02:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(worker):=20safelist=20=E7=BA=B3=E5=85=A5?= =?UTF-8?q?=20push/register=20=E4=B8=8E=20push/disable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 网关白名单此前无条件 fail-closed,后端注册的 18 条路由中只有 POST /api/push/register 与 POST /api/push/disable 两条需要业务流量却未 被转发,导致 PWA 订阅/退订必须绕过网关直连后端。 - SAFE_ROUTES 12 → 14 条(backends.js:9-24) - ROUTE_METHODS 同步补两条 POST(index.js:40-55) - 头注释路由清单同步 - backends.test.js 转发集合断言补齐两条 - index.test.js 新增转发端到端用例(含鉴权头透传路径) 单入口边界:两条路由仍经 SAFE_ROUTES 无条件校验,未放宽白名单语义。 --- cloudflare-worker/src/backends.js | 2 ++ cloudflare-worker/src/index.js | 4 +++- cloudflare-worker/test/backends.test.js | 1 + cloudflare-worker/test/index.test.js | 25 +++++++++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cloudflare-worker/src/backends.js b/cloudflare-worker/src/backends.js index acb6db7..1a31546 100644 --- a/cloudflare-worker/src/backends.js +++ b/cloudflare-worker/src/backends.js @@ -17,6 +17,8 @@ export const SAFE_ROUTES = Object.freeze([ "/api/admin/session/revoke", "/webhook/tg", "/push/jmap", + "/api/push/register", + "/api/push/disable", "/reconcile", "/ready", ]); diff --git a/cloudflare-worker/src/index.js b/cloudflare-worker/src/index.js index 9308c61..1f55e47 100644 --- a/cloudflare-worker/src/index.js +++ b/cloudflare-worker/src/index.js @@ -16,7 +16,7 @@ * - GET|PUT /api/config、PUT /api/business-config、POST /api/admin/session[/revoke] * → 透传至后端;后端校验 bootstrap 凭据或短期 admin session * - GET /healthz → LB 聚合健康(MOD-HEALTH-AGG) - * - POST /webhook/tg|/push/jmap|/reconcile、GET /ready → 透传 + 有界故障转移 + * - POST /webhook/tg|/push/jmap|/api/push/register|/api/push/disable|/reconcile、GET /ready → 透传 + 有界故障转移 * - 其它 → 404 / 405 * * 日志红线:仅打「方法 / 路径 / origin / 失败类别」,绝不打印 header/body/secret。 @@ -48,6 +48,8 @@ const ROUTE_METHODS = Object.freeze({ "/api/admin/session/revoke": ["POST"], "/webhook/tg": ["POST"], "/push/jmap": ["POST"], + "/api/push/register": ["POST"], + "/api/push/disable": ["POST"], "/reconcile": ["POST"], "/ready": ["GET"], }); diff --git a/cloudflare-worker/test/backends.test.js b/cloudflare-worker/test/backends.test.js index 00fa866..af52ca8 100644 --- a/cloudflare-worker/test/backends.test.js +++ b/cloudflare-worker/test/backends.test.js @@ -45,6 +45,7 @@ test("BACKEND_ORIGINS_JSON parses, dedups, validates all entries", () => { test("route safelist excludes /healthz and unknown paths (C-LB-SINGLE-REG-URL)", () => { for (const r of [ "/", "/assets/config.js", "/assets/styles.css", "/api/status", "/ready", "/webhook/tg", "/push/jmap", "/reconcile", + "/api/push/register", "/api/push/disable", "/api/config", "/api/business-config", "/api/admin/session", "/api/admin/session/revoke", ]) { assert.ok(LB_ROUTES.includes(r), `forwarded: ${r}`); diff --git a/cloudflare-worker/test/index.test.js b/cloudflare-worker/test/index.test.js index 1870bf4..d229097 100644 --- a/cloudflare-worker/test/index.test.js +++ b/cloudflare-worker/test/index.test.js @@ -131,3 +131,28 @@ test("Worker: valid env, GET /ready forwarded to backend with auth header intact restore(); } }); + +test("Worker: POST /api/push/register and /api/push/disable are forwarded", async () => { + const seen = []; + const restore = stubFetch(async (url, init) => { + seen.push([url, init.method]); + return new Response("{\"error\":\"invalid_request\",\"request_id\":\"x\"}", { + status: 400, + headers: { "content-type": "application/json" }, + }); + }); + try { + for (const path of ["/api/push/register", "/api/push/disable"]) { + const res = await handleFetch( + new Request(`https://lb.example${path}`, { method: "POST", body: "{}" }), + makeEnv(), + ); + assert.equal(res.status, 400); + assert.deepEqual(await res.json(), { error: "invalid_request", request_id: "x" }); + } + assert.equal(seen.length, 2); + assert.ok(seen.every(([url, method]) => method === "POST")); + } finally { + restore(); + } +});