Files
cmroubao/docs/tasks/T-204.md
T

11 KiB
Raw Blame History

id, title, phase, deps, status, created, context_ref, work_branch, write_paths
id title phase deps status created context_ref work_branch write_paths
T-204 实现用户与设备最小鉴权 2
T-201
T-202
DONE 2026-07-26 c5d3b21 main
README.md
backend-api/**
init.ps1
init.sh
docs/00-ai-start-here.md
docs/02-requirements.md
docs/03-tech-stack.md
docs/04-architecture.md
docs/05-coding-rules.md
docs/07-user-stories.md
docs/08-interaction-checklist.md
docs/api.md
docs/current-state.md
docs/routes.md
docs/design/admin-login.html
docs/design/android-login.html
docs/design/android-settings.html
docs/tasks/T-204.md
progress.md

问题 / 背景

T-203 为避免匿名管理接口提前暴露,临时把全部业务路由限制在 loopback 并使用固定 local-admin 主体。现在没有正式登录、角色验证、服务端会话、采购员与预授权设备 联合认证或凭证初始化命令,T-205 无法安全实现领取和租约,局域网也不能开放。

正式文档还留有四个需要在实现前收敛的断点:

  • Android 原型出现“重新登记设备”,但没有安全登记 API,正式前置条件是授权设备。
  • App access token 示例为 1 小时,但 refresh、App logout 和轮换协议尚未定义。
  • 管理 JSON/multipart API 是否接受 Cookie 会话以及写请求 CSRF 规则尚未写清。
  • T-203 任务都在共享 local-admin scope,直接换为用户 UUID 会让已有任务不可见。

已定合约

  1. MVP 只有 ADMIN 和 BUYER 两种角色。管理 Web/API 只接受有效 ADMIN 会话; /api/v1/auth/token 只为有效 BUYER 和已启用的预授权设备建立联合身份。
  2. 不实现自助注册、找回密码、完整用户/设备管理 UI、设备自助登记、token refresh、 App logout 或多管理租户隔离。账号和设备由本地 authctl 显式预置。
  3. 用户密码使用 bcrypt,只保存 hash;密码 12 至 72 个 UTF-8 字节。不存在、密码 错误、角色错误和禁用账号的管理登录均返回相同文案,并执行等价 bcrypt 校验。
  4. 管理 session、设备预共享 token 和 App access token 都是至少 256 bit 的随机 opaque secret;响应/Cookie 只返回原值一次,数据库只保存 SHA-256。
  5. 管理 session 固定 8 小时绝对有效期;App access token 固定 1 小时。禁用用户或 设备后,即使 token 尚未到期也立即鉴权失败。
  6. 管理 Cookie 为 HttpOnly、SameSite=Lax、Path /;TLS 请求必须设置 Secure。 Web 所有 POST 继续要求 SameSite CSRF Cookie 与表单字段匹配;Cookie 认证的管理 API POST 还要求 X-CSRF-Token 匹配。
  7. 未登录 Web 请求以 303 跳转 /login?next=...;next 只允许本站 /tasks 路径。未授权 API 返回稳定 401,角色/设备禁用不泄露资源内容。
  8. T-203 的 local-admin 保留为单管理共享资源 scope,避免已有任务消失;认证上下文 同时携带真实 user_id,新任务和后续事件可单独记录 actor。完整多租户/RBAC 后置。
  9. 设备必须由 authctl create-device 预授权。首次成功的 BUYER 登录可原子绑定空闲 设备;已绑定其他用户、设备 token 错误或设备禁用统一拒绝,不提供“重新登记”。
  10. T-204 交付后端 App 联合认证端点和正式管理登录/退出;Android 安全存储及页面 接入属于 T-206,T-204 不把 token 写入当前 App 或普通偏好设置。
  11. HTTP 明文只允许 loopback 开发监听;非 loopback 监听必须同时配置 TLS certificate 和 private key,由服务直接 ListenAndServeTLS。不提供静默降级或“临时局域网 明文”开关。
  12. 管理与 App 登录在 bcrypt 前按服务端来源地址独立限流,5 分钟最多 10 次;成功 登录清零,超限返回通用 429 和 Retry-After。

方案

  1. 新增 users、devices、admin_sessions、access_tokens 和任务 actor migration, 约束角色、启用状态、hash、过期时间和外键。
  2. 在 domain/usecase/repository/sqlite 现有分层增加认证实体、密码/令牌端口和事务; token lookup 必须同时校验用户、设备、绑定、禁用和到期。
  3. 新增 cmd/authctl,从非参数秘密输入创建 ADMIN/BUYER 和预授权设备;设备 token 只输出一次,命令与日志不回显密码,并提供账号/设备启用与禁用命令。
  4. Gin 路由拆成公共认证路由、管理页面/API 与未来设备路由;移除固定 loopback 主体 之前先建立管理 session middleware 和稳定上下文。
  5. 实现 /login、POST /login、POST /logout 以及 POST /api/v1/auth/token;管理页复用现有嵌入 CSS/JS 和 CSRF。
  6. 用真实 SQLite/HTTP/浏览器验证角色隔离、会话过期、禁用、设备绑定、token hash、 open redirect、CSRF 和响应/日志脱敏。

验收要点

  • migration 可 up/down/up,密码或 token 原值无法进入数据库。
  • authctl 可创建唯一 ADMIN/BUYER 和设备;弱密码、重复账号与无效输入被拒绝。
  • authctl 可启用/禁用账号和设备,现存 session/token 在下一次请求立即失效。
  • 正确管理凭证建立 8 小时会话;错误/不存在/禁用/BUYER 登录文案一致。
  • 未登录管理 Web 跳转安全登录页,API 返回 401;登录后 T-203 全流程仍可用。
  • 登录 next 不允许绝对 URL、//、反斜杠或非 /tasks 路径。
  • logout、过期、篡改 session 失效;Cookie 属性和所有写请求 CSRF 正确。
  • BUYER + 正确设备 secret 建立 1 小时 token,并原子绑定未绑定设备。
  • ADMIN、错误密码、错误/禁用/已占用设备不能取得 App token,错误不泄露细节。
  • Bearer token 不能访问管理路由,管理 Cookie 不能替代设备身份。
  • 禁用用户或设备使现存 session/token 立即失效。
  • 日志、错误、HTML、JSON、数据库均不含密码、原始 token 或私有样本。
  • 管理/App 登录在 bcrypt 前独立限流,超限不调用认证服务且返回 429 重试时间。
  • 任务创建与取消事件记录真实 ADMIN actor,历史数据保持可空。
  • Go test/race/vet/gofmt、migration/authctl/HTTP smoke、Playwright 和根脚本通过。

边界

  • 不实现完整 RBAC、用户/设备管理页面、密码重置、自助注册、SSO 或审计后台。
  • 不实现 access token refresh、App logout、安全存储 UI 接入;属于 T-206。
  • 不实现 claim token、租约或任务状态机;属于 T-205。
  • 不把 smoke 运行时密码、设备 token、session、数据库或命令输出提交到 Git;单元 测试只使用明显脱敏、无复用价值的 sentinel。

执行记录

2026-07-26:任务开始

  • 基于提交 c5d3b21 开始,工作区干净。
  • codebase-memory MCP 本轮未暴露 graph 工具,按项目规则回退到 rg 和定点读取。
  • 已审计 T-203 路由、固定主体、CSRF、用例/仓储装配与正式 auth/API/IX/原型,先固定 上述凭证、设备预授权、共享 scope 和任务边界再编码。

2026-07-26:认证领域与持久化

  • 新增 00003_auth.sql:users、devices、admin_sessions、access_tokens,以及 purchase_tasks.created_by_user_id、task_events.actor_user_id;up/down/up 已用 真实 SQLite 验证。
  • 密码限定为 12 至 72 个有效 UTF-8 字节,生产 bcrypt cost 为 12;不存在账号使用 同 cost 预计算 dummy hash 校验,管理登录不泄露账号、角色或禁用状态。
  • 管理 session、设备 secret 和 App access token 均为 32 随机字节的 Raw URL token, 数据库只存 SHA-256。管理 session 为 8 小时,App token 为 1 小时。
  • 每次认证联查用户/设备启用、角色、绑定、撤销和到期。SQLite immediate transaction 保证两个 BUYER 并发首次绑定同一设备时精确一个成功。
  • 新任务仍使用 local-admin 共享可见范围,并从认证上下文另存真实 ADMIN actor; 创建与取消事件同时记录 actor,历史任务和事件的 actor 允许为空。

2026-07-26:传输、命令与 TLS

  • 路由拆为公开登录/App token、ADMIN Cookie 管理路由和未来 BUYER 设备路由;删除 T-203 临时来源地址门禁,Cookie 与 Bearer 不能互换。
  • 管理登录成功轮换 session 与 CSRF,Cookie 为 HttpOnly/SameSite=Lax/Path /; Web POST 校验表单 CSRF,Cookie 管理 API 写请求校验 X-CSRF-Token。
  • 实现安全 next、通用登录错误、服务端 logout、页头退出入口和旧 /tasks 路径 CSRF Cookie 清理,避免升级后同名 Cookie 导致退出 403。
  • POST /api/v1/auth/token 校验 BUYER 密码、预授权设备 secret、App/Android 版本, 首次原子绑定并只在响应返回 access token。
  • 新增 authctl create-user、create-device 以及用户/设备 enable/disable 命令。 密码只读 CMROUBAO_AUTH_PASSWORD,设备 token 只输出一次;根脚本构建第三个入口。
  • 管理与 App 登录在 bcrypt 前使用 scope 隔离、线程安全且内存有界的来源地址限流; 5 分钟 10 次,成功清零,超限返回通用 429 和 Retry-After。
  • 配置新增 TLS certificate/private key;两者必须同时设置,非 loopback 监听无 TLS 时启动前拒绝,配置后由 ListenAndServeTLS 直接服务。

2026-07-26:验证与收尾

  • GOTOOLCHAIN=local go test -count=1 -json ./...:142 个测试通过;覆盖 bcrypt、 dummy 校验、hash 持久化、过期/撤销/禁用、并发设备绑定、角色隔离、CSRF、迁移、 登录限流、authctl 启停、TLS 配置与 Secure Cookie、任务 actor 和入口装配。
  • go test -race -count=1 ./...、go vet ./...、gofmt -l cmd internal migrations 和 API/migration/authctl Windows 构建通过;根 init.ps1 保持 Android 166 次测试 与后端全量门禁通过。
  • 真实 CLI/Gin/SQLite smoke 完成 migration up/down/up、ADMIN/BUYER/设备预置、 App token、匿名 Web 303、匿名 API 401、管理登录、图片/任务创建 actor 和 logout; 数据库抽查只存在密码/token hash。
  • Playwright 连接真实服务验证错误凭证通用文案、正确登录、任务页、创建和退出; 1440x900、390x844、360x800 无横向溢出或控件重叠,可见交互控件不小于 44 px。 浏览器复验发现并修复 /logout 主装配遗漏和旧 CSRF Cookie 路径兼容问题。
  • smoke 数据、临时凭证、数据库、图片和浏览器产物均位于被忽略目录或在提交前删除; 未读取或提交本地蝦皮订单样本。T-204 状态改为 DONE,下一任务为 T-205。