feat: WmailHub v1.0.0 —— Cloudflare Email Workers 邮件接收与转发平台(本地可用版本)

- Console:React 19 + antd 6.6.5 前端(登录 admin/admin)+ Hono 管理 API
  (Nodes/Domains/Mailboxes/Emails/Webhooks/Keys/OAuth/Admin)
- Node:Bun 转发节点(HMAC 签名、有限重试、心跳、结构化日志、优雅退出)
- Email Worker:Cloudflare email handler + 本地联调入口
- shared:HMAC 协议(docs/04)、wmh_ Key(32 位)、AES-GCM 加密、SSRF 防护
- 邮件管线:postal-mime 解析、附件、catch-all、限额、malformed 降级、TTL 1 天
- Webhook:签名投递、指数退避重试、SSRF 校验、投递日志
- API v1 + MCP(JSON-RPC,六工具,凭据 kind 隔离)
- Cloudflare OAuth:真实 + 本地 sim 模式,自动安装管线(幂等,无需手工命令)
- 测试:bun test 41 例(协议安全/管线/TTL/Webhook/SSRF/MCP/OAuth/鉴权)+ Playwright UI 冒烟
- LOGO/favicon 由 codex CLI 生成
This commit is contained in:
BigCode
2026-09-30 03:34:37 +08:00
commit d4c1334143
87 changed files with 9268 additions and 0 deletions
+243
View File
@@ -0,0 +1,243 @@
/**
* Cloudflare API 客户端抽象。
*
* - RealCFApi:真实调用 api.cloudflare.com / dash.cloudflare.com(OAuth access token)
* - SimCFApi :本地 sim 模式。不访问外网,把"创建"的资源记录到 cf_sim_resources 表,
* 让 Add Node 自动安装链路在本地完整可演示、可测试。
*
* Console 通过 env.CF_OAUTH_CLIENT_ID/SECRET 判断模式;两者齐备为 real,否则 sim。
*/
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 模式) **/
export const CF_OAUTH_AUTHORIZE_URL = "https://dash.cloudflare.com/oauth2/auth";
/** # 成员变量: OAuth token 端点(real 模式) **/
export const CF_OAUTH_TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
/** # 成员变量: Cloudflare API 基址(real 模式) **/
export const CF_API_BASE = "https://api.cloudflare.com/client/v4";
/** # 成员变量: OAuth 申请权限(docs/08:最小权限——账户与 Zone 读、Email Routing 读写、Workers 脚本写) **/
export const CF_OAUTH_SCOPES = [
"account:read",
"zone:read",
"email_routing:edit",
"workers_scripts:edit",
].join(" ");
/** # 成员变量: 邮件发现的 Cloudflare 资源描述 **/
export interface CFAccount {
id: string;
name: string;
}
export interface CFZone {
id: string;
name: string;
account_id: string;
status: string;
}
/** # 成员变量: Cloudflare API 客户端接口(自动安装所需操作) **/
export interface CFApi {
/** 用 code 换 access token(real);sim 直接签发假 token */
exchangeToken(code: string, redirectUri: string): Promise<{ accessToken: string; expiresIn: number }>;
/** 发现 token 可访问的账户 */
listAccounts(accessToken: string): Promise<CFAccount[]>;
/** 发现账户下的域名 Zone */
listZones(accessToken: string, accountId: string): Promise<CFZone[]>;
/** 创建或更新 Email Worker(幂等) */
putEmailWorker(opts: {
accessToken: string;
accountId: string;
scriptName: string;
ingressUrl: string;
ingressSecret: string;
}): Promise<{ id: string }>;
/** 开启 Zone 的 Email Routing(幂等) */
enableEmailRouting(opts: { accessToken: string; zoneId: string }): Promise<{ enabled: boolean }>;
/** 配置 Zone catch-all 规则指向 Email Worker(幂等) */
setCatchAllRule(opts: {
accessToken: string;
zoneId: string;
workerName: string;
}): Promise<{ ok: boolean }>;
}
/** # 函数
* 根据环境构造 Cloudflare API 客户端(real 或 sim)
*/
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);
}
/** # 成员变量: 真实 Cloudflare API 实现 **/
export class RealCFApi implements CFApi {
/** # 成员变量 **/
constructor(private env: Env) {}
async exchangeToken(code: string, redirectUri: string) {
const resp = await fetch(CF_OAUTH_TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
client_id: this.env.CF_OAUTH_CLIENT_ID ?? "",
client_secret: this.env.CF_OAUTH_CLIENT_SECRET ?? "",
redirect_uri: redirectUri,
}),
});
if (!resp.ok) throw new Error(`cloudflare token exchange failed: ${resp.status}`);
const data = (await resp.json()) as { access_token: string; expires_in?: number };
return { accessToken: data.access_token, expiresIn: data.expires_in ?? 3600 };
}
private async api<T>(token: string, path: string, init?: RequestInit): Promise<T> {
const resp = await fetch(`${CF_API_BASE}${path}`, {
...init,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
...(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}`);
return data.result;
}
listAccounts(token: string) {
return this.api<CFAccount[]>(token, "/accounts");
}
listZones(token: string, accountId: string) {
return this.api<CFZone[]>(token, `/zones?account.id=${encodeURIComponent(accountId)}&per_page=50`);
}
async putEmailWorker(opts: {
accessToken: string;
accountId: string;
scriptName: string;
ingressUrl: string;
ingressSecret: string;
}) {
// Email Worker 即带 email handler 的 Worker 脚本;ingress 地址与密钥以明文常量打进脚本,
// 避免给 Worker 配置 Console 凭据(docs/08:Worker 只持有 Node ingress secret)。
const script = renderEmailWorkerScript(opts.ingressUrl, opts.ingressSecret);
const form = new FormData();
form.append(
"metadata",
new Blob([JSON.stringify({ main_module: "worker.js", bindings: [] })], { type: "application/json" }),
);
form.append("worker.js", new Blob([script], { type: "application/javascript+module" }));
const resp = await fetch(
`${CF_API_BASE}/accounts/${opts.accountId}/workers/scripts/${opts.scriptName}`,
{
method: "PUT",
headers: { Authorization: `Bearer ${opts.accessToken}` },
body: form,
},
);
if (!resp.ok) throw new Error(`upload email worker failed: ${resp.status}`);
return { id: opts.scriptName };
}
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 };
}
async setCatchAllRule(opts: { accessToken: string; zoneId: string; workerName: string }) {
await this.api(opts.accessToken, `/zones/${opts.zoneId}/email/routing/rules/catch_all`, {
method: "PUT",
body: JSON.stringify({
matchers: [{ type: "all" }],
actions: [{ type: "worker", value: [opts.workerName] }],
enabled: true,
name: "wmailhub catch-all",
}),
});
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[]> {
const zones = [
{
id: "sim-zone-example.test",
name: "example.test",
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;
ingressUrl: string;
ingressSecret: string;
}) {
// 与真实实现同一模板渲染,但只记录不外发
await this.record("email_worker", opts.scriptName, {
account_id: opts.accountId,
ingress_url: opts.ingressUrl,
script: renderEmailWorkerScript(opts.ingressUrl, opts.ingressSecret).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 };
}
}