refactor: v2.2.0 移除本地 sim 模式,Cloudflare OAuth 只走真实授权
- 删除 SimCFApi / WMAILHUB_SIM_CF / isRealOAuth / POST /api/oauth/cloudflare/sim/approve - 删除前端 OAuthSim 模拟授权页与 /oauth/sim/authorize 路由;账号页「添加账号」整页跳 Cloudflare - 迁移 0005 删除 cf_sim_resources 表 - 未配置 CF_OAUTH_CLIENT_ID/SECRET 时 start/callback 一律 503(不再降级出假账号假资源) - 修 OAuth 回调端点:跨站重定向不携带 SameSite=Strict 会话 cookie,改为由一次性 state 承担 CSRF 防护 - 测试改用 tests/cf-mock.ts 在 fetch 层拦截 Cloudflare API,断言真实 HTTP 调用(72/72 通过) - 本地 dev 无账号时只拉起 Console 并提示人工授权,不再自动造账号
This commit is contained in:
@@ -1,25 +1,22 @@
|
||||
/**
|
||||
* Cloudflare API 客户端抽象。
|
||||
* Cloudflare API 客户端:真实调用 api.cloudflare.com / dash.cloudflare.com。
|
||||
*
|
||||
* - RealCFApi:真实调用 api.cloudflare.com / dash.cloudflare.com(OAuth access token)
|
||||
* - SimCFApi :本地 sim 模式。不访问外网,把"创建"的资源记录到 cf_sim_resources 表,
|
||||
* 让 Connect Cloudflare Account 自动接入链路在本地完整可演示、可测试。
|
||||
*
|
||||
* Console 通过 env.CF_OAUTH_CLIENT_ID/SECRET 判断模式;两者齐备为 real,否则 sim。
|
||||
* OAuth access token 来自 Cloudflare OAuth 授权(docs/02),Console 用它完成
|
||||
* 账号/域名发现、Email Worker 部署与 Email Routing 配置。
|
||||
* 未配置 CF_OAUTH_CLIENT_ID/SECRET 时不提供任何降级通道——
|
||||
* /api/oauth/cloudflare/start 直接返回 503,避免出现"看似接入成功"的假数据。
|
||||
*/
|
||||
|
||||
import { renderEmailWorkerScript } from "../../../shared/src/index.ts";
|
||||
import type { Env } from "../env.ts";
|
||||
import { ids } from "../../../shared/src/index.ts";
|
||||
import { nowIso } from "../util.ts";
|
||||
|
||||
/** # 成员变量: OAuth 授权端点(real 模式) **/
|
||||
/** # 成员变量: OAuth 授权端点 **/
|
||||
export const CF_OAUTH_AUTHORIZE_URL = "https://dash.cloudflare.com/oauth2/auth";
|
||||
|
||||
/** # 成员变量: OAuth token 端点(real 模式) **/
|
||||
/** # 成员变量: OAuth token 端点 **/
|
||||
export const CF_OAUTH_TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
||||
|
||||
/** # 成员变量: Cloudflare API 基址(real 模式) **/
|
||||
/** # 成员变量: Cloudflare API 基址 **/
|
||||
export const CF_API_BASE = "https://api.cloudflare.com/client/v4";
|
||||
|
||||
/** # 成员变量: OAuth 申请权限(docs/08:最小权限——账户与 Zone 读、Email Routing 读写、Workers 脚本写) **/
|
||||
@@ -45,7 +42,7 @@ export interface CFZone {
|
||||
|
||||
/** # 成员变量: Cloudflare API 客户端接口(自动安装所需操作) **/
|
||||
export interface CFApi {
|
||||
/** 用 code 换 access token(real);sim 直接签发假 token */
|
||||
/** 用 code 换 access token */
|
||||
exchangeToken(code: string, redirectUri: string): Promise<{ accessToken: string; expiresIn: number }>;
|
||||
/** 发现 token 可访问的账户 */
|
||||
listAccounts(accessToken: string): Promise<CFAccount[]>;
|
||||
@@ -74,15 +71,20 @@ export interface CFApi {
|
||||
}
|
||||
|
||||
/** # 函数
|
||||
* 根据环境构造 Cloudflare API 客户端(real 或 sim)
|
||||
* Cloudflare OAuth 是否已配置(client id + secret 齐备)
|
||||
*/
|
||||
export function getCFApi(env: Env, db: Env["DB"]): CFApi {
|
||||
const real = env.CF_OAUTH_CLIENT_ID && env.CF_OAUTH_CLIENT_SECRET && !env.WMAILHUB_SIM_CF;
|
||||
if (real) return new RealCFApi(env);
|
||||
return new SimCFApi(db);
|
||||
export function isOAuthConfigured(env: Env): boolean {
|
||||
return Boolean(env.CF_OAUTH_CLIENT_ID && env.CF_OAUTH_CLIENT_SECRET);
|
||||
}
|
||||
|
||||
/** # 成员变量: 真实 Cloudflare API 实现 **/
|
||||
/** # 函数
|
||||
* 构造 Cloudflare API 客户端
|
||||
*/
|
||||
export function getCFApi(env: Env): CFApi {
|
||||
return new RealCFApi(env);
|
||||
}
|
||||
|
||||
/** # 成员变量: Cloudflare API 实现 **/
|
||||
export class RealCFApi implements CFApi {
|
||||
/** # 成员变量 **/
|
||||
constructor(private env: Env) {}
|
||||
@@ -186,73 +188,3 @@ export class RealCFApi implements CFApi {
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
|
||||
/** # 成员变量: 本地 sim 实现:资源写入 cf_sim_resources,行为与真实链路一致 **/
|
||||
export class SimCFApi implements CFApi {
|
||||
/** # 成员变量 **/
|
||||
constructor(private db: Env["DB"]) {}
|
||||
|
||||
private async record(kind: string, refId: string, payload: unknown): Promise<void> {
|
||||
await this.db
|
||||
.prepare(
|
||||
"INSERT INTO cf_sim_resources (id, kind, ref_id, payload, created_at) VALUES (?, ?, ?, ?, ?) " +
|
||||
"ON CONFLICT(id) DO UPDATE SET payload = excluded.payload",
|
||||
)
|
||||
.bind(ids.simResource(), kind, refId, JSON.stringify(payload), nowIso())
|
||||
.run();
|
||||
}
|
||||
|
||||
async exchangeToken(_code: string, _redirectUri: string) {
|
||||
return { accessToken: "sim_" + ids.oauth(), expiresIn: 3600 };
|
||||
}
|
||||
|
||||
async listAccounts(_token: string): Promise<CFAccount[]> {
|
||||
// sim 固定一个演示账户;幂等记录
|
||||
const account = { id: "sim-account-0001", name: "WmailHub Sim Account" };
|
||||
await this.record("account", account.id, account);
|
||||
return [account];
|
||||
}
|
||||
|
||||
async listZones(_token: string, accountId: string): Promise<CFZone[]> {
|
||||
// sim 返回多个演示域名,覆盖「OAuth 后自动同步全部域名」的展示
|
||||
const zones = [
|
||||
{ id: "sim-zone-example.test", name: "example.test", account_id: accountId, status: "active" },
|
||||
{ id: "sim-zone-demo.test", name: "demo.test", account_id: accountId, status: "active" },
|
||||
{ id: "sim-zone-mailbox.dev", name: "mailbox.dev", account_id: accountId, status: "active" },
|
||||
];
|
||||
for (const z of zones) await this.record("zone", z.id, z);
|
||||
return zones;
|
||||
}
|
||||
|
||||
async putEmailWorker(opts: {
|
||||
accessToken: string;
|
||||
accountId: string;
|
||||
scriptName: string;
|
||||
consoleUrl: string;
|
||||
workerId: string;
|
||||
workerSecret: string;
|
||||
}) {
|
||||
// 与真实实现同一模板渲染,但只记录不外发
|
||||
await this.record("email_worker", opts.scriptName, {
|
||||
account_id: opts.accountId,
|
||||
console_url: opts.consoleUrl,
|
||||
worker_id: opts.workerId,
|
||||
script: renderEmailWorkerScript(opts.consoleUrl, opts.workerId, opts.workerSecret).length,
|
||||
});
|
||||
return { id: opts.scriptName };
|
||||
}
|
||||
|
||||
async enableEmailRouting(opts: { zoneId: string }) {
|
||||
await this.record("routing_settings", opts.zoneId, { enabled: true });
|
||||
return { enabled: true };
|
||||
}
|
||||
|
||||
async setCatchAllRule(opts: { zoneId: string; workerName: string }) {
|
||||
await this.record("routing_rule", opts.zoneId, {
|
||||
type: "catch_all",
|
||||
action: "worker",
|
||||
value: opts.workerName,
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user