fix: v2.3.2 用探测确定真实 scope 词表,接入已实测可用

- 上一版默认 scope 名是猜的(account.read / email_routing.write),全部被拒。
  正确词表是点号分隔的 <group>.<verb>,且:
  - 账号是 account-settings.read(选择器里叫 Account Settings Read),不是 account.read
  - Workers 脚本是 workers-scripts.write,不是 workers_scripts.write
  - Email Routing 在选择器里是四个独立权限,改 catch-all 对应 email-routing-rule.write
  - account:read 这种冒号写法是 wrangler 的第一方简写,第三方 client 一律被拒
  - client 注册的 scope 是上限不是默认:没勾的权限即使账号有也不生效
- preflight 扩展 scope 探测:授权端点在同意页之前就校验并点名拒绝的 scope,
  逐轮「请求剩余候选 -> 剔除被点名项」即可收敛出 App 已授权集合(未认证请求,
  不登录/不产生授权),结果按 allowed/denied 返回并在设置页用绿/橙标签展示
- 缺少账号级读权限时不再失败:discover() 先试 /accounts,403 则改为列 token 可见的 /zones,
  用 zone 自带的 account_id 反推账户(展示名用第一个域名)
- 默认 scope 收敛为接入必需的三项(zone.read / workers-scripts.write /
  email-routing-rule.write),account-settings.read 改为可选:勾了才显示真实账号名
- 实测:线上 preflight 已 ok=true(Cloudflare 跳登录页),无需再改 App 即可「添加账号」
This commit is contained in:
bigcode
2026-09-30 16:26:43 +08:00
parent 1f4d45f719
commit 7a1625d575
9 changed files with 390 additions and 50 deletions
+5 -8
View File
@@ -131,21 +131,18 @@ export async function provisionAccount(env: Env, input: ProvisionInput): Promise
// 1. OAuth 授权
await mark("oauth", "done");
// 2. Account/Zone 发现
// 2. Account/Zone 发现(没有账号级读权限时自动从 zone 的 account_id 反推)
await mark("discover", "running");
const accounts = await cf.listAccounts(input.accessToken);
if (accounts.length === 0) throw new Error("no cloudflare account accessible");
const cfAccount = input.cfAccountId
? accounts.find((a) => a.id === input.cfAccountId) ?? accounts[0]
: accounts[0];
// 自动创建的账号以 Cloudflare 账户名命名
const found = await cf.discover(input.accessToken);
const cfAccount = found.account;
const zones = found.zones;
// 自动创建的账号以 Cloudflare 账户名(或反推出的展示名)命名
const newName = account.name.startsWith("Cloudflare 账号") ? cfAccount.name : account.name;
await env.DB.prepare(
"UPDATE cloudflare_accounts SET cloudflare_account_id = ?, name = ?, updated_at = ? WHERE id = ?",
)
.bind(cfAccount.id, newName, nowIso(), input.accountId)
.run();
const zones = await cf.listZones(input.accessToken, cfAccount.id);
await mark("discover", "done");
// 3. 同步全部域名(pending,等待逐域名「设置 Email Workers」)+ catch-all 邮箱