v2.9.0: 设置新增 ZITADEL 统一登录(OIDC Authorization Code+PKCE,身份主键 (issuer,subject),JIT 建号即管理员,docs/12);账号密码 tab 支持修改用户名(验密+重签会话);新增 docs/API.md 全量 API 文档;迁移 0006(oidc_identities/oidc_states)
This commit is contained in:
+275
@@ -0,0 +1,275 @@
|
||||
# WmailHub API 文档
|
||||
|
||||
适用版本:**v2.9.0**(版本号以 `/api/health` 返回为准)。Base URL:生产 `https://wmailhub.adx9.com`,本地开发 `http://127.0.0.1:18787`。
|
||||
|
||||
## 目录
|
||||
|
||||
- [认证方式](#认证方式)
|
||||
- [健康检查与探测](#健康检查与探测)
|
||||
- [登录会话 API(/api/auth)](#登录会话-apiapiauth)
|
||||
- [ZITADEL 统一登录(/auth)](#zitadel-统一登录auth)
|
||||
- [Console Web API(/api/*,Cookie 会话)](#console-web-apiapi-cookie-会话)
|
||||
- [开放 API(/api/v1/*,Bearer Key)](#开放-apiapiv1bearer-key)
|
||||
- [MCP(/api/v1/mcp)](#mcpapiv1mcp)
|
||||
- [Webhook(收件推送)](#webhook收件推送)
|
||||
- [内部 API(/internal,Email Worker 专用)](#内部-apiinternalemail-worker-专用)
|
||||
- [错误码约定](#错误码约定)
|
||||
|
||||
---
|
||||
|
||||
## 认证方式
|
||||
|
||||
三套通道并存,按端点区分:
|
||||
|
||||
| 通道 | 适用端点 | 凭据 |
|
||||
|------|----------|------|
|
||||
| **Cookie 会话** | `/api/auth/*`、`/api/*`(Console Web)、`/api/admin/*`、`/api/settings/*`、`/api/oauth/*` | 登录后 `wmh_session` Cookie(HttpOnly,7 天) |
|
||||
| **Bearer Key** | `/api/v1/*`(含 `/api/v1/mcp`) | `Authorization: Bearer wmh_…`(32 位;API 与 MCP 共用同一 Key) |
|
||||
| **HMAC 签名** | `/internal/*` | Email Worker 专用,见 docs/04 / docs/11 |
|
||||
|
||||
- Key 在 **设置 → 全局 Webhook → API / MCP Key** 生成/轮换/撤销;明文仅生成时返回一次,服务端只存 SHA-256;新 Key 生成后旧 Key 立即失效。
|
||||
- Cookie 会话的写请求有 CSRF 防护:带 Cookie 时 `Origin`/`Referer` 必须与站点 Host 一致,否则 403。Bearer Key 调用不受影响。
|
||||
|
||||
## 健康检查与探测
|
||||
|
||||
### GET /api/health
|
||||
|
||||
公开。返回 `{ ok: true, name: "WmailHub", version: "2.9.0" }`。
|
||||
|
||||
### GET /auth/status
|
||||
|
||||
公开。统一登录探测:返回 `{ enabled: boolean, issuer: string | null }`,登录页据此显示「ZITADEL 统一登录」按钮。
|
||||
|
||||
## 登录会话 API(/api/auth)
|
||||
|
||||
### POST /api/auth/login
|
||||
|
||||
```json
|
||||
{ "username": "admin", "password": "admin" }
|
||||
```
|
||||
|
||||
成功 `200 { ok: true, user: { id, username } }` 并下发会话 Cookie;失败 `401 {"error":"invalid username or password"}`。默认账号 `admin / admin`(首次启动自动播种)。
|
||||
|
||||
### POST /api/auth/logout
|
||||
|
||||
清除会话 Cookie,返回 `{ ok: true }`。(ZITADEL 登录的用户同样只清本地会话,IdP 侧 SSO 会话保留。)
|
||||
|
||||
### GET /api/auth/me
|
||||
|
||||
返回当前用户 `{ user: { id, username }, now }`;未登录 `401`。用户名以库内实时数据为准。
|
||||
|
||||
## ZITADEL 统一登录(/auth)
|
||||
|
||||
OIDC Authorization Code + PKCE(无 client secret),与密码登录双通道并存;**登录成功即管理员**(本平台没有权限设置)。配置入口:设置 → ZITADEL 统一登录(issuer + client_id;回调地址 `{origin}/auth/callback` 需登记到 ZITADEL 应用)。详细设计见 docs/12。
|
||||
|
||||
| 端点 | 说明 |
|
||||
|------|------|
|
||||
| `GET /auth/login` | 发起授权,302 到 IdP;可选 `?return=/<站内路径>` 指定回跳(默认 `/`)。未启用 302 → `/login?error=oidc_disabled` |
|
||||
| `GET /auth/callback` | IdP 回调(浏览器重定向,无需调用):一次性 state 校验 → 换码 → userinfo → 建号/匹配 → 下发会话 Cookie → 302 回站内;失败 302 → `/login?error=<code>` |
|
||||
|
||||
错误码:`oidc_disabled` / `oidc_unavailable` / `state_invalid` / `oidc_exchange_failed` / `oidc_not_linked`(JIT 关闭且身份未绑定)。
|
||||
|
||||
## Console Web API(/api/*,Cookie 会话)
|
||||
|
||||
以下端点未登录一律 `401`。分页参数统一 `limit`(默认 20,最大 100)与 `offset`,列表返回 `{ items, total }`。
|
||||
|
||||
### Cloudflare 账号 /api/accounts
|
||||
|
||||
| 方法与路径 | 说明 |
|
||||
|------------|------|
|
||||
| `GET /api/accounts` | 账号列表 |
|
||||
| `GET /api/accounts/:id` | 账号详情(接入步骤、OAuth 连接、域名、Worker 凭据) |
|
||||
| `PATCH /api/accounts/:id` | 改名 `{ name }` |
|
||||
| `DELETE /api/accounts/:id` | 删除账号(级联清理 Cloudflare 侧 Email Routing/Worker,尽力而为) |
|
||||
| `POST /api/accounts/batch-delete` | 批量删除 `{ ids: string[] }` → `{ ok, deleted, failed[] }` |
|
||||
| `POST /api/accounts/token` | API Token 接入 `{ name, token }`(后端先探 Cloudflare 再接入)→ `{ ok, account_id }` |
|
||||
| `POST /api/accounts/:id/rotate` | 轮换 Worker 凭据;secret 仅本次返回,已配域名自动重部署 |
|
||||
| `GET /api/accounts/:id/worker-secret` | 本地/运维:读取当前 Worker credential |
|
||||
|
||||
### 域名 /api/domains
|
||||
|
||||
| 方法与路径 | 说明 |
|
||||
|------------|------|
|
||||
| `GET /api/domains` | 域名列表(含各域名 Webhook 配置与邮箱计数) |
|
||||
| `GET /api/domains/filters/accounts` | 账号筛选项 |
|
||||
| `POST /api/domains/:id/setup` | 配置 Email Workers(部署 Worker + 开 Routing + catch-all)→ `{ ok, error? }` |
|
||||
| `PUT /api/domains/:id/webhook` | 域名级 Webhook `{ url, headers?, key? }`(url 空串清除;key undefined 保留、空格清除) |
|
||||
| `DELETE /api/domains/:id/webhook` | 清除域名级 Webhook |
|
||||
| `DELETE /api/domains/:id` / `POST /api/domains/batch-delete` | 删除(级联邮箱与邮件) |
|
||||
|
||||
### 邮箱 /api/mailboxes
|
||||
|
||||
| 方法与路径 | 说明 |
|
||||
|------------|------|
|
||||
| `GET /api/mailboxes?domain_id=` | 邮箱列表 |
|
||||
| `POST /api/mailboxes` | 创建 `{ domain_id, address, retention_seconds?, catch_all? }` → `{ ok, id, address }`;重名 409 |
|
||||
| `PATCH /api/mailboxes/:id` | `{ status?: "active"\|"disabled", retention_seconds?: number\|null }`(null=回到全局默认 86400 秒) |
|
||||
| `PUT /api/mailboxes/:id/webhook` | 邮箱级 Webhook,语义同域名级 |
|
||||
| `DELETE /api/mailboxes/:id/webhook` | 清除邮箱级 Webhook |
|
||||
| `DELETE /api/mailboxes/:id` / `POST /api/mailboxes/batch-delete` | 删除 |
|
||||
|
||||
Webhook 生效顺序:**邮箱 → 域名 → 全局**,取第一个已配置的。
|
||||
|
||||
### 邮件 /api/emails
|
||||
|
||||
| 方法与路径 | 说明 |
|
||||
|------------|------|
|
||||
| `GET /api/emails?domain_id=&mailbox_id=&q=&limit=&offset=` | 列表(仅未过期) |
|
||||
| `GET /api/emails/:id` | 详情(正文 text/html/markdown + 附件列表);过期 `410` |
|
||||
| `GET /api/emails/:id/attachments/:attId` | 下载附件 |
|
||||
| `GET /api/emails/:id/raw` | 原始 EML |
|
||||
| `DELETE /api/emails/:id` / `POST /api/emails/batch-delete` | 删除 |
|
||||
| `POST /api/emails/retain` | 保留 `{ ids }`(永不过期,Cron 跳过)→ `{ ok, retained }` |
|
||||
|
||||
### 操作日志 /api/logs
|
||||
|
||||
`GET /api/logs?target_type=&action=&q=&limit=&offset=`。`target_type` ∈ `account/domain/mailbox/email/webhook/user/settings`;库里最多保留 10000 条。
|
||||
|
||||
### 设置 /api/settings
|
||||
|
||||
| 方法与路径 | 说明 |
|
||||
|------------|------|
|
||||
| `GET /api/settings` | 全量读取:当前用户名、全局 Webhook(key 只回 last4)、API Key 元信息、Cloudflare OAuth、ZITADEL 统一登录(含回调地址与当前账号绑定身份) |
|
||||
| `PUT /api/settings` | 保存 `{ webhook?, cf_oauth?, oidc? }`;`webhook.key`/`cf_oauth.secret`:undefined 保留、空串清除、非空设置;`oidc: { issuer, client_id, enabled?, jit? }`(开关缺省沿用已存值) |
|
||||
| `PUT /api/settings/password` | 改密码 `{ old_password, new_password }`(≥6 位) |
|
||||
| `PUT /api/settings/username` | 改用户名 `{ new_username, password }`(需当前密码;2-32 位字母/数字/`_ . -`;成功重签会话 Cookie;重名 409) |
|
||||
| `POST /api/settings/api-key` | 生成(轮换)API/MCP Key → `201 { key, created_at }`(明文仅此一次) |
|
||||
| `DELETE /api/settings/api-key` | 撤销并删除 Key |
|
||||
| `DELETE /api/settings/cf-oauth` | 清除 Cloudflare OAuth 凭据(env 兜底仍可用) |
|
||||
| `DELETE /api/settings/oidc` | 清除统一登录配置(已建立的绑定保留) |
|
||||
|
||||
### Cloudflare OAuth /api/oauth
|
||||
|
||||
| 方法与路径 | 说明 |
|
||||
|------------|------|
|
||||
| `GET /api/oauth/mode` | `{ mode: "cloudflare"|"unconfigured", source, scope }` |
|
||||
| `GET /api/oauth/cloudflare/start` | 生成一次性 state,返回 `{ url, state }`(浏览器跳 url 完成授权) |
|
||||
| `GET /api/oauth/cloudflare/callback` | 授权回调(浏览器重定向,无需调用) |
|
||||
| `GET /api/oauth/cloudflare/preflight` | 授权配置自检(探测 Cloudflare 授权端点 + scope 探测) |
|
||||
| `GET /api/oauth/connections` | 已建立的 OAuth 连接(不含 token) |
|
||||
|
||||
### 管理 /api/admin
|
||||
|
||||
`GET /api/admin/version`;`GET /api/admin/stats`(Dashboard 统计 + 最近 8 封邮件);`POST /api/admin/cron/run`(手动触发 TTL 清理与 Webhook 投递)。
|
||||
|
||||
## 开放 API(/api/v1/*,Bearer Key)
|
||||
|
||||
认证:`Authorization: Bearer wmh_…`;无 Key/Key 无效 `401`。所有邮件读取强制只返回**未过期**邮件。
|
||||
|
||||
### GET /api/v1/accounts
|
||||
|
||||
`{ items: [{ id, name, cloudflare_account_id, worker_id, status, created_at }] }`
|
||||
|
||||
### GET /api/v1/domains
|
||||
|
||||
`{ items: [{ id, name, status, account_id, created_at }] }`
|
||||
|
||||
### GET /api/v1/mailboxes
|
||||
|
||||
`{ items: [{ id, address, status, is_catch_all, retention_seconds, domain_id, domain_name }] }`
|
||||
|
||||
### POST /api/v1/mailboxes
|
||||
|
||||
```json
|
||||
{ "domain_id": "dom_xxx", "address": "hello", "retention_seconds": 86400, "webhook": "https://hooks.example.com/wmail" }
|
||||
```
|
||||
|
||||
- `domain_id` **可省略(v2.8.0 起)**:随机分配一个已启用 Email Workers 的域名;此时 `address` 必须为本地部分(不带 `@`)。
|
||||
- `webhook` **可选(v2.8.0 起)**:创建时直接配置邮箱级 Webhook(先校验再建邮箱,校验失败不留半截数据;SSRF 规则同 Console——拒绝私网/localhost/元数据地址)。
|
||||
- 返回 `201 { ok: true, id, address, webhook? }`;错误:`400 address required` / `404 domain not found` / `409 mailbox already exists` / `409 no available domain (none has Email Workers enabled)`。
|
||||
|
||||
### DELETE /api/v1/mailboxes/:id
|
||||
|
||||
成功 `{ ok: true }`;不存在 `404`。
|
||||
|
||||
### GET /api/v1/emails?mailbox_id=&limit=&offset=
|
||||
|
||||
`{ items: [{ id, mailbox_id, mailbox_address, message_id, from_address, to_addresses, subject, size, received_at, expires_at }] }`(仅未过期)。
|
||||
|
||||
### GET /api/v1/emails/:id
|
||||
|
||||
`{ email: { …全字段含 text_body/html_body/markdown_body }, attachments: [{ id, filename, content_type, size, content_id }] }`;不存在 `404`,已过期 `410 {"error":"email expired"}`。
|
||||
|
||||
#### curl 示例
|
||||
|
||||
```bash
|
||||
KEY="wmh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
BASE="https://wmailhub.adx9.com"
|
||||
|
||||
# 随机域名建邮箱 + 邮箱级 Webhook
|
||||
curl -s -X POST "$BASE/api/v1/mailboxes" \
|
||||
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
|
||||
-d '{"address":"hello","webhook":"https://hooks.example.com/wmail"}'
|
||||
|
||||
# 拉最近邮件(取第一条详情)
|
||||
curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/emails?limit=1"
|
||||
```
|
||||
|
||||
## MCP(/api/v1/mcp)
|
||||
|
||||
Streamable HTTP(JSON-RPC 2.0,协议版本 `2025-06-18`),**仅 POST**(GET → 405),Bearer Key 与 API 共用。方法:`initialize` → `notifications/initialized` → `tools/list` / `tools/call`;`ping` 可用。
|
||||
|
||||
MCP 客户端配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"wmailhub": {
|
||||
"type": "http",
|
||||
"url": "https://wmailhub.adx9.com/api/v1/mcp",
|
||||
"headers": { "Authorization": "Bearer wmh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 工具清单(v2.9.0 共 6 个)
|
||||
|
||||
| 工具 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `list_domains` | — | 列出可用域名(创建邮箱的 domain_id 由此获取) |
|
||||
| `list_mailboxes` | — | 列出邮箱(含域名与保留策略) |
|
||||
| `list_emails` | `mailbox_id?`, `limit?` | 列出未过期邮件(按收到时间倒序) |
|
||||
| `get_email` | `email_id` | 邮件详情(正文 + 附件);过期返回资源错误 -32002 |
|
||||
| `create_mailbox` | `address`, `domain_id?`, `retention_seconds?`, `webhook?` | 创建邮箱;`domain_id` 省略时随机分配(此时 address 须为本地部分);`webhook` 为可选邮箱级 Webhook URL |
|
||||
| `delete_mailbox` | `mailbox_id` | 删除邮箱 |
|
||||
|
||||
错误约定:`-32601` 未知方法/工具、`-32602` 参数错误、`-32002` 资源不存在(如邮件过期)。
|
||||
|
||||
## Webhook(收件推送)
|
||||
|
||||
收到邮件时按 邮箱 → 域名 → 全局 取第一个已配置的 Webhook 入队投递。Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "email.received",
|
||||
"id": "msg_xxx",
|
||||
"mailbox": "hello@example.com",
|
||||
"from": "user@example.org",
|
||||
"to": ["hello@example.com"],
|
||||
"subject": "Hello",
|
||||
"text": "Hello World",
|
||||
"markdown": "Hello **World**",
|
||||
"received_at": "2026-09-30T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
请求头:`X-WmailHub-Event`、`X-WmailHub-Delivery`、`X-WmailHub-Timestamp`、`X-WmailHub-Signature`(签名 = `HMAC-SHA256(key, "<timestamp>.<body>")`,配了 key 才有);配置 key 时默认携带 `Authorization: Bearer <key>`,自定义头优先。`2xx` 为成功;5xx/超时按指数退避重试,耗尽后置 `exhausted`;Cron 周期性重投 `pending/failed`。
|
||||
|
||||
## 内部 API(/internal,Email Worker 专用)
|
||||
|
||||
`POST /internal/email/authorize` 与 `POST /internal/email/ingest`:Email Worker 取收件凭据与投递邮件,HMAC 签名认证,协议见 docs/04 与 docs/11,不对外暴露。
|
||||
|
||||
## 错误码约定
|
||||
|
||||
| 状态码 | 场景 |
|
||||
|--------|------|
|
||||
| 400 | 参数缺失/非法(webhook URL 不合法、用户名格式错误等) |
|
||||
| 401 | 未登录 / Bearer Key 缺失或无效 |
|
||||
| 403 | CSRF 校验失败(带 Cookie 的跨源写请求) |
|
||||
| 404 | 资源不存在 |
|
||||
| 409 | 冲突(邮箱已存在、无可用域名、用户名已存在) |
|
||||
| 410 | 邮件已过期(Gone) |
|
||||
| 503 | 依赖未配置(如 Cloudflare OAuth 未配置时添加账号) |
|
||||
|
||||
错误响应统一为 `{"error": "<中文或英文消息>"}`。
|
||||
Reference in New Issue
Block a user