feat: v2.0.0 邮件接收架构改造 —— 移除 Node,Email Worker 直连 Console(docs/11)

- 两阶段接收:POST /internal/email/authorize(accept/reject/temporary_error)+ POST /internal/email/ingest(raw MIME 直传)
- Worker-Console 协议:Authorization: WmailHub-Worker + timestamp/nonce/body-hash/HMAC,nonce 防重放,凭据撤销即时生效
- 数据模型:nodes/node_credentials -> cloudflare_accounts/worker_credentials(迁移 0003),emails/domains 归属 account_id
- 接入流程 Connect Cloudflare Account:OAuth 自动同步域名 + 部署账号级 Email Worker + 签发 Worker 凭据;rotate 自动重部署
- 动态邮箱:创建/删除/禁用下一封邮件立即生效;拒绝码 DOMAIN_*/MAILBOX_*;超时/5xx 不伪装成邮箱不存在
- 删除 node/ 目录、旧 /api/internal/nodes 协议、ingress/enrollment 概念;dev.ts 简化为 console + email worker 双进程
- 前端:账号页 Email Worker/凭据管理,设置窗口移除默认 Ingress,仪表盘/文案同步;版本 2.0.0
- 测试 62 项全过(新增 authorize 决策矩阵 + Worker 两阶段临时错误语义);本机虚拟 E2E 12 场景全通
- docs 00-10 同步改写;README 更新
This commit is contained in:
bigcode
2026-09-30 13:10:26 +08:00
parent 421cfb2f04
commit fec96e49cc
56 changed files with 2584 additions and 1416 deletions
+20 -13
View File
@@ -3,7 +3,7 @@
*
* - RealCFApi:真实调用 api.cloudflare.com / dash.cloudflare.com(OAuth access token)
* - SimCFApi :本地 sim 模式。不访问外网,把"创建"的资源记录到 cf_sim_resources 表,
* 让 Add Node 自动安装链路在本地完整可演示、可测试。
* 让 Connect Cloudflare Account 自动接入链路在本地完整可演示、可测试。
*
* Console 通过 env.CF_OAUTH_CLIENT_ID/SECRET 判断模式;两者齐备为 real,否则 sim。
*/
@@ -51,13 +51,17 @@ export interface CFApi {
listAccounts(accessToken: string): Promise<CFAccount[]>;
/** 发现账户下的域名 Zone */
listZones(accessToken: string, accountId: string): Promise<CFZone[]>;
/** 创建或更新 Email Worker(幂等) */
/** 创建或更新账号级 Email Worker(幂等) */
putEmailWorker(opts: {
accessToken: string;
accountId: string;
scriptName: string;
ingressUrl: string;
ingressSecret: string;
/** Console 对外地址(Worker -> Console) **/
consoleUrl: string;
/** Worker 身份(与 WorkerCredential.worker_id 一致) **/
workerId: string;
/** Worker credential secret(HMAC 签名密钥) **/
workerSecret: string;
}): Promise<{ id: string }>;
/** 开启 Zone 的 Email Routing(幂等) */
enableEmailRouting(opts: { accessToken: string; zoneId: string }): Promise<{ enabled: boolean }>;
@@ -136,12 +140,13 @@ export class RealCFApi implements CFApi {
accessToken: string;
accountId: string;
scriptName: string;
ingressUrl: string;
ingressSecret: string;
consoleUrl: string;
workerId: string;
workerSecret: string;
}) {
// Email Worker 即带 email handler 的 Worker 脚本;ingress 地址与密钥以明文常量打进脚本,
// 避免给 Worker 配置 Console 凭据(docs/08:Worker 只持有 Node ingress secret)。
const script = renderEmailWorkerScript(opts.ingressUrl, opts.ingressSecret);
// Email Worker 即带 email handler 的 Worker 脚本;Console 地址与 Worker 凭据以明文常量打进脚本,
// 避免给 Worker 配置额外的 secrets 绑定(docs/11 §8:凭据本身经 HMAC 保护 Console API)。
const script = renderEmailWorkerScript(opts.consoleUrl, opts.workerId, opts.workerSecret);
const form = new FormData();
form.append(
"metadata",
@@ -223,14 +228,16 @@ export class SimCFApi implements CFApi {
accessToken: string;
accountId: string;
scriptName: string;
ingressUrl: string;
ingressSecret: string;
consoleUrl: string;
workerId: string;
workerSecret: string;
}) {
// 与真实实现同一模板渲染,但只记录不外发
await this.record("email_worker", opts.scriptName, {
account_id: opts.accountId,
ingress_url: opts.ingressUrl,
script: renderEmailWorkerScript(opts.ingressUrl, opts.ingressSecret).length,
console_url: opts.consoleUrl,
worker_id: opts.workerId,
script: renderEmailWorkerScript(opts.consoleUrl, opts.workerId, opts.workerSecret).length,
});
return { id: opts.scriptName };
}