审核流程
MODERATION_REQUIRED 控制是否开启人工审核,默认 true。
开启审核(默认)
评论者提交
→ 蜜罐 / 长度 / 链接数 / 限频
→ Cloudflare Turnstile 校验(若已配置)
→ 整条评论封成加密 + 签名信封
→ 发到博主邮箱
博主点邮件链接
→ 静态审核页只预览(不写入)
→ 点「批准并写入仓库」
→ 复验签名 / 7 天期限 / 来源白名单
→ GitHub Contents API 写入
→ 约 60 秒后博客可见待审数据不写入任何存储,只存在于审核邮件的 URL fragment 里。
为什么批准必须是 POST
邮件安全网关会预抓取邮件里的链接。如果批准动作是 GET,扫描器会替你批准。因此链接只打开预览页,真正的写入是页面里的 POST。
URL fragment 存储
密文放在 #token= 后面而不是查询串里:
- fragment 不会发送到服务器,不进访问日志
- 长度限制几乎不存在(Chrome / Firefox 可承载数 MB)
- 审核页加载后立即用
history.replaceState清除,防止 Referer 泄露
凭据规则
- 内容绑定签名(HMAC-SHA256)
- 7 天有效期
- 一次性内容幂等:同 id 同内容重复批准不会重复写入;同 id 不同内容返回 409 且不覆盖
关闭审核
设置 MODERATION_REQUIRED=false:
评论者提交
→ 蜜罐 / 长度 / 链接数 / 限频
→ Cloudflare Turnstile 校验(若已配置)
→ 直接写入 GitHub
→ 约 60 秒后博客可见此时:
- 不发任何邮件,
MODERATOR_EMAIL与发件配置不再必填 - widget 成功文案变为「留言已发布,约 1 分钟后可见」
/api/health返回moderation: false并附警告
风险:若未配置 Turnstile,或 Turnstile 被绕过,匿名提交会直接写入目标仓库。同仓库模式下后果更严重。若无法承担该风险,请开启 Turnstile 或保持人工审核。
人机验证开关(可选)
TURNSTILE_SITE_KEY 与 TURNSTILE_SECRET 成对配置即启用人机验证,都不配置则关闭。它与本文的审核开关相互独立:
| 审核 | Turnstile | 提交后的路径 |
|---|---|---|
| 开启 | 配置 | 校验 → 审核邮件 → 批准后写入 |
| 开启 | 关闭 | 直接发审核邮件(缺少反垃圾强度) |
| 关闭 | 配置 | 校验 → 直接写入 |
| 关闭 | 关闭 | 直接写入(压力全在蜜罐与限频上) |
只配置其中一个 key 会被视为配置错误,/api/health 报告 degraded。
表单知情同意
无论审核是否开启,表单都要求勾选:
我同意昵称和留言经审核后公开,并保留在公开 Git 仓库的历史记录中。
删除评论只能删除当前版本,Git 历史中的记录仍然存在。这一点必须让评论者知情。