背景: - 开发计划 A1 批次要求登录/刷新/登出、用户生命周期、路由与按钮权限、 权限三态用例;视觉已选定方案 B(主从分栏 + 分屏登录)。 改动: - packages/shared:新增 identity 契约(User/Me/Login/Refresh/Department/Role) 与 user:view 权限点;写接口 strict 校验。 - apps/api:新增 identity 模块与 Prisma expand 迁移(departments/users/roles/ user_roles/role_permissions/refresh_tokens);Argon2id、JWT access+refresh 旋转与 jti 吊销、登录失败锁定、首登强改、守卫链、/me、@Audit 上下文、幂等种子。 - apps/web:按方案 B 落地 /users、/users/new、/users/:id、分屏登录与 403; usePermission/<Can>、路由守卫、401 单飞刷新、停用会话兜底、Semi + token。 - e2e:新增 A1-QA-01 六类端到端用例(A1_E2E 门控)。 - openapi 与前端生成类型同步;.env.example 登记认证变量;实施报告入库。 影响面: - 新增运行时依赖 @nestjs/jwt、argon2(计划 A1-BE-02 已批准,版本精确锁定)。 - 迁移为 expand,不改既有表;本地需 prisma migrate 后重新 seed。 - 平台导航「用户与组织」由 /platform/users 调整为 /users。 回滚方式: - 代码整体回滚本提交;迁移为 expand,回滚代码不需删表; - 本地如需彻底回退按实施报告 §6 处理。 验证证据: - pnpm format:check / lint / typecheck 通过; - pnpm test:shared 95 / api 60 / web 55; - pnpm test:int:7 个(真实 PG/Redis); - A1_E2E=1 playwright test e2e/a1-identity.spec.ts:6/6; - 报告:docs/开发计划/灵犀_A1身份认证与用户管理_实施报告_V0.1.md
This commit is contained in:
1 parent
f0d218bd99
commit
4d9eb980af
120 files changed
+13112
-314
No files matched your search
@@ -0,0 +1,128 @@
|
||||
# 灵犀 A1 身份认证与用户管理 · 实施报告 V0.1
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 实施日期 | 2026-11-02 |
|
||||
| 批次 | A1(身份认证与用户管理) |
|
||||
| 状态 | 已完成,门禁通过;**未提交** |
|
||||
| 依据 | 开发计划 V0.1 §6.2 / §7.1、技术架构 V0.4 §5.4.1 / §6.3、工程规范 01–10 |
|
||||
| 视觉基线 | A0「方向 A · 云白蓝」+ 选定方案 B(主从分栏 + 分屏登录) |
|
||||
|
||||
---
|
||||
|
||||
## 1 范围与完成度
|
||||
|
||||
| 任务 | 内容 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| A1-BE-01 | 部门 / 用户 CRUD、停用启用、角色绑定、客户转交占位 | ✅ |
|
||||
| A1-BE-02 | Argon2id 登录、JWT access + refresh 旋转、jti 吊销、登出、失败锁定、密码策略、SSO 接口预留 | ✅(SSO 仅预留) |
|
||||
| A1-BE-03 | Jwt → Permission → SpecialPermission → Scope 守卫链、`@Audit` 与请求上下文 | ✅(Scope 骨架) |
|
||||
| A1-BE-04 | `/me` 用户 / 角色 / 权限 / 数据范围 / 可见客户;权限即时生效 | ✅(dataScope 仅 all / 本部门及下级,六类留 A2) |
|
||||
| A1-FE-01 | 登录页、token 存储刷新、登出、过期跳转、路由回跳 | ✅ |
|
||||
| A1-FE-02 | 用户列表(游标分页 / 筛选 / 排序)、新建 / 编辑、停用确认、部门树、角色多选、客户转交入口 | ✅ |
|
||||
| A1-FE-03 | `/me` 缓存、`usePermission` / `<Can>`、路由与按钮级控制、403 页 | ✅ |
|
||||
| A1-QA-01 | 登录 / 刷新 / 登出 / 停用 / 角色变更 / 越权 6 类用例 | ✅(接口 int + 浏览器 e2e) |
|
||||
|
||||
口径确认(用户拍板):初始密码由管理员设定、首次登录强制修改;手机号列表掩码、详情全号仅 `user:manage` 可见;路由按开发计划附录 C 使用 `/users`。
|
||||
|
||||
---
|
||||
|
||||
## 2 交付物
|
||||
|
||||
### 2.1 契约(`packages/shared`)
|
||||
|
||||
- 新增 `src/schemas/identity.ts`(+ spec):`UserSummary`(`phoneMasked`,永不含全号)、`UserDetail`(`phone?` 仅授权时返回)、`UserListQuery`(`cursor/limit/keyword/department_id/role_id/status/sort/order`)、`CreateUserInput`、`UpdateUserInput`、`DisableUserInput`、`TransferCustomersInput`、`LoginInput`、`RefreshInput`、`LoginResponse = tokenPair + me`、`MeResponse`、`DepartmentNode`、`RoleSummary`。
|
||||
- `permissions.ts` 新增 `USER_VIEW = 'user:view'`;保留 `USER_MANAGE`。
|
||||
- 写 body camelCase、query snake_case;写接口 `.strict()`;错误码沿用 A0 统一表。
|
||||
|
||||
### 2.2 后端(`apps/api` + `prisma`)
|
||||
|
||||
- 依赖:`@nestjs/jwt@12.0.3`、`argon2@0.45.1`(计划 A1-BE-02 已批准,版本精确锁定)。
|
||||
- 迁移:`prisma/migrations/20261102143519_a1_identity`(expand):`departments`、`users`、`roles`、`user_roles`、`role_permissions`、`refresh_tokens`。
|
||||
- 种子(幂等):8 角色 + 角色权限映射、3 个一级部门 + 6 个小组、11 个用户(`admin` 超管 + 10 位示例成员),统一初始密码 `lingxi2026`、`must_change_password=true`。
|
||||
- identity 模块(`apps/api/src/modules/identity`):
|
||||
- `POST /auth/login|refresh|logout|change-password`、`GET /me`、`GET/POST /users`、`GET/PATCH /users/{id}`、`POST /users/{id}/disable|enable|transfer-customers`、`GET /departments/tree`、`POST/PATCH /departments`、`GET /roles`。
|
||||
- refresh token 旋转:jti + token_hash + family,旧 token 原子吊销;重放整族吊销。
|
||||
- 登录失败 Redis 计数,达阈值锁定 15 分钟(42901)。
|
||||
- 权限每请求从 DB 解析,角色变更下一次请求生效。
|
||||
- 未修改初始密码时除白名单接口外统一 40903。
|
||||
- 手机号:列表只 `phoneMasked`;详情仅调用者具 `user:manage` 时附带 `phone`。
|
||||
- 安全与上下文(`apps/api/src/common/security`):`@Public`、`@RequirePermissions`、`@CurrentUser`、`@Audit`、AsyncLocalStorage 请求上下文;`@Audit` 先写 Pino 结构化日志(A5 落库预留)。
|
||||
- env:`JWT_ACCESS_SECRET` / `JWT_REFRESH_SECRET`(≥32 位)/ `JWT_ACCESS_TTL_SECONDS=900` / `JWT_REFRESH_TTL_SECONDS=604800` / `AUTH_MAX_LOGIN_FAILURES=5` / `AUTH_LOCK_MINUTES=15`;`.env.example` 已登记,本地 `.env.local` 已配置。
|
||||
|
||||
### 2.3 前端(`apps/web`)
|
||||
|
||||
- 路由:`/users`、`/users/new`、`/users/:id`、`/403`;`RequireAuth` + `RequirePermission`;未登录跳 `/login?redirect=`(含 `sanitizeRedirect` 防开放重定向);无权限进 403;停用账号回登录并给出停用说明。
|
||||
- 权限:`usePermission` / `usePermissions` + `<Can>`,无权限按钮不渲染;`/me` 走 TanStack Query 缓存。
|
||||
- 登录:方案 B 左右分屏(左 560 品牌 + 能力 + CSS 规格缩略,右表单卡);真实 `POST /auth/login`;错误 / loading / 锁定态;首次登录强改在登录页内完成,成功后清会话要求用新密码重登。
|
||||
- 用户列表 / 详情:左 400 列表 + 右 760 详情(基本资料 / 角色权限 / 负责客户 / 登录与安全 / 操作记录);搜索、部门、角色、状态、排序、游标分页;停用 / 启用 / 客户转交 / 调整角色 / 超管授予均「对象卡 → 后果 → 勾选」二次确认;编辑资料弹窗;空 / 加载 / 错误三态;1024 / 1280 适配。
|
||||
- 新建用户:单栏三段(账号信息 / 组织与角色 / 初始设置),Semi Form + 共享 Zod 同源校验,部门两级联动、角色多选、超管二次确认。
|
||||
- 样式:`--lx-*` token + Semi 组件 + `styles/users.css`;未引入其他组件库/图标库。
|
||||
|
||||
### 2.4 测试(`e2e`)
|
||||
|
||||
- `e2e/a1-identity.spec.ts`:6 条浏览器端到端用例(`A1_E2E=1` 门控,CI 无依赖时 skip),真实 API + 真实 PG/Redis;测试数据自建自清。
|
||||
- `e2e/README.md`:运行前置、环境变量、CORS 说明。
|
||||
|
||||
---
|
||||
|
||||
## 3 验证结果
|
||||
|
||||
| 门禁 | 命令 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 格式 | `pnpm format:check` | ✅ |
|
||||
| Lint | `pnpm lint` | ✅ 0 warning |
|
||||
| 类型 | `pnpm typecheck` | ✅ 10/10 tasks |
|
||||
| 单测 | `pnpm test` | ✅ shared 95 / api 60 / web 55,共 210 |
|
||||
| 集成 | `pnpm test:int` | ✅ 2 files / 7 tests(真实 PG + Redis) |
|
||||
| OpenAPI | `pnpm openapi:export` | ✅ 新增 14 条 identity 路径,`apps/web/src/api/generated/openapi.d.ts` 已更新 |
|
||||
| 浏览器 e2e | `A1_E2E=1 pnpm exec playwright test e2e/a1-identity.spec.ts` | ✅ 6/6(登录+强改、刷新旋转、登出、停用、角色变更、越权 403) |
|
||||
|
||||
联调截图:`design/a1-ui-v1/shots/impl/`(登录、强改、用户列表、详情、角色 Tab、停用弹窗、命中真实接口);e2e 截图:`design/a1-ui-v1/shots/impl/e2e/`。
|
||||
|
||||
---
|
||||
|
||||
## 4 本地运行
|
||||
|
||||
```bash
|
||||
pnpm check:dev # 确认 PG 5432 / Redis 6379
|
||||
pnpm db:reset # 迁移 + 种子(admin / lingxi2026,首次登录须改密)
|
||||
pnpm dev # API :3000 + Web :5173
|
||||
# 浏览器打开 http://127.0.0.1:5173/login
|
||||
```
|
||||
|
||||
种子账号:`admin`(超级管理员);示例成员 `chen.mo` / `lin.qing` / `zhou.an` / `zhao.yinuo` / `gao.cen` / `su.he` / `shen.zhiyuan` / `jiang.lan`;停用示例 `tang.jia` / `he.an`。初始密码统一 `lingxi2026`,首次登录强制修改。
|
||||
|
||||
---
|
||||
|
||||
## 5 已知差异与遗留
|
||||
|
||||
| 编号 | 项 | 说明 / 建议 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 数据范围六类 | 当前 `/me` 只区分 `all` 与 `department_and_children`;六类范围与 `ScopeContext` 注入按计划由 A2 完成,`ScopeGuard` 骨架已就位。 |
|
||||
| 2 | 客户转交 | `POST /users/{id}/transfer-customers` 现返回 42201「客户模块(A3)提供」;负责客户 Tab 暂为空态。A3 落地后替换。 |
|
||||
| 3 | 审计落库 | `@Audit` 目前写结构化 Pino 日志;A5 建审计表后接入持久化与检索/导出。 |
|
||||
| 4 | OpenAPI schema | 路径已导出,但请求/响应 schema 仍缺(Zod 入参无 DTO 类);建议后续用 shared Zod 生成 JSON Schema 并补 `@ApiBody/@ApiOkResponse`。前端当前直接用 shared 类型。 |
|
||||
| 5 | 记住我 | 契约暂无 `rememberMe`,登录页保留交互;如需真正 7 天会话需扩展 refresh TTL 策略。 |
|
||||
| 6 | SSO | 仅预留适配器接口,未实现(计划 M3-3)。 |
|
||||
| 7 | 对比度 | 辅助文字 `#8F959E` 白底约 2.8:1 属 A0 全局 token 问题,本轮未改;建议作为一次全局 token 调整统一处理。 |
|
||||
| 8 | CORS / e2e | API `WEB_ORIGIN` 仅允许 `http://127.0.0.1:5173`;e2e 跑在 dev 5173。若改用 preview 4173,需把该 origin 加进 `WEB_ORIGIN`。 |
|
||||
| 9 | 本地账号状态 | 联调过程中曾修改本地 admin/jiang.lan 口令并触发锁定;交付前已 `pnpm db:reset` 恢复种子(`lingxi2026`)。 |
|
||||
| 10 | 客户转交必填 | PRD 要求离职停用前必须转交名下客户;当前因 A3 未落地,停用弹窗的转交为可选项,A3 之后应收紧为有客户时必填。 |
|
||||
|
||||
---
|
||||
|
||||
## 6 回滚方式
|
||||
|
||||
- 代码:整体回滚本批 commit(未提交前可直接 `git restore` 上述路径)。
|
||||
- 数据库:本批为 expand 迁移,回滚代码不需要删表;如需彻底回退,用 `prisma migrate resolve` 标记后手工 drop 新增表(仅本地)。
|
||||
- 配置:删除 `.env.local` 中新增的 JWT / 认证变量,API 启动会因校验失败而阻止启动(有意的 fail-fast)。
|
||||
|
||||
---
|
||||
|
||||
## 7 下一步
|
||||
|
||||
1. **A2 角色权限与数据范围**:角色 CRUD、权限点树、六类数据范围、SoD 校验、`ScopeContext` 全链路注入(当前 identity 已留接口与守卫骨架)。
|
||||
2. **A3 客户与行业目录**:替换客户转交占位、负责客户 Tab 接真实数据、停用前转交必填。
|
||||
3. **A5 审计**:`@Audit` 落库、检索与导出,A1 e2e 增加审计断言。
|
||||
4. **工程补强**:OpenAPI schema 由 shared Zod 生成;全局对比度 token 决策;`rememberMe` 与 refresh TTL 策略。
|
||||
Reference in new issue
Block a user