# IAM 治理后台 — PRD（IAM Admin Console）

**状态**：📝 规划中 | **创建**：2026-04-25 | **关联 PR**：#138（基础设施）/ 待开（本规划）

## 1. 背景

PR #138 落地了 IAM 四层模型 + 9 个治理服务的**后端基础设施**，但前端管理界面不完整：

| 后端能力 | 后端 service 状态 | 前端管理 UI |
|---|---|---|
| L4 DataScope（数据权限） | ✅ DataScopeService 决议运行时 | ❌ **零 UI**，新建/绑定要跑 SQL/seed |
| L4 FieldPermission（字段级脱敏） | ✅ FieldPermissionService.maskFields | ❌ **零 UI**，规则改要跑 SQL |
| EmergencyBypass（紧急豁免） | ✅ EmergencyBypassService | ❌ **零 UI**，运维 SSH 调 Redis CLI |
| iam_audit_log（IAM 审计） | ✅ IamAuditService 写入 | ❌ **零 UI**，要查只能 psql |
| 委托（Delegation） | ✅ + Controller | ⚠️ 已有 page 但 UX 粗糙（UUID 输入框 / prompt 弹窗 / 单视角） |
| Access Review | ✅ + Controller | ⚠️ 已有 page 但用 prompt/confirm 取 comment |

## 2. 目标

1. **管理闭环**：admin 在 UI 完成所有 IAM 治理动作（DataScope / FieldPermission / 委托 / 复核 / 豁免 / 审计查看）
2. **UX 一致性**：现有委托/复核页升级到项目其他模块的 UX 标准（Dialog / toast / UserSelector / displayName）
3. **可观测性**：iam_audit_log 有专门查询页，admin 操作可追溯

## 3. 非目标（不做）

- DataScope 自定义规则编辑（CUSTOM 永久 NotImplementedException，规则 §5.3.4）
- 自助委托 UI（普通员工自己发起委托）—— 当前 admin 代发起
- MFA 配置 UI（MfaService 引入但未挂 login，留待后续）
- IAM 审计完整性校验 UI（hash chain 验证背景任务，不暴露给 admin）

## 4. 范围拆分（两个 PR）

### Part A：现有页面 UX 升级（PR-A，约 5 文件）

不依赖新后端，**立即可做**。

#### A1. `(modules)/organization/delegations/page.tsx`

- ❌ `<input type="text" placeholder="uuid">` 让 admin 粘 UUID
- ✅ 用 `UserSelector` 替换（已有组件）
- ❌ 单一列表把 from/to 混在一起 + UUID slice(0,8) 显示
- ✅ 拆 Tab："我发起的 / 我收到的 / 全部（admin 视角）"
- ✅ 后端 `listMyDelegations` 已 join user，前端展示 `displayName`
- ❌ 自造 `<div className="fixed inset-0 z-50 ...">` modal
- ✅ 用 shadcn `<Dialog>` 替换

#### A2. `(modules)/organization/access-review/page.tsx`

- ❌ `prompt(...)` 取 comment / `confirm(...)` 二次确认
- ✅ 改成 Dialog + textarea（comment 必填校验）
- ❌ `alert(...)` 报错
- ✅ 改成 toast

### Part B：新建 IAM Admin 后台（PR-B，约 25 文件）

新模块路径：`frontend/src/app/(modules)/iam-admin/`
权限码：`iam_admin:read` / `iam_admin:manage`（外加 `system:admin` OR fallback）

#### B1. 后端 5 个新 Controller + DTO（强制 class-validator）

```
backend/src/modules/organization/iam-governance/
├── data-scopes.controller.ts          ← list/create/update/delete + listByRole
├── role-data-scopes.controller.ts     ← bind/unbind/listMatrix
├── field-permissions.controller.ts    ← CRUD
├── emergency-bypass.controller.ts     ← listActive/enable/disable
└── iam-audit.controller.ts            ← query with filter (actor/action/resource/timeRange)
```

每个 controller：
- 挂 `@RequirePermissions('iam_admin:manage')`（read 端点用 `iam_admin:read`）
- DTO 完整 class-validator 装饰（避开 PR #138 踩过的 whitelist 剥光坑）
- service 已有的方法直接复用，缺的补到对应 service

