|
|
@@ -0,0 +1,219 @@
|
|
|
+# 故事 012.015:数据统计API安全修复与路由集成
|
|
|
+
|
|
|
+## 状态
|
|
|
+Ready
|
|
|
+
|
|
|
+## 故事
|
|
|
+**作为**企业用户,
|
|
|
+**我希望**数据统计API具备完整的企业数据隔离安全性并正确注册到server路由,
|
|
|
+**以便**确保企业数据安全隔离,防止越权访问,并保证API实际可用。
|
|
|
+
|
|
|
+**史诗上下文**:此故事是史诗012(用人方小程序API补充与数据库扩展)的第15个故事,解决故事012.004已实现的数据统计API中存在的安全漏洞和路由集成缺失问题,确保企业用户数据安全隔离和API实际可用性。
|
|
|
+
|
|
|
+## 验收标准
|
|
|
+
|
|
|
+1. [ ] statistics-module所有统计API不再接受`companyId`查询参数,企业ID强制从认证用户的JWT token中获取
|
|
|
+2. [ ] order-module企业统计API(打卡统计、视频统计)同样移除`companyId`查询参数,强制从认证token获取企业ID
|
|
|
+3. [ ] statistics-module路由正确注册到server包,路径前缀为`/api/v1/yongren/statistics`
|
|
|
+4. [ ] server包添加`@d8d/allin-statistics-module`依赖,并导出`EnterpriseStatisticsRoutes`类型
|
|
|
+5. [ ] 更新故事011.005文档,修正API规范中不准确的安全要求和集成状态描述
|
|
|
+6. [ ] 所有修复通过集成测试验证,确保企业数据隔离安全要求满足
|
|
|
+
|
|
|
+## 任务 / 子任务
|
|
|
+
|
|
|
+### 任务1:修复statistics-module安全漏洞(AC:1)
|
|
|
+- [ ] **移除查询参数**:修改`allin-packages/statistics-module/src/schemas/statistics.schema.ts`
|
|
|
+ - 移除`StatisticsQuerySchema`中的`companyId`字段定义
|
|
|
+ - 创建`EnterpriseStatisticsQuerySchema`(空的object,仅用于中间件验证)
|
|
|
+- [ ] **更新路由定义**:修改`allin-packages/statistics-module/src/routes/statistics.routes.ts`
|
|
|
+ - 所有6个统计路由使用`EnterpriseStatisticsQuerySchema`作为查询参数schema
|
|
|
+ - 移除路由handler中`query.companyId || user?.companyId`的逻辑
|
|
|
+ - 改为强制使用`user?.companyId`(从`enterpriseAuthMiddleware`验证获取)
|
|
|
+ - 当`user?.companyId`为空时返回403错误(无企业权限)
|
|
|
+- [ ] **更新响应Schema**:确保所有响应中的`companyId`字段使用从认证用户获取的值
|
|
|
+- [ ] **更新服务层**:修改`allin-packages/statistics-module/src/services/statistics.service.ts`
|
|
|
+ - 确保所有统计方法严格使用传入的`companyId`参数(从认证用户获取)
|
|
|
+ - 添加企业数据隔离验证日志(可选)
|
|
|
+- [ ] **更新集成测试**:修改`allin-packages/statistics-module/tests/integration/statistics.integration.test.ts`
|
|
|
+ - 移除测试中的`companyId`查询参数传递
|
|
|
+ - 验证企业用户只能访问自己企业的数据
|
|
|
+ - 测试尝试传递`companyId`参数应被忽略或返回错误
|
|
|
+
|
|
|
+### 任务2:修复order-module企业统计API安全漏洞(AC:2)
|
|
|
+- [ ] **移除查询参数**:修改`allin-packages/order-module/src/routes/order-custom.routes.ts`
|
|
|
+ - 移除企业统计路由中的`companyId`查询参数字段
|
|
|
+ - 打卡统计路由:`checkinStatisticsRoute`(第582-587行)
|
|
|
+ - 视频分类统计路由:`videoStatisticsRoute`(第620-625行)
|
|
|
+ - 企业维度订单查询路由:`companyOrdersRoute`(第665-670行)
|
|
|
+- [ ] **更新路由handler**:修改企业专用订单自定义路由(`enterpriseOrderCustomRoutes`)
|
|
|
+ - 移除`query.companyId || user?.companyId`逻辑
|
|
|
+ - 改为强制使用`user?.companyId`
|
|
|
+ - 当`user?.companyId`为空时返回403错误
|
|
|
+- [ ] **更新服务层**:修改`allin-packages/order-module/src/services/order.service.ts`
|
|
|
+ - `getCheckinStatistics`、`getVideoStatistics`、`getCompanyOrders`方法应严格使用传入的`companyId`
|
|
|
+- [ ] **更新集成测试**:修改`allin-packages/order-module/tests/integration/order.integration.test.ts`
|
|
|
+ - 移除企业统计API测试中的`companyId`查询参数
|
|
|
+ - 验证企业用户只能访问自己企业的数据
|
|
|
+
|
|
|
+### 任务3:server包集成与路由注册(AC:3,4)
|
|
|
+- [ ] **添加依赖**:修改`packages/server/package.json`
|
|
|
+ - 在dependencies中添加`"@d8d/allin-statistics-module": "workspace:*"`
|
|
|
+- [ ] **导入路由**:修改`packages/server/src/index.ts`
|
|
|
+ - 添加导入:`import { statisticsRoutes } from '@d8d/allin-statistics-module'`
|
|
|
+- [ ] **注册路由**:在同一文件中添加企业专用统计路由注册
|
|
|
+ - 在现有企业路由注册区域(第150-154行附近)添加:
|
|
|
+ ```typescript
|
|
|
+ export const enterpriseStatisticsApiRoutes = api.route('/api/v1/yongren/statistics', statisticsRoutes)
|
|
|
+ ```
|
|
|
+- [ ] **导出类型**:在同一文件的类型导出区域(第169-172行附近)添加:
|
|
|
+ ```typescript
|
|
|
+ export type EnterpriseStatisticsRoutes = typeof enterpriseStatisticsApiRoutes
|
|
|
+ ```
|
|
|
+- [ ] **验证注册**:运行server包集成测试,验证路由正确注册
|
|
|
+ - 检查`/api/v1/yongren/statistics/disability-type-distribution`等路径可访问
|
|
|
+
|
|
|
+### 任务4:文档更新(AC:5)
|
|
|
+- [ ] **更新故事011.005文档**:修改`docs/stories/011.005.story.md`
|
|
|
+ - API规范部分:移除`companyId?`查询参数描述
|
|
|
+ - 安全要求部分:强调企业ID只能从认证token获取,不接受查询参数
|
|
|
+ - 集成状态部分:注明statistics路由已注册,API实际可用
|
|
|
+ - 客户端示例:更新为正确的安全模式
|
|
|
+- [ ] **更新史诗012文档**:修改`docs/prd/epic-012-api-supplement-for-employer-mini-program.md`
|
|
|
+ - 在故事列表中添加故事012.015
|
|
|
+ - 更新史诗进度和完成状态
|
|
|
+- [ ] **更新变更日志**:在两个文档中添加相应的变更记录
|
|
|
+
|
|
|
+### 任务5:测试验证(AC:6)
|
|
|
+- [ ] **运行statistics-module测试**:`cd allin-packages/statistics-module && pnpm test`
|
|
|
+ - 确保7个集成测试全部通过
|
|
|
+ - 验证安全修复后的测试场景
|
|
|
+- [ ] **运行order-module测试**:`cd allin-packages/order-module && pnpm test`
|
|
|
+ - 确保企业统计API测试通过
|
|
|
+ - 验证不再接受`companyId`查询参数
|
|
|
+- [ ] **运行server包测试**:`cd packages/server && pnpm test`
|
|
|
+ - 验证新路由正确注册
|
|
|
+ - 运行现有的企业API连通性测试
|
|
|
+- [ ] **端到端验证**:使用企业用户token调用统计API
|
|
|
+ - 验证无法通过查询参数访问其他企业数据
|
|
|
+ - 验证返回数据仅限当前企业关联数据
|
|
|
+
|
|
|
+## 开发笔记
|
|
|
+
|
|
|
+### 发现问题背景
|
|
|
+在验证故事011.005(数据统计功能实现)的API描述准确性时,发现以下问题:
|
|
|
+
|
|
|
+1. **安全漏洞**:statistics-module和order-module的企业统计API接受`companyId`查询参数,允许覆盖认证用户的企业ID,违反企业数据隔离安全要求。
|
|
|
+2. **路由未注册**:statistics-module虽然已实现,但未在server包中注册,导致API端点实际不可用。
|
|
|
+3. **类型缺失**:server包未导出`EnterpriseStatisticsRoutes`类型,前端无法获得类型安全的API客户端。
|
|
|
+4. **文档不准确**:故事011.005中的API描述在安全要求和集成状态方面不准确。
|
|
|
+
|
|
|
+### 安全要求参考
|
|
|
+根据故事012.004第158-162行的企业数据隔离实现细节:
|
|
|
+> **"重要安全要求:所有统计API不接受`companyId`查询参数,企业ID强制从认证用户的JWT token中获取,确保企业用户只能访问自己关联的企业数据,防止越权访问。"**
|
|
|
+
|
|
|
+当前实现违反此安全要求,必须修复。
|
|
|
+
|
|
|
+### 相关文件位置
|
|
|
+
|
|
|
+**statistics-module文件**:
|
|
|
+- `allin-packages/statistics-module/src/schemas/statistics.schema.ts` - Schema定义
|
|
|
+- `allin-packages/statistics-module/src/routes/statistics.routes.ts` - 路由定义
|
|
|
+- `allin-packages/statistics-module/src/services/statistics.service.ts` - 服务层
|
|
|
+- `allin-packages/statistics-module/tests/integration/statistics.integration.test.ts` - 集成测试
|
|
|
+
|
|
|
+**order-module文件**:
|
|
|
+- `allin-packages/order-module/src/routes/order-custom.routes.ts` - 企业统计路由定义
|
|
|
+- `allin-packages/order-module/src/services/order.service.ts` - 订单服务
|
|
|
+- `allin-packages/order-module/tests/integration/order.integration.test.ts` - 集成测试
|
|
|
+
|
|
|
+**server包文件**:
|
|
|
+- `packages/server/package.json` - 依赖配置
|
|
|
+- `packages/server/src/index.ts` - 路由注册和类型导出
|
|
|
+
|
|
|
+**文档文件**:
|
|
|
+- `docs/stories/011.005.story.md` - 前端数据统计故事
|
|
|
+- `docs/prd/epic-012-api-supplement-for-employer-mini-program.md` - 史诗012文档
|
|
|
+
|
|
|
+### 修复原则
|
|
|
+
|
|
|
+1. **最小权限原则**:企业用户只能访问自己关联的企业数据
|
|
|
+2. **安全默认原则**:默认拒绝,必须显式授权
|
|
|
+3. **防御性编程**:验证所有输入,包括认证上下文
|
|
|
+4. **向后兼容**:修复不应破坏现有API契约(响应格式不变)
|
|
|
+5. **测试覆盖**:所有修复必须有相应的测试验证
|
|
|
+
|
|
|
+### 企业数据隔离实现
|
|
|
+
|
|
|
+**认证流程**:
|
|
|
+1. 企业用户通过`enterpriseAuthMiddleware`中间件验证
|
|
|
+2. 中间件从JWT token中提取`companyId`并添加到context
|
|
|
+3. 所有企业专用API通过`c.get('user')`获取包含`companyId`的用户对象
|
|
|
+
|
|
|
+**数据过滤**:
|
|
|
+- 统计查询必须包含`company_id`过滤条件
|
|
|
+- 通过`employment_order`→`order_person`→`disabled_person`关联链过滤
|
|
|
+- 服务层验证查询结果的企业ID与用户企业ID匹配
|
|
|
+
|
|
|
+**错误处理**:
|
|
|
+- 用户无`companyId`:返回403(无企业权限)
|
|
|
+- 用户有`companyId`但无关联数据:返回空结果或404
|
|
|
+- 尝试传递`companyId`查询参数:忽略参数或返回400错误
|
|
|
+
|
|
|
+## 文件位置
|
|
|
+
|
|
|
+**statistics-module**:
|
|
|
+- `allin-packages/statistics-module/src/schemas/statistics.schema.ts`
|
|
|
+- `allin-packages/statistics-module/src/routes/statistics.routes.ts`
|
|
|
+- `allin-packages/statistics-module/src/services/statistics.service.ts`
|
|
|
+- `allin-packages/statistics-module/tests/integration/statistics.integration.test.ts`
|
|
|
+
|
|
|
+**order-module**:
|
|
|
+- `allin-packages/order-module/src/routes/order-custom.routes.ts`
|
|
|
+- `allin-packages/order-module/src/services/order.service.ts`
|
|
|
+- `allin-packages/order-module/tests/integration/order.integration.test.ts`
|
|
|
+
|
|
|
+**server包**:
|
|
|
+- `packages/server/package.json`
|
|
|
+- `packages/server/src/index.ts`
|
|
|
+
|
|
|
+**文档**:
|
|
|
+- `docs/stories/011.005.story.md`
|
|
|
+- `docs/stories/012.015.story.md`(本文件)
|
|
|
+- `docs/prd/epic-012-api-supplement-for-employer-mini-program.md`
|
|
|
+
|
|
|
+## 技术约束
|
|
|
+
|
|
|
+- **API路径规范**:企业专用统计API使用`/api/v1/yongren/statistics`前缀
|
|
|
+- **企业数据隔离**:严格遵循故事012.004定义的安全要求
|
|
|
+- **中间件依赖**:所有统计API使用`enterpriseAuthMiddleware`中间件
|
|
|
+- **类型安全**:server包必须导出`EnterpriseStatisticsRoutes`类型供前端使用
|
|
|
+- **测试要求**:所有修复必须通过集成测试验证
|
|
|
+- **文档同步**:相关文档必须同步更新,确保准确性
|
|
|
+
|
|
|
+## 测试要求
|
|
|
+
|
|
|
+### 安全测试场景
|
|
|
+1. **企业用户访问自己企业数据**:应返回正确统计结果
|
|
|
+2. **企业用户尝试传递companyId参数**:参数应被忽略,仍返回自己企业数据
|
|
|
+3. **非企业用户访问统计API**:应返回403错误
|
|
|
+4. **企业用户访问不存在的企业ID**:应返回空结果或404
|
|
|
+5. **不同企业用户访问同一API**:应返回各自企业的数据
|
|
|
+
|
|
|
+### 集成测试场景
|
|
|
+1. **路由注册验证**:验证`/api/v1/yongren/statistics/*`路径可访问
|
|
|
+2. **类型导出验证**:验证`EnterpriseStatisticsRoutes`类型可从server包导入
|
|
|
+3. **前端客户端集成**:验证前端可使用正确类型创建API客户端
|
|
|
+4. **端到端流程**:企业登录→获取token→调用统计API→返回正确数据
|
|
|
+
|
|
|
+### 性能测试场景
|
|
|
+1. **大数据量统计查询**:验证查询性能不受安全修复影响
|
|
|
+2. **并发访问**:多个企业用户同时访问统计API
|
|
|
+
|
|
|
+## 变更日志
|
|
|
+
|
|
|
+| 日期 | 版本 | 描述 | 作者 |
|
|
|
+|------|------|------|------|
|
|
|
+| 2025-12-23 | 1.0 | 初始创建(数据统计API安全修复与路由集成故事) | Claude Code |
|
|
|
+
|
|
|
+## 开发代理记录
|
|
|
+*此部分由开发代理在实施过程中填充*
|