9.8 KiB
9.8 KiB
认证验证码与可信代理安全收口
Goal
关闭密码重置的远程账号接管链,并使密码重置、邮箱验证码登录共用的随机源、账号级节流、并发消费和代理 IP 边界具备可执行的安全合同;所有改动保持小团队单机部署可维护、可回滚,不引入 Redis 或新的认证平台。
Scope
- 密码重置验证码的生成、存储、申请、校验、消费、失败锁定和成功后的会话失效。
- 普通密码登录、用户主动改密和管理员重置密码与自助重置之间的并发/验证码失效边界。
- 邮箱登录验证码共用的密码学随机源、申请冷却和账号级失败边界,避免只修复一个六位码入口。
- Fastify
trustProxy、反向代理 XFF、API 宿主端口绑定和相关环境合同。 - Prisma additive migration、聚焦单测、真实 PostgreSQL 并发回归脚本/测试以及部署前静态门禁。
Approved Requirements
1. 密码学随机与安全存储
- 六位验证码统一使用
crypto.randomInt(0, 1_000_000)。 - 密码重置码使用带用途域分离的 HMAC;生产环境的验证码 pepper 必须配置且不得进入日志、响应或前端 bundle。
NODE_ENV=production时禁止启用任何显示或记录验证码的开发开关。
2. 单账号单有效码与申请节流
- 新的密码重置码必须在同一事务中使该账号所有旧未消费码失效。
- 事务先锁定员工行,再检查恢复锁、账号申请冷却和申请窗口,确保并发申请最终只有一枚有效码。
- 账号申请节流是数据库权威边界;IP 限流仅作外层防护,轮换 IP 不得绕过账号节流。
- 不存在、停用、锁定、冷却中的账号对申请端返回相同的通用成功结构,避免账号枚举和通知轰炸反馈。
3. 失败锁定与原子消费
- 密码恢复使用独立于密码登录的失败计数、失败窗口和锁定截止时间。
- 默认连续失败 5 次锁定恢复入口 30 分钟;重新申请验证码不能清零失败计数。
- 验证、失败累计、条件消费、密码更新、
token_version增加、refresh session 撤销、旧恢复码和未使用邮箱验证码失效必须在受锁事务内完成。 - 同一正确码并发提交时仅一个请求成功;其余请求返回通用无效/过期响应。
- 活跃、不存在、停用、锁定账号的无效验证响应保持同一 HTTP 状态和数据结构。
4. 可信代理和端口边界
- Fastify 不再使用无限制
trustProxy: true;开发默认不信任代理,部署环境只信任明确的一跳或配置的代理 CIDR。 - Caddy/Nginx 必须覆盖而不是追加不可信客户端 XFF;Nginx 模板不得使用会保留客户端链的配置。
- Compose API 端口只绑定宿主回环地址,阻止绕过反向代理直接访问。
- 可信 IP 修复作用于密码登录、邮箱验证码登录、忘记密码和重置密码全部认证限流入口。
5. 迁移、回滚与兼容
- 采用 additive migration:为密码恢复增加失效、失败、申请和锁定状态;不重写历史迁移。
- 部署迁移时统一使现有密码重置码失效,用户需要重新申请;不得保留旧弱验证码作为兼容。
- 第一版不依赖数据库 partial unique index,以员工行锁和条件更新保证不变量,保持旧应用能够启动。
- 发布迁移或回滚旧认证代码时,边缘必须继续关闭
/api/auth/forgot-password、/api/auth/reset-password、/api/auth/email-login/code、/api/auth/email-login/verify四个 challenge 端点,直到安全版本恢复;API 端口不得重新公网暴露。
Acceptance Criteria
- 密码重置和邮箱登录验证码均使用 CSPRNG,密码重置码不再使用无密钥 SHA-256。
- 顺序和并发重复申请后仅最新密码重置码有效,所有旧码失败。
- 轮换 XFF/IP 的错误尝试仍按账号累计,达到阈值后锁定;重新申请不清零。
- 20 个并发正确重置请求恰好一个成功,密码、
token_version和审计只产生一次有效状态迁移。 - 已用旧密码完成校验的并发登录在重置先提交时不能创建 refresh session;登录先提交时其会话被重置事务撤销。
- 重置成功后旧 access token、全部 refresh session、旧重置码和未使用邮箱登录码均不可用。
- 用户主动改密和管理员重置密码同样撤销旧会话并失效未使用的两类验证码。
- 活跃、不存在、停用、锁定账号的无效验证响应无可观察的结构差异。
- 伪造 XFF 不能改变应用识别的可信客户端 IP,Compose API 不绑定所有宿主接口。
- 生产配置启用验证码显示开关时应用 fail closed。
- Prisma validate、部署环境合同、聚焦认证测试、真实 PostgreSQL 并发验证、Trellis governance 和
git diff --check通过。 - 独立只读 reviewer 复核认证、迁移、并发、代理和回滚,不修改代码。
Definition of Done
- 两条已确认账号接管条件(多码穷举、并发重复消费)均由数据库可执行边界阻断。
- 认证限流不再信任任意客户端 XFF,部署端口不能绕过受控反向代理。
- 迁移和代码回滚路径明确,未访问生产数据库、未部署、未修改公网配置。
- 聚焦回归和项目门禁通过,改动以中文提交并引用本 task 名称。
Decision (ADR-lite)
Context:现有实现的验证码随机、生命周期、失败锁和代理 IP 都不是独立安全边界;单独调低 IP 限流或只改随机源不能关闭接管链。
Decision:采用“员工行锁 + 旧码失效 + 账号级恢复状态 + 条件消费 + 显式可信代理”的最小方案。保留现有单机 PostgreSQL,不引入 Redis;认证各入口共享随机与代理规则,但密码重置和邮箱登录保留各自业务动作。
Consequences:现有未完成重置流程会在迁移时失效;新增少量 schema 和部署环境配置。换来跨进程一致的账号锁、可验证的单码不变量和明确的回滚边界。
Implementation Plan
- 配置与模型:增加恢复安全配置、Prisma 字段和兼容迁移,生产开发开关 fail closed。
- 认证实现:抽取 CSPRNG/HMAC 与账号状态逻辑,重写申请和验证事务,补邮箱验证码相邻边界。
- 代理与部署:显式 trust proxy、回环端口、Nginx XFF 覆盖和环境合同。
- 验证:单元/路由测试、真实 PostgreSQL 双连接并发测试、静态门禁和只读安全 reviewer。
Test Plan
- 语法与模型:
node --check覆盖认证、用户、配置、应用、密码学工具和并发编排器;cd server && npm run prisma:validate。 - 聚焦单测:认证路由、管理员重置、生产安全配置/可信代理、隔离并发 guard;覆盖 CSPRNG/HMAC、旧码失效、冷却、账号级锁定、统一错误、条件消费、会话撤销和旧密码登录竞态守卫。
- 部署静态合同:
node deploy/trialrun/validate-env-contract.js、相关 shellbash -n,验证 API 回环绑定、Nginx 覆盖 XFF、强 pepper/单跳配置和“迁移先于应用切换”。 - 真实 PostgreSQL:
bash scripts/qa/run-web-gates.sh auth-security-local只创建hyb_oa_auth_*loopback 临时库,运行 20 个并发申请、20 个同码消费、reset-first、login-first 和 refresh/reset 行锁竞态,结束后删除本次创建的临时库。 - 项目门禁:
node scripts/qa/check-trellis-governance.mjs、git diff --check和bash scripts/check-minimal.sh;不连接生产数据库、不部署。 - 只读复核:独立 reviewer 检查认证、迁移、代理、发布顺序和回滚,不修改代码。
Verification Evidence (2026-07-17)
- 聚焦测试:4 个认证相关 Vitest 文件,
61/61通过。 - 完整最小门禁:服务端
155个测试文件、831/831通过;前端 typecheck 与生产构建通过。 - 模型与部署:Prisma validate、59 个 migration 空库部署、部署环境合同、相关 Node/Shell 语法均通过。
- 隔离 PostgreSQL:并发申请、同码消费、reset-first、login-first、refresh/reset 五组场景全部通过;本轮临时数据库和一次性容器均确认删除。
- 治理与隐私:Trellis task validate、governance、process baseline、敏感内容扫描和
git diff --check通过;文档注册审计为0error,保留既有未注册文档 warning。 - 两轮独立只读 reviewer 的阻断项已吸收;最终 delta review 未发现剩余 P0,实现与部署合同仅保留下面列出的后续项。
- 本轮未部署、未连接现有或生产数据库、未修改公网/Caddy 配置,也未发送外部邮件或企业微信写入。
Residual Follow-up
- 历史 password reset 外键删除语义与当前 Prisma schema 存在既有漂移;本次 migration 未夹带修复,后续单独裁决。
- 既有普通密码登录/通用 rate-limit 数值配置仍使用宽松解析,归入后续 P1 配置收口。
- 上线前需只读审计历史邮箱大小写重复,并由操作人完成四个 challenge 端点和 Caddy XFF 覆盖的
EXTERNAL_GATED人工确认。 - 根目录开发 Compose 的端口暴露不属于正式 trialrun 合同,作为后续 P2 开发环境收口项处理。
Out of Scope
- 公网配置修改、部署、生产数据库迁移、真实账号验证或日志取证。
- Redis/WAF/验证码服务平台、短信验证码、OIDC/企业微信登录架构重做。
- 采购附件 P0;该问题使用独立 Trellis task。
- 全站认证 UI 改版或密码策略重构。
Technical Notes
- 审查证据:
.trellis/tasks/07-16-system-multidimensional-audit/research/business-security-review.md。 - 主要实现范围:
server/src/routes/auth.js、server/src/app.js、server/src/config.js、Prisma schema/migration、部署 Compose/Nginx 和认证测试。 - 用户已于 2026-07-17 批准上述 P0 方案;本 task 无剩余产品偏好问题。