feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+27 -4
View File
@@ -143,9 +143,17 @@ Redis 限流使用原子 Lua 固定窗口,替代旧的近似滑动窗口 List
## PAT 调用契约
`User.AccessToken`(面板 PAT)继续支持 `Authorization: Bearer <pat>`,也兼容原有的单值 `Authorization: <pat>`。`New-Api-User` 不再参与鉴权,外部脚本不需要再发送 Bearer 与用户 ID 双请求头。这是有意的调用契约简化;旧 PAT 本身无需重新生成。
面板 PAT(个人访问令牌)通过 `Authorization: Bearer <pat>` 发送,也兼容单值 `Authorization: <pat>`;不接受 URL 参数。`New-Api-User` 不参与鉴权。
PAT 不是浏览器登录会话,不能调用登录会话管理接口,也不能签发绑定具体登录会话的 Security Proof。
- 令牌格式为 `nap_` 加 43 位 base62 随机串(约 256 bit)。明文只在创建时返回一次,数据库只保存 SHA-256 摘要,该摘要同时作为审计记录中的 `token_ref`。
- 每个用户最多 20 个令牌,存放在 `user_access_tokens` 表。创建时必须选择 scope 和过期时间:默认 30 天,最短 1 小时,也允许永不过期(界面会提示风险)。过期令牌立即失效,超过保留期后由 master 节点清理。
- scope 形如 `resource:action`。个人权限(资料、API 令牌、用量、钱包、账户安全)和部分后台权限由令牌目录定义;受 Casbin 保护的后台权限直接复用管理员权限矩阵的 resource 与 action。只能授予自己当前拥有的权限,并且每个请求都会重新读取角色和权限:撤回某项管理员权限后,携带该 scope 的令牌立即失去对应能力。
- 每条面板路由都必须声明所需 scope。未声明的路由一律拒绝(403 `ACCESS_TOKEN_ROUTE_UNDECLARED`),scope 不足返回 403 `ACCESS_TOKEN_SCOPE_DENIED`,令牌过期返回 401 `ACCESS_TOKEN_EXPIRED`。
- PAT 不能创建、改名或撤销 PAT,也不能管理登录会话;这些接口只接受浏览器登录会话。
- PAT 可以完成二次验证,Proof 绑定到该令牌,并且令牌必须持有该操作对应的 scope:`channel.key.read` 需要 `channel:secret_view`,`admin.user.*` 需要 `user:write`,本人的密码、2FA、Passkey、账号绑定和注销需要 `account_security:write`。PAT 不能使用 OAuth 验证方式。
- 通过 PAT 完成的安全变更(如修改密码、2FA、Passkey)会让该用户的所有浏览器登录会话下线,令牌本身保持可用。
- 用户被禁用期间,其令牌全部不可用,重新启用后恢复;注销或删除用户时,其令牌被删除。
- 最后使用时间和 IP 每个令牌每分钟最多更新一次;更新失败不影响请求。
## 临时鉴权流程与二次验证
@@ -153,13 +161,25 @@ OAuth state、2FA pending、Passkey ceremony、Telegram bind 等临时状态存
标准 OAuth 绑定回调由 popup 通过同源 `postMessage` 交给 opener;只有 opener 使用自身内存中的 Bearer 调用后端绑定接口。Telegram 绑定先由已登录前端创建绑定 AuthFlow,再让 widget 回调携带路径中的 `flow_token`,回调时会重新确认原登录会话仍有效。Telegram 的已签名 widget assertion 也会登记为一次性凭据,重复回放会被拒绝。
敏感操作使用有效期 5 分钟的 `X-Security-Proof`:
敏感操作使用有效期 1 分钟的 `X-Security-Proof`:
- `channel.key.read`:查看渠道密钥;
- `passkey.register`:注册 Passkey;
- `passkey.delete`:删除 Passkey。
Proof 同时绑定用户、登录会话、用户鉴权版本、会话版本和 scope,不能跨用户、跨会话或跨用途复用。
管理员对其他用户执行的高风险操作同样要求 Proof,且 scope 的 context 绑定被操作用户,签发给某个用户的 Proof 不能改用于另一个用户或另一种动作:
- `admin.user.delete`(`{"user_id"}`):`DELETE /api/user/:id` 与 `POST /api/user/manage` 的 `delete`;
- `admin.user.manage`(`{"user_id","action"}`,action 为 `disable` / `enable` / `promote` / `demote`):`POST /api/user/manage`;额度调整 `add_quota` 不要求 Proof;
- `admin.user.update`(`{"user_id"}`):`PUT /api/user/` 在请求包含新密码或 `admin_permissions` 时要求;仅修改显示名、分组、备注不要求;
- `admin.user.create`(`{"role"}`):`POST /api/user/` 创建管理员角色时要求;创建普通用户不要求;
- `admin.user.passkey.reset`(`{"user_id"}`):`DELETE /api/user/:id/reset_passkey`;
- `admin.user.2fa.disable`(`{"user_id"}`):`DELETE /api/user/:id/2fa`;
- `admin.user.binding.clear`(`{"user_id","binding_type"}` 或 `{"user_id","provider_id"}`):`DELETE /api/user/:id/bindings/:binding_type` 与 `DELETE /api/user/:id/oauth/bindings/:provider_id`。
这些 `admin.user.*` scope 只对管理员及以上角色签发;已启用 2FA 或 Passkey 的管理员必须使用其中之一,未启用时回退到密码(或已绑定的 OAuth)重新认证;密码登录被关闭时不接受密码验证。通过 PAT 调用上述接口时,令牌需持有 `user:write` 并完成二次验证;旧版令牌不能调用。
Proof 同时绑定用户、登录会话、用户鉴权版本、会话版本和 scope,不能跨用户、跨会话或跨用途复用。对 PAT 而言,Proof 绑定的“登录会话”就是令牌本身。
启用了 2FA 的用户注册 Passkey 时,register begin 与 finish 都必须携带有效的 `passkey.register` Proof;finish 会在消费一次性 AuthFlow 之前重新验证 Proof。未启用 2FA 的首次 Passkey 注册不要求该请求头。
@@ -174,3 +194,6 @@ Proof 同时绑定用户、登录会话、用户鉴权版本、会话版本和 s
- Redis 限流从近似滑动窗口改为原子固定窗口,存在明确的边界双倍突发语义。
- 用户级模型成功请求限流的 UTC 时间戳在滚动升级期间存在一个窗口的混合格式过渡,期间可能临时误放行或误拒绝。
- 自建客户端应按新的 AuthBundle、`flow_token` 和 Security Proof 契约升级;PAT 客户端可直接移除 `New-Api-User`。
- 旧版 PAT 保留在 `users.access_token` 中,自新版首次启动起 30 天内照常可用,之后自动停用并返回 401 `ACCESS_TOKEN_LEGACY_RETIRED`。停用日期记录在 `options` 表的 `LegacyAccessTokenRetireAt`,由服务端写入,不能通过设置接口修改。过渡期内不能再生成旧版令牌,但可以撤销。
- 删除了 `/api/user/token*` 接口,改为 `/api/user/access_tokens`。
- 降级到旧版本后,新令牌不可用;未撤销的旧版令牌会重新可用,因为旧版本不认停用日期。