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:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* WmailHub Console API / MCP Key 生成与校验
|
||||
*
|
||||
* 规则:Key 以 `wmh_` 开头,后接随机字符串,总长度 32 位
|
||||
* (即 `wmh_` + 28 位随机 [A-Za-z0-9])。
|
||||
*/
|
||||
|
||||
/** # 成员变量: Key 前缀 **/
|
||||
export const KEY_PREFIX = "wmh_";
|
||||
|
||||
/** # 成员变量: Key 总长度(含前缀) **/
|
||||
export const KEY_TOTAL_LENGTH = 32;
|
||||
|
||||
/** # 成员变量: Key 随机部分字母表 **/
|
||||
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
/** # 函数
|
||||
* 生成随机 token(拒绝采样避免模偏差)
|
||||
* 参数: length - 随机串长度
|
||||
* 返回值: 随机字符串
|
||||
*/
|
||||
export function randomToken(length: number): string {
|
||||
let out = "";
|
||||
while (out.length < length) {
|
||||
const bytes = crypto.getRandomValues(new Uint8Array(length * 2));
|
||||
for (const b of bytes) {
|
||||
if (b >= 248) continue; // 248 = 62 * 4,超出可整除区间则丢弃重采样
|
||||
out += ALPHABET[b % ALPHABET.length];
|
||||
if (out.length === length) break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** # 函数
|
||||
* 生成 Console API / MCP Key(wmh_ 前缀,总长 32)
|
||||
* 返回值: 形如 wmh_XXXXXXXXXXXXXXXXXXXXXXXXXXXX 的明文 Key(仅在创建时展示一次)
|
||||
*/
|
||||
export function generateWmailKey(): string {
|
||||
return KEY_PREFIX + randomToken(KEY_TOTAL_LENGTH - KEY_PREFIX.length);
|
||||
}
|
||||
|
||||
/** # 函数
|
||||
* 校验 Key 格式是否符合 wmh_ + 28 位随机串、总长 32 的规范
|
||||
*/
|
||||
export function isValidWmailKey(key: string): boolean {
|
||||
if (typeof key !== "string") return false;
|
||||
if (key.length !== KEY_TOTAL_LENGTH) return false;
|
||||
if (!key.startsWith(KEY_PREFIX)) return false;
|
||||
for (const ch of key.slice(KEY_PREFIX.length)) {
|
||||
if (!ALPHABET.includes(ch)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user