Files
junhong_cmp_fiber/openspec/specs/account-permission-check/spec.md
huang 80f560df33
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s
refactor(account): 统一账号管理API、完善权限检查和操作审计
- 合并 customer_account 和 shop_account 路由到统一的 account 接口
- 新增统一认证接口 (auth handler)
- 实现越权防护中间件和权限检查工具函数
- 新增操作审计日志模型和服务
- 更新数据库迁移 (版本 39: account_operation_log 表)
- 补充集成测试覆盖权限检查和审计日志场景
2026-02-02 17:23:20 +08:00

128 lines
6.2 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.
# 账号管理权限检查规格
## ADDED Requirements
### Requirement: 三层越权防护架构
系统 SHALL 实现三层越权防护机制,确保账号管理操作的安全性。
#### Scenario: 路由层中间件拦截企业账号
- **WHEN** 企业账号user_type=4访问账号管理接口/api/admin/accounts/*
- **THEN** 中间件应返回 403 错误:"无权限访问账号管理功能"
#### Scenario: Service层权限检查成功
- **WHEN** 代理账号创建自己店铺的账号
- **THEN** CanManageShop 检查应通过,账号创建成功
#### Scenario: GORM层自动过滤生效
- **WHEN** 代理账号查询账号列表
- **THEN** GORM Callback 应自动添加 `shop_id IN (当前店铺+下级店铺)` 过滤条件
### Requirement: 代理账号只能管理自己店铺及下级店铺的账号
系统 SHALL 验证代理账号对目标店铺的管理权限,禁止跨店铺越权操作。
#### Scenario: 代理创建自己店铺的账号成功
- **WHEN** 代理账号shop_id=100创建 shop_id=100 的账号
- **THEN** 权限检查通过,账号创建成功
#### Scenario: 代理创建下级店铺的账号成功
- **WHEN** 代理账号shop_id=100下级101,102创建 shop_id=101 的账号
- **THEN** GetSubordinateShopIDs 返回 [100,101,102],权限检查通过
#### Scenario: 代理创建其他店铺的账号失败
- **WHEN** 代理账号shop_id=100创建 shop_id=200 的账号
- **THEN** CanManageShop 返回错误:"无权限管理该店铺的账号",创建失败
#### Scenario: 代理创建平台账号失败
- **WHEN** 代理账号尝试创建 user_type=2 的平台账号
- **THEN** Service 层检查返回错误:"无权限创建平台账号",创建失败
### Requirement: 平台账号和超级管理员可以管理所有账号
系统 SHALL 允许平台账号和超级管理员跳过所有权限检查,管理所有账号。
#### Scenario: 平台账号创建任意类型账号
- **WHEN** 平台账号user_type=2创建代理账号user_type=3, shop_id=100
- **THEN** 权限检查跳过,账号创建成功
#### Scenario: 超级管理员创建任意类型账号
- **WHEN** 超级管理员user_type=1创建任意类型账号
- **THEN** 权限检查跳过,账号创建成功
#### Scenario: 平台账号查询所有账号
- **WHEN** 平台账号调用账号列表接口
- **THEN** GORM Callback 跳过过滤,返回所有账号
### Requirement: 企业账号禁止访问账号管理接口
系统 SHALL 禁止企业账号访问所有账号管理接口。
#### Scenario: 企业账号创建账号失败(路由层拦截)
- **WHEN** 企业账号user_type=4调用 POST /api/admin/accounts/enterprise
- **THEN** 路由层中间件返回 403 错误:"无权限访问账号管理功能"
#### Scenario: 企业账号更新账号失败Service层拦截
- **WHEN** 企业账号绕过路由层,直接调用 AccountService.Update
- **THEN** Service 层返回 403 错误:"企业账号不允许更新账号"
### Requirement: 统一错误返回防止信息泄露
系统 SHALL 在越权访问时统一返回模糊错误消息,防止攻击者判断资源是否存在。
#### Scenario: 查询不存在的账号返回模糊错误
- **WHEN** 用户查询不存在的账号 ID
- **THEN** 返回 403 错误:"无权限操作该资源或资源不存在"
#### Scenario: 查询越权的账号返回相同错误
- **WHEN** 代理账号shop_id=100查询 shop_id=200 的账号
- **THEN** 返回 403 错误:"无权限操作该资源或资源不存在"(与不存在的错误消息相同)
### Requirement: CanManageShop 权限检查函数
系统 SHALL 提供 CanManageShop 函数验证用户对目标店铺的管理权限。
#### Scenario: 验证代理对自己店铺的权限
- **WHEN** 调用 CanManageShop(ctx, 100, shopStore) 且当前用户 shop_id=100
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对下级店铺的权限
- **WHEN** 调用 CanManageShop(ctx, 101, shopStore) 且当前用户 shop_id=100下级包含 101
- **THEN** GetSubordinateShopIDs 返回 [100,101,102],返回 nil有权限
#### Scenario: 验证代理对其他店铺的权限失败
- **WHEN** 调用 CanManageShop(ctx, 200, shopStore) 且当前用户 shop_id=100
- **THEN** 返回错误:"无权限管理该店铺的账号"
#### Scenario: 验证平台账号自动通过
- **WHEN** 调用 CanManageShop(ctx, 200, shopStore) 且当前用户 user_type=2平台
- **THEN** 不调用 GetSubordinateShopIDs直接返回 nil有权限
### Requirement: CanManageEnterprise 权限检查函数
系统 SHALL 提供 CanManageEnterprise 函数验证用户对目标企业的管理权限。
#### Scenario: 验证平台账号管理任意企业
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且当前用户 user_type=2
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对归属企业的权限
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=100当前用户 shop_id=100
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对下级店铺企业的权限
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=101当前用户 shop_id=100下级包含 101
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对其他店铺企业的权限失败
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=200当前用户 shop_id=100
- **THEN** 返回错误:"无权限管理该企业的账号"
### Requirement: 权限检查性能优化
系统 SHALL 使用 Redis 缓存优化权限检查性能,确保 API 响应时间 < 200ms。
#### Scenario: GetSubordinateShopIDs 命中缓存
- **WHEN** 调用 GetSubordinateShopIDs(ctx, 100) 且缓存存在
- **THEN** 从 Redis 读取缓存,不查询数据库,耗时 < 5ms
#### Scenario: GetSubordinateShopIDs 缓存未命中
- **WHEN** 调用 GetSubordinateShopIDs(ctx, 100) 且缓存不存在
- **THEN** 递归查询数据库,写入 Redis 缓存30分钟返回结果
#### Scenario: 权限检查总耗时 < 10ms
- **WHEN** 执行完整权限检查(包含 GetSubordinateShopIDs
- **THEN** 总耗时 < 10ms缓存命中时 < 5ms