执行限制与验证
当前状态:I3 与 I4 保护已接入,本地验收进行中。 文件与个人任务接口已接入服务端原子限制,上传 / 任务提交 / 手动重试支持可选 Turnstile。收费与用户 API 密钥已复用业务限额;密钥自己的插件限额及版本接口见教程。真实验证码与云端负载仍待独立验收。
限制在哪里执行
文件写入和读取分别使用 storage.uploadsPerMinute、storage.readsPerMinute。服务端按当前登录账号和操作,在 PostgreSQL 用同一条件更新原子计数;通过账号与入口校验后,受限步骤的失败请求也计入。每分钟窗口根据数据库时间开始,拒绝后返回稳定的 RATE_LIMITED。HTTP 文件入口带 Retry-After: 60,tRPC 错误含 executionError 和 retryAfterSeconds。
限制在服务端执行,修改前端按钮或直接请求不会绕过。tRPC 批量请求的每个过程分别计数。当前策略是固定窗口,并不代表滚动窗口、全站并发上限或模型成本预算。独立站使用各自数据库,不能共享计数表。文件配额另用原子预留,不能用请求限流代替字节 / 数量占用。
当前个人文件与任务拒绝匿名会话;公开 JSON / 文本工具在浏览器本地处理,不调用收费接口。认证继续使用 Better Auth 自身限制。任务使用 tasks.writesPerMinute / readsPerMinute 和独立操作键,提交 / 重试 / 取消与读取分别计数;来源必须匹配 APP_ORIGIN,组织切换不扩大所有权。取消不要求验证码,便于用户停止等待。访客服务端入口及外部 API 限制随实际功能落实。
开启 Turnstile
- 在 Cloudflare 创建属于该站和环境的 widget,配置实际允许的域名。
- 在 API 环境填入
PUBLIC_TURNSTILE_SITE_KEY和TURNSTILE_SECRET_KEY。前者可公开,后者只保留在服务端,不提交到 Git。 - 设置
websiteConfig.security.turnstile.enabled = true,执行bun config:check,再一起构建前后端。
开启但缺少配置会使配置检查 / API 初始化失败,不静默变成未保护模式。两个值需成对配置;Cloudflare 官方测试键仅允许 development,staging / production 拒绝这些已知测试键。生成站不复制源站凭据,需要单独配置。
文件列表 API 只返回公开 site key 和动作 file_upload。工作台按此配置显式加载官方 SDK,关闭时不插入验证脚本。组件跟随浅深主题,使用紧凑尺寸,避免窄屏表单被 iframe 的最小宽度撑开;加载失败、验证失败或过期会清空 token 并提示重试。路由退出 / 重新验证时移除旧 widget,迟到回调不能重新激活旧 token。
上传把 token 放入 X-Turnstile-Token 请求头,原始文件仍为请求体。服务端先确认会话、本站来源与操作频率,再调用固定 Siteverify 地址,最后才读取文件并预留存储。只有成功且 action === file_upload、hostname === new URL(APP_ORIGIN).hostname 的主机名匹配时继续。客户端传来的域名或动作不决定服务端期望值。
任务提交和手动重试将 token 放在独立的 verificationToken 字段,分别要求 task_submit / task_retry 动作。服务端验证后才受理或开启新的尝试,token 不保存在请求指纹与数据库。页面复用同一 SDK 生命周期和错误恢复,公开接口只返回 site key 与期望动作。验证失败、错误域名 / 动作不会创建任务;批量请求的每项均验证并计数。
验证请求最长等待 10 秒;网络错误、服务异常、非成功 HTTP 响应或非法响应结构都拒绝操作。token 使用后不可再用,所以每次提交结束都会清空并重新生成;不自动重传上传文件,也不自动重试结果不确定的验证。失去上传响应时先刷新文件列表确认是否已保存。日志不记录 token、secret、provider 响应或文件内容。
Siteverify 请求使用 redirect: "manual",仅接受成功 HTTP 响应,3xx 不跟随、不继续转发服务端 secret 或用户 token。2026-10-11 的实际 workerd 验收确认,原 error 模式在当前运行器中会于请求发送前失败;修复后正常请求、外站 Location 拒绝和十秒期限均通过。Cloudflare 文档仍列出三种重定向值,因此该结论限定于本项目已安装运行器的实际行为,不推断所有云端版本均不支持 error。
| 错误代码 | 行为 / 排查 |
|---|---|
RATE_LIMITED | 等待窗口结束,再提交;不要连续自动调用 |
VERIFICATION_REQUIRED | token 缺失或超长,先重新验证 |
VERIFICATION_FAILED | 验证失败、过期 / 重放,或动作 / 域名不匹配 |
VERIFICATION_UNAVAILABLE | 验证服务故障,保留输入并稍后重试;不会继续写存储 |
VERIFICATION_NOT_CONFIGURED | 检查开关、公开 site key 与服务端 secret |
目前工作台 HTML 尚未设置 CSP。若后续增加,需按 Cloudflare 官方说明允许 widget 所需的脚本与 iframe 来源;文件下载的独立限制响应头保持原有策略。
验证口径与尚未完成项
服务模块测试控制 Siteverify 响应,覆盖成功、缺失配置、token 过长、错误动作 / 域名、重复 / 过期、网络失败、异常状态与响应结构。组件测试使用受控 SDK 回调,检查脚本失败重试、过期清空、卸载及旧回调失效,不下载第三方脚本或完成真实验证码。
实际 Hono 文件路由、Better Auth 会话、真实迁移的 PGlite 和本地 R2 共同验证:未通过验证时文件与配额表为空;成功后存储一份文件;公开列表不泄露 secret。真实 tRPC 三项批量读取、上限为二时,只有两项成功,第三项有 429 业务错误和重试提示,数据库计数为二。这些是本地集成证据,不代表真实 Cloudflare widget 验收或线上负载表现。
任务路由测试另验证缺 token、上传动作误用于任务、提交动作通过后只创建一个任务,以及真正 tRPC HTTP 批量读取的逐过程限制与重试字段。主预览默认保持验证关闭,实际任务操作可以检查且未加载第三方验证码脚本。真实 widget 的前端 / 服务端匹配、上线域名、网络错误下体验、真实验证码手机尺寸及负载成本仍需独立验收;收费与 API 密钥路径保留原计划要求。
bun files:validate
bun files:validate --turnstile两个命令均使用一次性 loopback PostgreSQL、生产 workerd 和本地 R2,不给主库迁移或写入。第二条在自己的配置文件开启校验,使用受控 Siteverify 接口,不连接 Cloudflare、不下载 widget 或完成真实验证码。它核对缺失 / 超长 token、错误域名 / 动作、重放、非法响应、503、带外站 Location 的 302 以及实际十秒超时;拒绝后文件 / 配额表和 R2 都为空,正确响应只写一份文件,随后仍执行原上传 / 私有读取 / 删除 / 清理验收。额外探测消耗真实业务限额,程序只调整一次性账号的窗口以继续验证,没有提高产品限额。最终清理自己资源并核对正常网站配置字节未变。
文件入口的原生证据与共享校验函数五种动作的单元测试分别记录,不将这次检查扩大为真实任务 / 收费 / 营销 widget 或云端验收。
参考官方文档(服务端 / 请求重定向于 2026-10-11 复核):服务端校验、Worker Request 与重定向、显式组件渲染、测试键与真实验证的区别。