fix(email): 开启 Email Routing 改用 PUT,并补上 zone-settings.write 权限
POST /zones/{id}/email/routing 不是有效路由,Cloudflare 返回
10405 Method not allowed for this authentication scheme。正确接口是
PUT /zones/{id}/email/routing(Apply Email Routing settings);
POST .../email/routing/enable 能用但官方已标 Deprecated。
该接口的 Accepted Permission 是 Zone Settings Write,而不是 Email Routing 权限族
—— 权限名和接口所属资源组对不上,Email Routing 四个权限全勾也不够。
- CF_OAUTH_SCOPES_DEFAULT / 探测候选集加 zone-settings.write;设置页文案同步
- cfapi 的 API 错误带上 Cloudflare 错误码(如 10405),便于区分路径错与权限错
- cf-mock 对非 PUT 的 /email/routing 返回 10405,旧写法会被打回
- 88/88 测试
This commit is contained in:
@@ -335,7 +335,8 @@ export interface OAuthPreflightResult {
|
||||
}
|
||||
|
||||
/** 本应用默认需要的 scope(点号分隔)——与后端 CF_OAUTH_SCOPES_DEFAULT 保持一致 */
|
||||
export const CF_OAUTH_SCOPES_DEFAULT = "zone.read workers-scripts.write email-routing-rule.write";
|
||||
export const CF_OAUTH_SCOPES_DEFAULT =
|
||||
"zone.read workers-scripts.write email-routing-rule.write zone-settings.write";
|
||||
|
||||
/** 授权配置自检:探测 Cloudflare 授权端点是否接受当前配置(不产生授权) */
|
||||
export const oauthPreflight = () => api<OAuthPreflightResult>("/api/oauth/cloudflare/preflight");
|
||||
|
||||
@@ -390,11 +390,13 @@ function CloudflareTab({ info, onSaved }: { info: SettingsInfo; onSaved: () => v
|
||||
<>
|
||||
Cloudflare 的 OAuth scope 是<b>点号分隔</b>(zone.read),冒号写法(zone:read)是 wrangler 的
|
||||
第一方简写,第三方客户端会被判 invalid_scope。必须与 OAuth App 里勾选的权限一致;检测按钮会
|
||||
直接告诉你哪些已授权、哪些还要补勾。三项就够:账号名随 zone 一起返回,不需要账号级权限。
|
||||
直接告诉你哪些已授权、哪些还要补勾。四项分别对应:发现域名、上传 Email Worker、改 catch-all
|
||||
规则、<b>开启 Email Routing</b>(最后这项在选择器里叫 Zone Settings Write,不归 Email
|
||||
Routing 管,漏了会在「设置 Email Workers」时才报错)。
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Input placeholder="account.read zone.read email_routing.write workers_scripts.write" />
|
||||
<Input placeholder="zone.read workers-scripts.write email-routing-rule.write zone-settings.write" />
|
||||
</Form.Item>
|
||||
<Space>
|
||||
<Button loading={checking} onClick={() => void runPreflight()}>
|
||||
|
||||
@@ -36,6 +36,9 @@ export const CF_OAUTH_CALLBACK_PATH = "/api/oauth/cloudflare/callback";
|
||||
* 该接口只要求 Zone Read,所以本应用完全用不到 Account Settings Read。
|
||||
* - Email Routing 在选择器里是 **四个独立权限**,其中改 catch-all 规则对应
|
||||
* `email-routing-rule.write`(选择器里叫 Email Routing Rules Edit)。
|
||||
* - **「开启 Email Routing」不归 Email Routing 权限管**:PUT /zones/{id}/email/routing
|
||||
* 的 Accepted Permission 是 Zone Settings Write => `zone-settings.write`。
|
||||
* 权限名和接口所属的资源组对不上是这里最大的坑,配少了会在「设置 Email Workers」时才报出来。
|
||||
* - 客户端注册的 scope 是「上限」而不是「默认值」:请求里没写的 scope 不会自动生效。
|
||||
* 以上名称可用 /cloudflare/preflight 的 scope 探测功能现场核对(见 discoverAllowedScopes)。
|
||||
**/
|
||||
@@ -43,6 +46,7 @@ export const CF_OAUTH_SCOPES_DEFAULT = [
|
||||
"zone.read",
|
||||
"workers-scripts.write",
|
||||
"email-routing-rule.write",
|
||||
"zone-settings.write",
|
||||
].join(" ");
|
||||
|
||||
/** # 成员变量: scope 探测候选集(本应用所需 + 常见备选)
|
||||
@@ -51,6 +55,7 @@ export const CF_OAUTH_SCOPE_CANDIDATES = [
|
||||
"zone.read",
|
||||
"workers-scripts.write",
|
||||
"email-routing-rule.write",
|
||||
"zone-settings.write",
|
||||
"email-routing-account-rule.read",
|
||||
"email-routing-address.write",
|
||||
"email-routing-suppression.write",
|
||||
@@ -478,8 +483,18 @@ export class RealCFApi implements CFApi {
|
||||
...(init?.headers ?? {}),
|
||||
},
|
||||
});
|
||||
const data = (await resp.json()) as { success: boolean; errors?: { message: string }[]; result: T };
|
||||
if (!data.success) throw new Error(`cloudflare api ${path}: ${data.errors?.[0]?.message ?? resp.status}`);
|
||||
const data = (await resp.json()) as {
|
||||
success: boolean;
|
||||
errors?: { code?: number; message: string }[];
|
||||
result: T;
|
||||
};
|
||||
if (!data.success) {
|
||||
const first = data.errors?.[0];
|
||||
// 带上 Cloudflare 的错误码(如 10405),只看 message 经常分不清是路径错还是权限错
|
||||
throw new Error(
|
||||
`cloudflare api ${path}: ${first?.code ? `[${first.code}] ` : ""}${first?.message ?? resp.status}`,
|
||||
);
|
||||
}
|
||||
return data.result;
|
||||
}
|
||||
|
||||
@@ -539,12 +554,19 @@ export class RealCFApi implements CFApi {
|
||||
return { id: opts.scriptName };
|
||||
}
|
||||
|
||||
/** 开启 Zone 的 Email Routing(加并锁定 MX/SPF 记录)
|
||||
* 注意:走 `PUT /zones/{id}/email/routing`(Apply Email Routing settings)。
|
||||
* - `POST /zones/{id}/email/routing` 不是有效路由,Cloudflare 返回
|
||||
* `10405 Method not allowed for this authentication scheme`(2026-09-30 线上踩过)
|
||||
* - `POST .../email/routing/enable` 虽然能用,但官方已标 Deprecated
|
||||
* 该接口的 Accepted Permission 是 **Zone Settings Write**(不是 Email Routing 那一类权限)。 */
|
||||
async enableEmailRouting(opts: { accessToken: string; zoneId: string }) {
|
||||
await this.api(opts.accessToken, `/zones/${opts.zoneId}/email/routing`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ enabled: true, skip_wizard: true }),
|
||||
});
|
||||
return { enabled: true };
|
||||
const settings = await this.api<{ enabled?: boolean; status?: string }>(
|
||||
opts.accessToken,
|
||||
`/zones/${opts.zoneId}/email/routing`,
|
||||
{ method: "PUT", body: JSON.stringify({ enabled: true, skip_wizard: true }) },
|
||||
);
|
||||
return { enabled: settings?.enabled ?? true };
|
||||
}
|
||||
|
||||
async setCatchAllRule(opts: { accessToken: string; zoneId: string; workerName: string }) {
|
||||
|
||||
Reference in New Issue
Block a user