#### B2. 前端 5 个页面

```
frontend/src/app/(modules)/iam-admin/
├── layout.tsx                  ← 模块导航 + PermissionGuard(['iam_admin:*', 'system:admin'])
├── page.tsx                    ← Dashboard（统计 + 入口卡片）
├── data-scopes/page.tsx        ← DataScope 定义 CRUD
├── role-data-scopes/page.tsx   ← 矩阵：角色 × resource → DataScope
├── field-permissions/page.tsx  ← 字段级权限 CRUD
├── emergency-bypass/page.tsx   ← 豁免启用/查看/禁用
└── audit-logs/page.tsx         ← iam_audit_log 查询过滤
```

#### B3. 前端共享组件

```
frontend/src/components/iam/
├── ResourceCombobox.tsx        ← 从 data-scope-resources 常量列出（含 LEGACY 标识）
├── ScopeTypeSelector.tsx       ← ALL/ORGANIZATION/DEPARTMENT_TREE/SELF（CUSTOM 禁选）
├── RolePicker.tsx              ← 现有 roles 列表选一个
└── ActionBadge.tsx             ← audit_log action 染色（CREATE 蓝/UPDATE 黄/DELETE 红/ADMIN_BYPASS 灰）
```

#### B4. 服务层

```
frontend/src/services/api/iam-admin.ts  ← 5 个 controller 的 fetch 封装
```

#### B5. i18n

```
frontend/src/locales/iamAdmin/{zh,en}.ts  ← 注册到 locales/index.ts
```

## 5. 数据契约（后端新 endpoint）

### B1.1 DataScope CRUD

```
GET    /api/v1/iam/data-scopes              → list all
POST   /api/v1/iam/data-scopes              → create { code, name, scopeType, rules? }
PATCH  /api/v1/iam/data-scopes/:id          → update partial
DELETE /api/v1/iam/data-scopes/:id          → delete (built-in 拒绝)
```

### B1.2 Role × DataScope 绑定

```
GET    /api/v1/iam/role-data-scopes?roleId=  → 该角色所有绑定（按 resource 分组）
POST   /api/v1/iam/role-data-scopes          → { roleId, dataScopeId, resource }
DELETE /api/v1/iam/role-data-scopes/:id      → unbind
```

### B1.3 FieldPermission CRUD

```
GET    /api/v1/iam/field-permissions?resource=&roleId=
POST   /api/v1/iam/field-permissions         → { resource, field, roleId?, dataScopeId?, accessLevel }
PATCH  /api/v1/iam/field-permissions/:id
DELETE /api/v1/iam/field-permissions/:id
```

### B1.4 EmergencyBypass

```
GET    /api/v1/iam/emergency-bypass          → list active（扫 Redis 'emergency_bypass:*'）
POST   /api/v1/iam/emergency-bypass          → { endpoint, reason, ttlSec }
DELETE /api/v1/iam/emergency-bypass/:endpoint → 解除
```

### B1.5 IamAuditLog 查询

```
GET /api/v1/iam/audit-logs?actor=&action=&resource=&from=&to=&page=&pageSize=
```

## 6. 实施步骤

### Step 1：Part A 委托/复核 UX 升级（不依赖新后端）

| 子步 | 内容 | 文件数 |
|---|---|---|
| 1.1 | delegations 接 UserSelector + Dialog + Tab + displayName | 1 |
| 1.2 | access-review 接 Dialog 取 comment | 1 |
| 1.3 | 后端 `listRelatedToUser` 加 user include（让前端能 join displayName） | 1 |
| 1.4 | i18n 补 zh/en（已有 `iamGovernance` locale 扩） | 2 |
| 1.5 | MCP 验证两页 UX 切换 | — |

→ Commit + Push + PR-A → merge

### Step 2：Part B 后端 5 个 Controller + DTO + 集成测试

