v2.4.0 修 Email Routing 假成功 + 加 API Token 接入与邮箱/域名页交互改进
问题 6(收不到邮件、Dashboard 邮件路由里看不到域名)的根因:
上一版把「开启 Email Routing」改成了 PUT /zones/{id}/email/routing,
但实测该接口(以及 PATCH)是**纯回显** —— 返回 success:true 但 enabled 纹丝不动,
域名在 Cloudflare 侧仍是 unconfigured、连 MX 记录都没有。Console 却照它的返回值
把域名标成「已配置 Email Workers」,于是「显示配好了却收不到邮件」。
实测只有 POST /zones/{id}/email/routing/enable 真的开启(线上对 adx9.com 与
galaxyemail.com 逐一验证)。
后端
- cfapi: 改用 /enable,并在开启后回读 GET 校验 enabled===true,不满足即判失败;
新增 CFApiError(带 Cloudflare 错误码)与 getEmailRoutingSettings
- cfapi: 错误码 2008(Non-Cloudflare MX records exist)翻译成中文并点名冲突的 MX 记录
- provision: Email Worker 统一命名 wmailhub-email(原先按账号 id 拼名),
部署走 PUT,同名即覆盖更新
- db: 新增 decryptAccountToken —— 按账号取 token,不再取「最近一条连接」,
并跳过已过期的 OAuth token;oauth_connections.account_id 真正写入
- resources: 新增 POST /api/accounts/token(API Token 方式添加账号):
先校验 token 与账户 ID,确认可用才建账号并自动接入
前端
- 账号:「添加账号」改为模态窗口,OAuth / API Token 两个 tab,各自带说明
- 域名: 删除「Email Worker」列;「设置 Email Workers / 重新配置」先弹确认框
(说明会改 MX 与路由),执行期间按钮 loading 且互相禁用;error 状态改成可读的 Alert + 重试
- 邮箱: 删除「状态」列(已由「启用」开关承担);地址列加复制按钮(带弹跳+变绿动画);
「添加邮箱」窗口的域名只列已装 Email Workers 的、支持搜索、默认选中最近使用的域名;
地址框可一键从 namedb 取随机用户名;勾选 catch-all 后地址框禁用
- 收件箱: 域名筛选只显示已装 Email Workers 的域名
测试
- cf-mock 复刻「PUT/PATCH 回 success 但不改 enabled」与 2008,锁住本次修复
- oauth.test 新增 4 例:2008 冲突、回读校验、API Token 接入、Token/账户 ID 校验失败
- ui-smoke 覆盖新增的添加账号/添加邮箱/确认框等交互(28/28 通过)
文档: docs/02 补「三种开启接口只有 /enable 有效」的实测表、2008 处理、
两种接入方式与按账号取 token 的说明
This commit is contained in:
+39
-8
@@ -33,6 +33,10 @@ export interface CfMockOptions {
|
||||
scopesAllowed?: string[];
|
||||
/** 授权端点固定错误(走 Cloudflare 自己的 /oauth/error 页,不弹回回调地址) **/
|
||||
authorizeError?: string;
|
||||
/** Email Routing 开启被拒(模拟 2008:域名上已有非 Cloudflare 的 MX 记录) **/
|
||||
emailRoutingBlocked?: boolean;
|
||||
/** emailRoutingBlocked 时 /dns_records 返回的 MX 列表(用于验证中文提示点名了冲突记录) **/
|
||||
foreignMx?: { id?: string; name: string; content: string; priority?: number }[];
|
||||
}
|
||||
|
||||
/** # 成员变量: Cloudflare API 桩句柄 **/
|
||||
@@ -66,6 +70,8 @@ export function installCloudflareMock(opts: CfMockOptions = {}): CfMock {
|
||||
const accessToken = opts.accessToken ?? "cf-oauth-access-token-xyz";
|
||||
const calls: CfCall[] = [];
|
||||
const original = globalThis.fetch;
|
||||
// Email Routing 的真实开关状态:PUT/PATCH 不会改它,只有 POST .../enable 会
|
||||
const state = { emailRoutingEnabled: false };
|
||||
|
||||
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||
@@ -96,6 +102,13 @@ export function installCloudflareMock(opts: CfMockOptions = {}): CfMock {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
/** Cloudflare 风格的失败信封(success:false + errors 数组) */
|
||||
const jsonError = (errors: { code: number; message: string }[], status: number) =>
|
||||
new Response(JSON.stringify({ success: false, errors, messages: [], result: null }), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
if (url.startsWith(CF_AUTHORIZE_URL)) {
|
||||
if (opts.authorizeError) {
|
||||
return new Response(null, {
|
||||
@@ -147,18 +160,36 @@ export function installCloudflareMock(opts: CfMockOptions = {}): CfMock {
|
||||
}
|
||||
if (url.startsWith(`${API_BASE}/zones`)) {
|
||||
if (url.includes("/email/routing/rules/catch_all")) return json({ id: "catch-all-rule" });
|
||||
if (url.endsWith("/email/routing")) {
|
||||
// 开启 Email Routing 的正确方法是 PUT;POST 到同一路径不是有效路由,
|
||||
// 真实 Cloudflare 返回 10405(2026-09-30 线上踩过),这里照抄以便锁住修复。
|
||||
if (method !== "PUT") {
|
||||
return json(
|
||||
{ success: false, errors: [{ code: 10405, message: "Method not allowed for this authentication scheme" }], result: null },
|
||||
// 开启 Email Routing 只有一个接口真的干活,这里照抄 Cloudflare 的真实行为:
|
||||
// POST .../email/routing/enable -> 真的把 enabled 翻成 true
|
||||
// PUT / PATCH .../email/routing -> 回 success:true 但 enabled 原封不动(纯回显)
|
||||
// 2026-09-30 线上正是因为信了 PUT 的返回值,把没开成 Email Routing 的域名标成了「已配置」,
|
||||
// 表现为域名显示配好了却收不到邮件、Dashboard 邮件路由里也找不到它。
|
||||
if (url.endsWith("/email/routing/enable")) {
|
||||
if (method !== "POST") {
|
||||
return jsonError(
|
||||
[{ code: 10405, message: "Method not allowed for this authentication scheme" }],
|
||||
405,
|
||||
);
|
||||
}
|
||||
return json({ id: url.split("/").at(-2), enabled: true, skip_wizard: true, status: "ready" });
|
||||
if (opts.emailRoutingBlocked) {
|
||||
return jsonError([{ code: 2008, message: "Non-Cloudflare MX records exist" }], 400);
|
||||
}
|
||||
state.emailRoutingEnabled = true;
|
||||
return json({ id: url.split("/").at(-3), enabled: true, skip_wizard: true, status: "ready" });
|
||||
}
|
||||
if (url.includes("/email/routing")) return json({ enabled: true });
|
||||
if (url.endsWith("/email/routing")) {
|
||||
// 回显型接口:无论 PUT 还是 PATCH,一律 success 但绝不改 enabled
|
||||
return json({
|
||||
id: url.split("/").at(-2),
|
||||
name: zones[0]?.name ?? "example.com",
|
||||
enabled: state.emailRoutingEnabled,
|
||||
skip_wizard: true,
|
||||
status: state.emailRoutingEnabled ? "ready" : "unconfigured",
|
||||
});
|
||||
}
|
||||
// 开启被 2008 挡下时,补一次 MX 查询,验证中文提示里确实点名了冲突记录
|
||||
if (url.includes("/dns_records")) return json(opts.foreignMx ?? []);
|
||||
return json(zones);
|
||||
}
|
||||
if (url.includes("/workers/scripts/")) return json({ id: url.split("/").pop() });
|
||||
|
||||
Reference in New Issue
Block a user