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:
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
From: Alice Sender <alice@example.org>
|
||||
To: hello@example.test
|
||||
Cc: bob@example.net
|
||||
Subject: WmailHub 端到端测试邮件
|
||||
Message-ID: <test-001@example.org>
|
||||
Date: Wed, 30 Sep 2026 03:20:00 +0000
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/mixed; boundary="BOUNDARY-XYZ"
|
||||
|
||||
--BOUNDARY-XYZ
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Hello WmailHub! This is a plain text body.
|
||||
--BOUNDARY-XYZ
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<html><body><h1>Hello WmailHub</h1><p>This is <b>HTML</b> body.</p></body></html>
|
||||
--BOUNDARY-XYZ
|
||||
Content-Type: text/plain; charset=utf-8; name="notes.txt"
|
||||
Content-Disposition: attachment; filename="notes.txt"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
VGhpcyBpcyBhbiBhdHRhY2htZW50IGZpbGUu
|
||||
--BOUNDARY-XYZ--
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 本地 Webhook 接收器(联调用):
|
||||
* bun tests/fixtures/webhook-receiver.ts [port]
|
||||
* 验证 X-WmailHub-Signature = HMAC-SHA256(secret, `${timestamp}.${body}`),打印收到的 payload。
|
||||
*/
|
||||
|
||||
const port = Number(process.argv[2] ?? 18790);
|
||||
/** # 成员变量: 与创建 webhook 时一致的 secret(由环境变量传入) **/
|
||||
const SECRET = process.env.WEBHOOK_SECRET ?? "";
|
||||
|
||||
Bun.serve({
|
||||
port,
|
||||
async fetch(req) {
|
||||
if (req.method !== "POST") return new Response("ok");
|
||||
const body = await req.text();
|
||||
const ts = req.headers.get("X-WmailHub-Timestamp") ?? "";
|
||||
const sig = req.headers.get("X-WmailHub-Signature") ?? "";
|
||||
const delivery = req.headers.get("X-WmailHub-Delivery") ?? "";
|
||||
const key = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
new TextEncoder().encode(SECRET),
|
||||
{ name: "HMAC", hash: "SHA-256" },
|
||||
false,
|
||||
["sign"],
|
||||
);
|
||||
const expected = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${ts}.${body}`));
|
||||
const expectedHex = [...new Uint8Array(expected)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||
const valid = expectedHex === sig;
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
t: new Date().toISOString(),
|
||||
msg: "webhook received",
|
||||
delivery,
|
||||
signature_valid: valid,
|
||||
payload: JSON.parse(body),
|
||||
}),
|
||||
);
|
||||
return Response.json({ received: true });
|
||||
},
|
||||
});
|
||||
console.log(`webhook receiver on :${port}`);
|
||||
Reference in New Issue
Block a user