diff --git a/docs/task/50-admin-首次管理员初始化与网页登录.md b/docs/task/50-admin-首次管理员初始化与网页登录.md new file mode 100644 index 0000000..1e8f070 --- /dev/null +++ b/docs/task/50-admin-首次管理员初始化与网页登录.md @@ -0,0 +1,86 @@ +# 50 Admin:实现首次管理员初始化与网页登录 + +- 类型:需求 +- 父级大工单:#14 +- 所属 MVP / 版本:#15 / MVP +- 状态:已完成 +- 日期:2026-08-09 +- Gitea 工单: + +## 背景与目标 + +Admin 原有全部网页都可匿名访问,也没有管理员初始化、登录或退出能力。本任务 +建立最小且完整的网页登录边界:首次运行由操作员创建唯一的首位管理员,随后 +所有业务网页必须凭服务端 Session 访问,同时保持 Client API 的现有调用方式不变。 + +## 最终方案 + +- 新增 v6 迁移,在末尾追加 `users` 和 `web_sessions`,未改写 v1–v5;用户名 + 使用不区分大小写的唯一约束,角色和状态使用固定值约束。 +- `/setup` 仅在用户表为空时开放;创建管理员前在写事务内再次检查,两个并发 + 初始化请求最多一个成功。入口一旦关闭,即使账号以后禁用也不会重新开放。 +- 密码使用 bcrypt 默认成本哈希,服务层校验至少 8 个字符和 bcrypt 的 72 字节 + 上限;数据库、页面及日志均不保存或回显明文密码。 +- 登录成功生成 32 字节密码学随机 Token,浏览器 Cookie 保存原文,数据库只保存 + SHA-256。Session 固定 12 小时有效,退出和账号禁用会使旧 Session 失效。 +- 认证 Cookie 使用 `HttpOnly`、`SameSite=Lax`、`Path=/`,HTTPS 请求自动增加 + `Secure`;登录后的 `next` 只接受本站绝对路径,阻止开放重定向。 +- Web 公开页和业务页都经过 CSRF;认证中间件只挂业务网页路由组。Client API + 继续注册在独立路由组,不要求网页登录 Cookie,也不经过浏览器 CSRF。 +- 增加独立初始化页和登录页;业务导航显示当前用户名,并用 POST 表单退出。 + +实际实现与工单确认方案一致。采购员管理、密码重置和登录限速留给 #51,Client +API Key 认证留给 #52,本任务没有扩大范围。 + +## 改了哪些 + +- `admin/repository/db.go`、`admin/model/model.go`、`admin/repository/user.go`: + v6 数据结构、用户与 Session 数据访问及初始化事务。 +- `admin/service/auth.go`:首次初始化、bcrypt 密码校验、登录认证、Session 哈希与退出。 +- `admin/handler/web/auth.go`、`admin/handler/web/web.go`:公开认证页、业务页中间件、 + Cookie、本站回跳和退出路由。 +- `admin/templates/auth/`、`admin/templates/partials/header.html`、 + `admin/static/css/app.css`:初始化、登录和已登录导航界面。 +- `admin/auth_integration_test.go`、`admin/handler/web/auth_test.go`、 + `admin/service/auth_test.go`、`admin/repository/migrate_test.go`、`admin/main_test.go`: + 迁移、并发、安全属性、HTTP 完整流程和回归测试。 +- `docs/admin/03-data-model.md`:将 v6 用户与 Session 模型从规划更新为已实现。 + +## 验收结果 + +| 验收标准 | 结果 | +|---|---| +| v6 可从全新库和历史结构升级,且未改写旧迁移 | 通过 | +| 首次初始化并发请求最多创建一个管理员 | 通过 | +| 初始化完成后 `/setup` 永久关闭 | 通过 | +| 密码只保存 bcrypt 哈希,错误凭据统一提示 | 通过 | +| Session 原文不入库,固定 12 小时过期 | 通过 | +| 退出、过期和账号禁用后旧 Session 失效 | 通过 | +| Cookie 具备 HttpOnly、SameSite、Path 和 HTTPS Secure 属性 | 通过 | +| 登录回跳不能跳往外部网站 | 通过 | +| 所有 Web POST 均经过 CSRF | 通过 | +| Client API 不经过网页登录与浏览器 CSRF | 通过 | +| 1366×768 初始化、登录及已登录导航布局 | 通过 | +| Go 1.23.0 全量检查 | 通过 | + +## 测试 + +- 执行的命令: + + ```powershell + cd D:\chengma\cmautobuy\admin + gofmt -l . + go vet ./... + go build ./... + go test ./... -count=1 + ``` + +- 结果:Go 版本为 `go1.23.0 windows/amd64`;全部通过,`gofmt -l .` 无输出。 +- 浏览器验证:使用隔离的临时数据库,在 1366×768 完成 `/setup` 初始化、`/login` + 登录并进入 `/shopee`;表单标签、密码框、提示、用户名和退出按钮均正常。 +- **没验证到的部分**:没有在真实 HTTPS 服务上手工检查浏览器 Cookie,`Secure` + 属性由带 TLS 请求的单元测试覆盖;没有触发真实采集或采购。 + +## 相关提交 + +- `0a7db40` feat: 实现 Admin 首次初始化与网页登录 (#50)