fix: v2.3.1 Cloudflare OAuth scope 点号格式 + 授权失败可读提示 + 配置自检

- 根因:Cloudflare OAuth 的 scope 是点号分隔(account.read),冒号写法不被接受,
  授权端点直接返回 error=invalid_scope。默认 scope 改为
  account.read zone.read email_routing.write workers_scripts.write
- scope 可在设置页自定义(settings.cf_oauth_scopes),需与 App 登记值逐字一致
- 新增 GET /api/oauth/cloudflare/preflight + 设置页「检测授权配置」按钮:
  只读探测授权端点(不跟随跳转),区分 登录同意页 / 弹回回调 / Cloudflare /oauth/error,
  并把 invalid_scope、invalid_request 翻译成可执行的处置建议
- 回调端点处理 error 参数:不再拿空 code 去兑换 token,改为跳回账号页并弹窗说明原因
  (此前会落到 SPA 404 空壳页)
- 账号页读取 ?oauth_error= 弹窗展示 Cloudflare 的原始描述
- docs/02 补 Cloudflare OAuth App 配置清单(Redirect URL / Client URI 域名校验 /
  Promote to public / 点号 scope);83 个测试全通过
This commit is contained in:
bigcode
2026-09-30 16:08:27 +08:00
parent 3be6e483b4
commit 1f4d45f719
11 changed files with 426 additions and 25 deletions
+16 -1
View File
@@ -306,6 +306,8 @@ export interface SettingsInfo {
secret_last4: string;
/** settings = 后台设置;env = 部署变量兜底;none = 未配置 */
source: "settings" | "env" | "none";
/** 实际请求的 scope(点号分隔,空格分隔多个) */
scope: string;
};
/** 需要在 Cloudflare Dashboard 登记的授权回调地址 */
redirect_uri: string;
@@ -317,9 +319,22 @@ export const getSettings = () => api<SettingsInfo>("/api/settings");
* webhook.key / cf_oauth.secret 语义一致:undefined 保留原值、空串清除、非空设置新值 */
export const putSettings = (body: {
webhook?: { url: string; headers: Record<string, string>; key?: string };
cf_oauth?: { client_id: string; secret?: string };
cf_oauth?: { client_id: string; secret?: string; scope?: string };
}) => api("/api/settings", "PUT", body);
/** # 成员变量: 授权配置自检结果 **/
export interface OAuthPreflightResult {
ok: boolean;
location: string;
error: string | null;
description: string | null;
hint: string | null;
used: { client_id: string; redirect_uri: string; scope: string };
}
/** 授权配置自检:探测 Cloudflare 授权端点是否接受当前配置(不产生授权) */
export const oauthPreflight = () => api<OAuthPreflightResult>("/api/oauth/cloudflare/preflight");
/** 清除后台保存的 Cloudflare OAuth 客户端凭据(env 兜底不受影响) */
export const deleteCfOAuth = () => api("/api/settings/cf-oauth", "DELETE");