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

173 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-204
title: 实现用户与设备最小鉴权
phase: 2
deps:
- T-201
- T-202
status: DONE
created: 2026-07-26
context_ref: c5d3b21
work_branch: main
write_paths:
- 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 和响应/日志脱敏。
## 验收要点
- [x] migration 可 up/down/up,密码或 token 原值无法进入数据库。
- [x] authctl 可创建唯一 ADMIN/BUYER 和设备;弱密码、重复账号与无效输入被拒绝。
- [x] authctl 可启用/禁用账号和设备,现存 session/token 在下一次请求立即失效。
- [x] 正确管理凭证建立 8 小时会话;错误/不存在/禁用/BUYER 登录文案一致。
- [x] 未登录管理 Web 跳转安全登录页,API 返回 401;登录后 T-203 全流程仍可用。
- [x] 登录 next 不允许绝对 URL、`//`、反斜杠或非 `/tasks` 路径。
- [x] logout、过期、篡改 session 失效;Cookie 属性和所有写请求 CSRF 正确。
- [x] BUYER + 正确设备 secret 建立 1 小时 token,并原子绑定未绑定设备。
- [x] ADMIN、错误密码、错误/禁用/已占用设备不能取得 App token,错误不泄露细节。
- [x] Bearer token 不能访问管理路由,管理 Cookie 不能替代设备身份。
- [x] 禁用用户或设备使现存 session/token 立即失效。
- [x] 日志、错误、HTML、JSON、数据库均不含密码、原始 token 或私有样本。
- [x] 管理/App 登录在 bcrypt 前独立限流,超限不调用认证服务且返回 429 重试时间。
- [x] 任务创建与取消事件记录真实 ADMIN actor,历史数据保持可空。
- [x] 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。