feat: 为本地 HTTP API 增加 Token 鉴权与安全加固

- 使用 Electron safeStorage 加密存储并自动初始化 API Token
- 为 health 以外的接口增加 Bearer Token 鉴权
- 限制 CORS 仅允许可信本地 Origin
- 增加鉴权、Token rotation、safeStorage 和手动验收测试
This commit is contained in:
Wxw-Gu
2026-08-07 17:48:05 +08:00
parent 0c21008ec3
commit a73af3b5ad
33 changed files with 1328 additions and 130 deletions
+12
View File
@@ -0,0 +1,12 @@
# Local API Security
WechatExplorer 的安全模型是:本机回环地址 + 高熵 Bearer Token。
- Token 使用密码学安全随机源生成,并由 Electron safeStorage 加密保存。
- 应用升级或首次启动时自动、幂等生成;应用重启后保持不变。
- API Center 可以显示、复制或重新生成 Token。重新生成后旧 Token 立即失效。
- `/api/v1/health` 公开且不返回聊天内容、数据库路径、Token 或 Provider 信息。
- 其他 endpoint 缺少或使用错误 Token 时返回 `401 Unauthorized`
- CORS 仅允许精确的 localhost、127.0.0.1 和 ::1 HTTP Origin;无 Origin 的 curl、Node 和本地 Agent 请求正常工作。
本地 API 不应暴露到公网或不受信任网络。Bearer Token 提供本机 API 访问保护,但不是公网网关、用户账户系统或完整权限 Scope 系统。
+16
View File
@@ -0,0 +1,16 @@
# WechatExplorer Local HTTP API
WechatExplorer v2.1.9 默认在 `127.0.0.1:6131` 提供 Local HTTP API。
- `GET /api/v1/health` 无需鉴权。
- 其他数据和 Agent endpoint 需要 `Authorization: Bearer <TOKEN>`
- Token 从 WechatExplorer → API Center → API Token 获取。
- Token 不得放入 URL、仓库或共享配置。
```bash
export WECHATEXPLORER_API_TOKEN="<YOUR_API_TOKEN>"
curl -H "Authorization: Bearer $WECHATEXPLORER_API_TOKEN" \
http://127.0.0.1:6131/api/v1/recent_chat
```
完整 endpoint 与使用流程见 [Reader Skill](../skill/wechatexplorer-reader/SKILL.md),安全边界见 [API Security](./api-security.md)。
+11
View File
@@ -0,0 +1,11 @@
# Reader Skill Authentication
WechatExplorer Reader 是 Local HTTP API Skill,不是 MCP Server。
1. 打开 WechatExplorer → API Center。
2. 确认 API 和数据库已就绪。
3. 在 API Token 区域复制 Token。
4. 将它保存到 Agent 自己的本地环境配置:`WECHATEXPLORER_API_TOKEN=<YOUR_API_TOKEN>`
5. 安装 Reader Skill,并让所有数据请求携带 `Authorization: Bearer $WECHATEXPLORER_API_TOKEN`
Codex、Claude Code、OpenClaw 和其他 Agent 均使用相同的 HTTP Bearer Token 模型。WechatExplorer 不会自动把 Token 写入任何 Agent 配置。
+11
View File
@@ -0,0 +1,11 @@
# WechatExplorer v2.1.9 API Authentication
v2.1.9 为 Local HTTP API 增加 Bearer Token 鉴权。这是有意的 breaking change。
- v2.1.8`GET /api/v1/contact` 可能直接返回数据。
- v2.1.9:相同请求必须携带 `Authorization: Bearer <TOKEN>`,否则返回 `401`
- `GET /api/v1/health` 保持公开。
- 老用户升级后会自动生成并安全保存 Token,不改变原有 apiEnabled、host 或 port 设置。
- Token 可在 WechatExplorer → API Center 中显示、复制和重新生成。
Reader Skill 和本地 Agent 需要使用 `WECHATEXPLORER_API_TOKEN` 更新本机配置。
+84 -67
View File
@@ -11,7 +11,8 @@ description: 通过本地 HTTP API 读取 WechatExplorer 解锁后的微信聊
- **本服务由 WechatExplorer.app 提供**,数据完全在本地处理,不会上传任何服务器
- 用户必须在 WechatExplorer 主窗口完成**首次密钥配置**(解锁 WCDB 数据库)
- 默认监听 `127.0.0.1:6131`,仅本机可访问,无需鉴权
- 默认监听 `127.0.0.1:6131`,仅本机可访问
- 除 health 外的 API 均要求 Bearer Token。Token 获取路径:WeChatExplorer → API Center → API Token → 显示/复制 Token
## 前置条件
@@ -19,21 +20,48 @@ description: 通过本地 HTTP API 读取 WechatExplorer 解锁后的微信聊
2. **首次启动时完成密钥配置**:在主界面第一步输入微信数据库密钥(64 位 hex),完成 WCDB 初始化
3. **如需 7×24 提供 API**:用 `WXE_TRAY=1``--tray` 参数启动 app,启用菜单栏常驻模式(主窗口关闭后服务仍在)
## Authentication
WechatExplorer Reader 使用的是 **WechatExplorer Local HTTP API**,不是 MCP Server。
1. 在 WechatExplorer 中打开 **API Center**
2.**API Token** 区域点击“复制 Token”。
3. 把 Token 保存到 Agent 自己的本地环境配置中:
```bash
export WECHATEXPLORER_API_TOKEN="<YOUR_API_TOKEN>"
```
health 可以不带 Token:
```bash
curl http://127.0.0.1:6131/api/v1/health
```
除 health 外,所有请求必须使用标准 Bearer header:
```bash
curl -H "Authorization: Bearer $WECHATEXPLORER_API_TOKEN" \
http://127.0.0.1:6131/api/v1/recent_chat
```
本文件后续写出的所有 `GET` / `POST` 数据请求都默认包含上述 Authorization header。禁止把 Token 放入 URL query、路径、Skill 文件或仓库。
## API 列表
GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图)。所有端点返回 JSON。
| 端点 | 用途 | 关键参数 |
|------|------|---------|
| `GET /api/v1/health` | 健康检查 + 是否已初始化 | — |
| `GET /api/v1/current_time` | 获取当前本地时间(用于"今天/昨天"换算) | — |
| `GET /api/v1/contact` | 联系人 / 群聊列表 | `filter`(昵称模糊)、`type`(`user` \| `group`) |
| `GET /api/v1/chatroom` | 群聊列表(等同 contact?type=group) | `keyword` |
| `GET /api/v1/recent_chat` | 最近会话 | `limit`(默认 50) |
| `GET /api/v1/chatlog` | 聊天记录 | `talker``time``startTime`/`endTime` |
| `GET /api/v1/group_snapshot` | 群成员快照 | `md5` |
| `GET /api/v1/resolve` | 把昵称/wxid/md5 解析成 md5 | `q` |
| `POST /api/v1/report` | 生成群聊日报 HTML + 长图 PNG | JSON body(见下文,推荐传 `metadata.talker` 让服务端自动反推真头像) |
| 端点 | 用途 | 关键参数 |
| ---------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
| `GET /api/v1/health` | 健康检查 + 是否已初始化 | — |
| `GET /api/v1/current_time` | 获取当前本地时间(用于"今天/昨天"换算) | — |
| `GET /api/v1/contact` | 联系人 / 群聊列表 | `filter`(昵称模糊)、`type`(`user` \| `group`) |
| `GET /api/v1/chatroom` | 群聊列表(等同 contact?type=group) | `keyword` |
| `GET /api/v1/recent_chat` | 最近会话 | `limit`(默认 50) |
| `GET /api/v1/chatlog` | 聊天记录 | `talker``time``startTime`/`endTime` |
| `GET /api/v1/group_snapshot` | 群成员快照 | `md5` |
| `GET /api/v1/resolve` | 把昵称/wxid/md5 解析成 md5 | `q` |
| `POST /api/v1/report` | 生成群聊日报 HTML + 长图 PNG | JSON body(见下文,推荐传 `metadata.talker` 让服务端自动反推真头像) |
### `talker` 参数可接受的值
@@ -51,8 +79,8 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
```json
{
"m_nsUsrName": "49023470180@chatroom", // wxid, 用作 chatlog 的 talker
"m_nsNickName": { "buffer": "...", "type": "Buffer" }, // nickname 原 buffer
"m_nsUsrName": "49023470180@chatroom", // wxid, 用作 chatlog 的 talker
"m_nsNickName": { "buffer": "...", "type": "Buffer" }, // nickname 原 buffer
"type": "group",
"md5": "..."
}
@@ -64,12 +92,12 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
支持以下格式:
| 输入 | 含义 |
|------|------|
| `2026-07-03` | 单日 00:00:00 ~ 23:59:59 |
| `2026-07-01~2026-07-03` | 日期范围(闭区间) |
| `2026-07-03/14:30` | 单分钟(从 14:30:00 起 60 秒) |
| `2026-07-03/14:30~2026-07-03/15:30` | 精确到分钟的范围 |
| 输入 | 含义 |
| ----------------------------------- | ---------------------------- |
| `2026-07-03` | 单日 00:00:00 ~ 23:59:59 |
| `2026-07-01~2026-07-03` | 日期范围(闭区间) |
| `2026-07-03/14:30` | 单分钟(从 14:30:00 起 60 秒) |
| `2026-07-03/14:30~2026-07-03/15:30` | 精确到分钟的范围 |
也可以直接传 unix 秒级时间戳作为 `startTime``endTime`
@@ -92,6 +120,7 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
**步骤 3**:用计算后的参数调 `chatlog`
示例:
- 用户: "今天 摸鱼交流群 聊了啥?"
- AI: 先 `GET /api/v1/current_time` → 得到 `2026-07-03T14:30:00+08:00` → 计算 `time=2026-07-03``GET /api/v1/chatlog?talker=摸鱼交流群&time=2026-07-03`
@@ -119,33 +148,41 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
{
"title": "话题标题",
"timeRange": "10:00-12:30",
"heat": "高", // "高" | "中" | "低"
"heat": "高", // "高" | "中" | "低"
"participants": ["张三", "李四"],
"summary": "本话题讨论了什么",
"conclusion": "可选,达成的结论",
"keywords": ["关键词1", "关键词2"]
}
],
"resources": [
{ "title": "链接/文件标题", "description": "为什么重要", "sender": "张三" }
],
"resources": [{ "title": "链接/文件标题", "description": "为什么重要", "sender": "张三" }],
"importantMessages": [
{ "sender": "张三", "time": "10:23", "content": "原消息文本", "note": "为什么重要" }
],
"quotes": [
{
"messages": [{ "sender": "李四", "content": "原话1" }, { "sender": "王五", "content": "原话2" }],
"messages": [
{ "sender": "李四", "content": "原话1" },
{ "sender": "王五", "content": "原话2" }
],
"note": "为什么这些话值得引用"
}
],
"qa": [
{ "question": "Q", "answer": "A", "answerer": "解答人(可选)" }
],
"qa": [{ "question": "Q", "answer": "A", "answerer": "解答人(可选)" }],
"unresolved": [
{ "question": "待跟进问题", "owner": "相关人(可选)", "status": "待跟进", "note": "为什么还没结束" }
{
"question": "待跟进问题",
"owner": "相关人(可选)",
"status": "待跟进",
"note": "为什么还没结束"
}
],
"storylines": [
{ "title": "剧情线", "stages": [{ "time": "10:12", "event": "提出问题" }], "result": "可选结果" }
{
"title": "剧情线",
"stages": [{ "time": "10:12", "event": "提出问题" }],
"result": "可选结果"
}
],
"reversals": [
{ "topic": "某话题", "initialView": "最初判断", "finalView": "最终判断", "note": "可选说明" }
@@ -191,7 +228,7 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
{
"success": true,
"htmlPath": "/Users/.../Desktop/技术交流_日报_2026-07-03.html",
"pngPath": "/Users/.../Desktop/技术交流_日报_2026-07-03.png",
"pngPath": "/Users/.../Desktop/技术交流_日报_2026-07-03.png",
"imageDataUrl": "data:image/png;base64,iVBORw0K..."
}
```
@@ -254,12 +291,12 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
模板顶部的 4 个统计格(`消息数 / 活跃人数 / 时间跨度 / 主要话题`)宽度均分,内容过长会被截断或换行:
| 字段 | 推荐格式 | 反例(会撑爆格子) |
|------|---------|----------------|
| `metadata.messageCount` | 纯数字 `"1234"` | `"约 1.2k 条"` |
| `metadata.activeUsers` | 纯数字 `"56"` | `"大约 50 多人"` |
| `metadata.timeSpan` | **持续时长紧凑半角** `"1 h"` / `"30 min"` / `"2 d"` | `"1 小时"` / `"7 小时"` / `"1天3小时"` |
| `metadata.topicCount` 等 | 数字 / 短中文 | 长句子 |
| 字段 | 推荐格式 | 反例(会撑爆格子) |
| ------------------------ | --------------------------------------------------- | -------------------------------------- |
| `metadata.messageCount` | 纯数字 `"1234"` | `"约 1.2k 条"` |
| `metadata.activeUsers` | 纯数字 `"56"` | `"大约 50 多人"` |
| `metadata.timeSpan` | **持续时长紧凑半角** `"1 h"` / `"30 min"` / `"2 d"` | `"1 小时"` / `"7 小时"` / `"1天3小时"` |
| `metadata.topicCount` 等 | 数字 / 短中文 | 长句子 |
`timeSpan` 是**首条到末条消息的持续时长**,不是时间区间。**单位用半角空格分隔**:
@@ -295,17 +332,20 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
## 典型工作流示例
**示例 1:今日群聊总结(纯文本)**
1. `GET /api/v1/current_time` → 获取今天日期
2. `GET /api/v1/chatroom?keyword=技术交流` → 找到目标群 md5
3. `GET /api/v1/chatlog?talker=技术交流&time=2026-07-03` → 拉取今天的聊天
4. AI 用 LLM 生成总结报告(话题 TOP N、最活跃发言者等)
**示例 2:搜索特定消息上下文**
1. `GET /api/v1/chatlog?talker=摸鱼群&time=2026-07-01~2026-07-03` → 粗查近 3 天
2. 在返回的消息中定位关键词出现的时间点 T1, T2, ...
3. 对每个 Ti 分别查 `chatlog?talker=摸鱼群&time=Ti-15min~Ti+15min`,分析上下文
**示例 3:群日报(可视化长图)**
1. `GET /api/v1/chatlog?talker=技术交流&time=2026-07-03` → 拉今天聊天
2. LLM 按上方 `GroupDailyReport` schema 总结出 `report` + `metadata`
3. `POST /api/v1/report` body = 上述 JSON → 拿到 `htmlPath` / `pngPath` / `imageDataUrl`
@@ -313,6 +353,7 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
## 错误处理
- `401 unauthorized` → Token 缺失、格式错误、已被重新生成或配置不正确;请回到 API Center 复制当前 Token
- `503` → WechatExplorer 未初始化(密钥未配置),提示用户在主窗口完成配置
- `404 talker not found` → talker 不存在,先调 `contact``resolve` 确认 md5/wxid
- `400 missing required parameter` → 检查必填参数(talker / md5 / q)
@@ -321,35 +362,11 @@ GET 用于读取数据,`POST /api/v1/report` 用于生成群日报(HTML + 长图
- `400 请求体为空 / 需包含 report 和 metadata` → 调用 `/report` 时 body 必须是非空 JSON,且有这两个顶层字段
- `500 success=false` → 模板渲染失败,通常因 `report` 字段缺失或 `metadata.groupName/reportDate` 为空,检查后重试
## 配置 Claude Desktop
## 配置 Codex / Claude Code / OpenClaw
把以下加入 `~/Library/Application Support/Claude/claude_desktop_config.json`:
- **Codex**:安装本 Skill,并在启动 Codex 的本地 shell 或项目私有环境中设置 `WECHATEXPLORER_API_TOKEN`
- **Claude Code**:安装本 Skill,并在启动 Claude Code 的本地 shell 或私有环境配置中设置 `WECHATEXPLORER_API_TOKEN`
- **OpenClaw**:安装本 Skill,把 `WECHATEXPLORER_API_TOKEN` 放入 OpenClaw 自己的本地 secret / environment 配置。
- **其他 Agent**:确保执行 HTTP 请求的本地进程能读取 `WECHATEXPLORER_API_TOKEN`
```json
{
"mcpServers": {
"wechatexplorer": {
"command": "npx",
"args": ["-y", "@wechatexplorer/mcp-bridge"]
}
}
}
```
(待 P3 实现 — MCP bridge 包,在此之前可直接用 `curl` 调用 HTTP API,或通过 mcp-remote 桥接。)
## 配置 Claude Code / Codex
`~/.claude/settings.json` 或项目级 `.claude/settings.local.json` 中:
```json
{
"mcpServers": {
"wechatexplorer": {
"url": "http://127.0.0.1:6131"
}
}
}
```
(视 MCP over HTTP 支持情况调整)
不要把 `http://127.0.0.1:6131` 配置成 `mcpServers.url`;6131 提供的是 Local HTTP API,不是 MCP Server。
+2 -2
View File
@@ -224,7 +224,7 @@ Windows 当前不会扫描二级目录,请确认目录没有多选或少选一
4. 复制安装指令,粘贴给对应 Agent 执行。
5. 安装完成后,让 Agent 读取和总结本地聊天。
本地 API 默认地址为 `http://127.0.0.1:6131`,默认仅监听本机且无鉴权。详细端点和参数见 [Reader Skill 文档](../skill/wechatexplorer-reader/SKILL.md)。
本地 API 默认地址为 `http://127.0.0.1:6131`,默认仅监听本机。除 health 外的数据接口需要 Bearer TokenToken 可在 API Center 中显示或复制。详细端点和参数见 [Reader Skill 文档](../skill/wechatexplorer-reader/SKILL.md)。
### Agent Hub
@@ -235,7 +235,7 @@ Windows 当前不会扫描二级目录,请确认目录没有多选或少选一
- WechatExplorer 只读取你有权访问的本机微信数据。
- 不使用 AI 时,应用不会因为读取聊天记录而自动上传聊天内容。
- 使用 AI 问问微信、日报或图片理解时,相关内容会发送到你配置的模型服务。
- 本地 API 默认监听 `127.0.0.1`且无鉴权。不要将它暴露在不可信的局域网环境中
- 本地 API 默认监听 `127.0.0.1`并要求 Bearer Token。它仍面向个人本机使用,不建议暴露到公网或不受信任网络
## 仍然无法解决?