v2.10.0: 邮件链路 401 修复 + 一账号一 token + 重装 Worker 按钮——workerAuth 同名脚本撞车时按候选逐一验签确定归属(修 itekmedia 收不到件);凭据 token(credential id)烤进 Worker 脚本、Console 按 token 精确鉴权(脚本名仅作旧版兼容);账号详情新增「重新安装 Worker 脚本」

This commit is contained in:
bigcode
2026-10-05 02:04:13 +08:00
parent 1c7dba025b
commit 2dede46509
19 changed files with 242 additions and 68 deletions
+55 -1
View File
@@ -132,6 +132,60 @@ describe("POST /internal/email/authorize 决策矩阵", () => {
expect(((await resp.json()) as Decision).decision).toBe("accept");
});
test("同名 worker 跨账号:凭据按签名归属,不得只取最新一条(v2.9.3 回归)", async () => {
const t = await makeTestEnv();
const now = new Date().toISOString();
// 第二个账号部署了与默认账号同名的 worker 脚本,凭据创建更晚
await t.env.DB.prepare(
"INSERT INTO cloudflare_accounts (id, name, cloudflare_account_id, worker_id, status, provision_steps, created_at, updated_at) VALUES ('acct_later', 'later-account', 'cfacct-2', ?, 'active', '{}', ?, ?)",
)
.bind(t.workerId, now, now)
.run();
const laterSecret = "whs_later_account_secret";
await t.env.DB.prepare(
"INSERT INTO worker_credentials (id, cloudflare_account_id, worker_id, secret_hash, secret_enc, secret_last4, status, created_at) VALUES ('wcred_later', 'acct_later', ?, ?, ?, ?, 'active', ?)",
)
.bind(t.workerId, await sha256Hex(laterSecret), await encryptSecret(t.env, laterSecret), laterSecret.slice(-4), now)
.run();
await t.env.DB.prepare(
"INSERT INTO domains (id, cloudflare_account_id, account_id, name, status, created_at, updated_at) VALUES ('dom_later', 'cfacct-2', 'acct_later', 'later.test', 'active', ?, ?)",
)
.bind(now, now)
.run();
await t.env.DB.prepare(
"INSERT INTO mailboxes (id, domain_id, address, status, is_catch_all, created_at, updated_at) VALUES ('mbx_later', 'dom_later', '*@later.test', 'active', 1, ?, ?)",
)
.bind(now, now)
.run();
// 先接入账号的 secret 签名(身份=脚本名,兼容路径) -> 必须验到先接入的凭据上(v2.9.3 前被最新凭据压住而 401)
const first = await authorize(t, "hello@example.test");
expect(first.status).toBe(200);
expect(first.body.decision).toBe("accept");
// 后接入账号的 secret 签名(身份=脚本名,兼容路径) -> 归属到后接入账号,它自己的域名 accept
const later = await t.app.fetch(
await signedAuthorizeRequest(t, "hello@later.test", "a@b.c", { workerId: t.workerId, secret: laterSecret }),
t.env,
);
expect(later.status).toBe(200);
expect(((await later.json()) as Decision).decision).toBe("accept");
// v2.10.0 一账号一 token:身份=凭据 id,精确匹配;token 钉死凭据,错 secret 直接 401
const tokenFirst = await t.app.fetch(
await signedAuthorizeRequest(t, "hello@example.test", "a@b.c", { workerId: "wcred1", secret: "wrong-secret" }),
t.env,
);
expect(tokenFirst.status).toBe(401);
const tokenLater = await t.app.fetch(
await signedAuthorizeRequest(t, "hello@later.test", "a@b.c", { workerId: "wcred_later", secret: laterSecret }),
t.env,
);
expect(tokenLater.status).toBe(200);
expect(((await tokenLater.json()) as Decision).decision).toBe("accept");
});
test("畸形收件人 -> reject MAILBOX_NOT_FOUND(不抛 500)", async () => {
const t = await makeTestEnv();
for (const bad of ["no-at-sign", "@example.test", "a@"]) {
@@ -170,7 +224,7 @@ function makeWorkerEnv(opts: FakeConsoleOptions): WorkerEnv {
}) as typeof fetch;
return {
CONSOLE_URL: "http://fake-console",
WORKER_ID: "wmailhub-email-test",
CREDENTIAL_TOKEN: "wcred_test_token",
WORKER_SECRET: "whs_fake_secret",
fetchImpl,
};