| 子步 | 内容 | 文件数 |
|---|---|---|
| 2.1 | DataScopeController + DTO | 2 |
| 2.2 | RoleDataScopeController + DTO | 2 |
| 2.3 | FieldPermissionController + DTO | 2 |
| 2.4 | EmergencyBypassController + DTO | 2 |
| 2.5 | IamAuditController + DTO | 2 |
| 2.6 | iam-governance.module.ts 注册 5 个 controller | 1 |
| 2.7 | seed 加 `iam_admin:read` / `iam_admin:manage` 权限码 + 默认绑给 Administrator | 1 |
| 2.8 | 集成测试套件 5 个文件（各 4-5 项核心 case） | 5 |

→ 跑 L1 全量回归确认无回归 → Commit

### Step 3：Part B 前端

| 子步 | 内容 | 文件数 |
|---|---|---|
| 3.1 | services/api/iam-admin.ts 5 套 fetch | 1 |
| 3.2 | components/iam/{ResourceCombobox, ScopeTypeSelector, RolePicker, ActionBadge} | 4 |
| 3.3 | (modules)/iam-admin/layout.tsx + page.tsx Dashboard | 2 |
| 3.4 | data-scopes/page.tsx | 1 |
| 3.5 | role-data-scopes/page.tsx（矩阵 UI） | 1 |
| 3.6 | field-permissions/page.tsx | 1 |
| 3.7 | emergency-bypass/page.tsx | 1 |
| 3.8 | audit-logs/page.tsx | 1 |
| 3.9 | locales/iamAdmin/{zh,en}.ts + index 注册 | 3 |
| 3.10 | 顶部导航加 IAM Admin 入口 | 1 |
| 3.11 | MCP 验主路径（admin 看到导航 / 非 admin 403） | — |

→ Commit + Push + PR-B → merge

### Step 4：测试 + 自检

- L0 静态契约（contract-check / assert-access-check / data-scope-resources-check）
- L1 全量集成回归（含 5 个新套件）
- 后端 build / 前端 build
- backend-main skill 是否需要补章节（IAM Admin 模块自身能做 admin 事？）

## 7. 风险 & 决策

### R1：DataScope `rules` 字段是 Json，admin 怎么编辑？

- 现在 ALL/ORGANIZATION/DEPARTMENT_TREE/SELF 都不需要 `rules`（决议逻辑硬编码）
- CUSTOM 永久 NotImplemented
- **决策**：UI 不暴露 rules 编辑；只支持 4 种 built-in scopeType。`rules` 留作未来扩展。

### R2：FieldPermission 的 `accessLevel` 取值

- 当前 schema：`FullText / Masked / Hidden`
- **决策**：UI 用 select + 各 level 的说明 tooltip

### R3：EmergencyBypass 列表如何拿？

- Redis 没原生 listAll，要 SCAN `emergency_bypass:*`
- **决策**：Service 加 `listActive(): BypassInfo[]`，Controller 暴露
- 注意：SCAN 在大库上慢，本场景上限 < 100 条 endpoint，可接受

### R4：审计页查询性能

- iam_audit_log 没有 actor/timestamp 复合 index
- **决策**：本 PR 加 index migration（actor / timestamp 各自 + (resource, action, timestamp)）

### R5：iam_admin 权限码与现有 system:admin 关系

- system:admin 是顶级 OR fallback；iam_admin 是细粒度
- **决策**：所有 iam-admin controller 用 `@RequirePermissions('iam_admin:manage')`，但 `isAdministrator(user)` bypass 自动放行 admin

## 8. 提交计划

预计 **2 个 PR**：

| PR | 范围 | 估算行 |
|---|---|---|
| PR-A | Part A（现有页面 UX） | ~400 行 |
| PR-B | Part B（新模块 + 后端 endpoint） | ~3000 行 |

每 PR 跟前面 #138 / #139 / #141 一样走 dance 合 develop。

## 9. 完成标准

- [ ] Part A 两页 UX 升级到与 Members/Departments 等模块一致
- [ ] Part B 5 个后端 Controller 完整 class-validator + 集成测试 ≥ 4 项/套件
- [ ] Part B 5 个前端页面 admin 能完整 CRUD
- [ ] L1 集成全过 / 前后端 build 全过 / L0 静态契约全过
- [ ] backend-main skill 同步更新（如有新模式抽离）
- [ ] PR 描述 + 部署须知（无）
