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:
+20
-20
@@ -88,8 +88,8 @@ export const health = () => api<{ ok: boolean; name: string; version: string }>(
|
||||
/** Dashboard 统计 */
|
||||
export const stats = () =>
|
||||
api<{
|
||||
accounts_online: number;
|
||||
accounts_total: number;
|
||||
accounts_active: number;
|
||||
domains: number;
|
||||
domains_active: number;
|
||||
mailboxes: number;
|
||||
@@ -98,59 +98,61 @@ export const stats = () =>
|
||||
global_webhook: number;
|
||||
deliveries_pending: number;
|
||||
oauth_connections: number;
|
||||
worker_credentials: number;
|
||||
recent_emails: { id: string; subject: string; from_address: string; received_at: string; size: number }[];
|
||||
}>("/api/admin/stats");
|
||||
|
||||
/** 手动触发 Cron */
|
||||
export const runCron = () => api<{ ok: boolean; stats: Record<string, number | string[]> }>("/api/admin/cron/run", "POST");
|
||||
|
||||
/* ---- 账号(原节点) ---- */
|
||||
/* ---- 账号(Cloudflare 账户) ---- */
|
||||
|
||||
export interface AccountRow {
|
||||
id: string;
|
||||
name: string;
|
||||
cloudflare_account_id: string | null;
|
||||
worker_id: string | null;
|
||||
status: string;
|
||||
version: string | null;
|
||||
last_seen_at: string | null;
|
||||
ingress_url: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const listAccounts = () => api<{ items: AccountRow[]; total: number }>("/api/accounts");
|
||||
|
||||
export interface AccountDetail {
|
||||
account: AccountRow & { enrollment_expires_at: string | null };
|
||||
account: AccountRow;
|
||||
steps: Record<string, { status: string; error?: string }>;
|
||||
oauth: { id: string; account_id: string | null; created_at: string } | null;
|
||||
domains: { id: string; name: string; status: string; worker_id: string | null }[];
|
||||
credential: { id: string; worker_id: string; secret_last4: string; status: string; created_at: string } | null;
|
||||
}
|
||||
|
||||
export const getAccount = (id: string) => api<AccountDetail>(`/api/accounts/${id}`);
|
||||
|
||||
export const updateAccount = (id: string, body: { name?: string; ingress_url?: string }) =>
|
||||
export const updateAccount = (id: string, body: { name?: string }) =>
|
||||
api(`/api/accounts/${id}`, "PATCH", body);
|
||||
|
||||
export const deleteAccount = (id: string) => api(`/api/accounts/${id}`, "DELETE");
|
||||
|
||||
/** 轮换 Worker credential:secret 仅本次返回可见;已配置域名自动重新部署 */
|
||||
export const rotateAccount = (id: string) =>
|
||||
api<{ ok: boolean; enrollment_token: string }>(`/api/accounts/${id}/rotate`, "POST");
|
||||
api<{ ok: boolean; worker_id: string; credential_id: string; secret: string; redeployed: string[] }>(
|
||||
`/api/accounts/${id}/rotate`,
|
||||
"POST",
|
||||
);
|
||||
|
||||
export const getAccountIngressSecret = (id: string) =>
|
||||
api<{ ingress_secret: string }>(`/api/accounts/${id}/ingress-secret`);
|
||||
/** 本地/运维:查看账号当前 Worker credential(secret 用于部署/联调) */
|
||||
export const getAccountWorkerSecret = (id: string) =>
|
||||
api<{ worker_id: string; secret: string }>(`/api/accounts/${id}/worker-secret`);
|
||||
|
||||
/* ---- OAuth ---- */
|
||||
|
||||
export const oauthMode = () => api<{ mode: "cloudflare" | "sim" }>("/api/oauth/mode");
|
||||
|
||||
/** 开始授权:nodeId 缺省 = 自动创建账号 */
|
||||
export const oauthStart = (nodeId?: string) =>
|
||||
api<{ url: string; state: string }>(
|
||||
`/api/oauth/cloudflare/start${nodeId ? `?node_id=${encodeURIComponent(nodeId)}` : ""}`,
|
||||
);
|
||||
/** 开始授权(自动创建账号记录;授权后自动接入) */
|
||||
export const oauthStart = () => api<{ url: string; state: string }>("/api/oauth/cloudflare/start");
|
||||
|
||||
export const oauthSimApprove = (state: string) =>
|
||||
api<{ ok: boolean; error?: string; node_id: string }>("/api/oauth/cloudflare/sim/approve", "POST", { state });
|
||||
api<{ ok: boolean; error?: string; account_id: string }>("/api/oauth/cloudflare/sim/approve", "POST", { state });
|
||||
|
||||
/* ---- 域名 ---- */
|
||||
|
||||
@@ -161,8 +163,8 @@ export interface DomainRow {
|
||||
setup_error: string | null;
|
||||
worker_id: string | null;
|
||||
zone_id: string | null;
|
||||
node_id: string | null;
|
||||
node_name: string | null;
|
||||
account_id: string | null;
|
||||
account_name: string | null;
|
||||
webhook_url: string | null;
|
||||
webhook_headers: string | null;
|
||||
webhook_has_key: number;
|
||||
@@ -257,7 +259,6 @@ export const deleteEmail = (id: string) => api(`/api/emails/${id}`, "DELETE");
|
||||
|
||||
export interface SettingsInfo {
|
||||
username: string;
|
||||
default_ingress_url: string;
|
||||
webhook: { url: string; headers: Record<string, string>; has_key: boolean; key_last4: string };
|
||||
}
|
||||
|
||||
@@ -265,7 +266,6 @@ export const getSettings = () => api<SettingsInfo>("/api/settings");
|
||||
|
||||
/** 保存设置;webhook.key:undefined 保留原值、空串清除、非空设置新 key */
|
||||
export const putSettings = (body: {
|
||||
default_ingress_url?: string;
|
||||
webhook?: { url: string; headers?: Record<string, string>; key?: string };
|
||||
}) => api("/api/settings", "PUT", body);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user