# iam 对接契约

> 负责人：ai06
> 关联：[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 裁决依据：[coord-final-decisions](../../coord-final-decisions.md) I1-I8、[president-final-rulings](../../president-final-rulings.md) §2.15/§2.16/§5.5

---

## §1 我提供什么（对外接口）

> **双入口策略**（president §2.16）：REST 供 gateway 透传 + admin-portal 直连，gRPC 供 BFF 聚合调用。同一 Application Service 同时被 REST Controller + gRPC Controller 调用，业务逻辑不重复。gateway 保持 HTTP 透传（不改为 gRPC 客户端）。

### 1.1 gRPC 接口（供 BFF 聚合调用）

> 端口 50052，P2 即启用（I1 裁决）。⚠️ 当前 iam.proto 仅 4 RPC，待 coord 补全至 12 RPC（ISSUE-005）。

| Service    | RPC                     | 请求                           | 响应                         | 端口  | 消费方                                   |
| ---------- | ----------------------- | ------------------------------ | ---------------------------- | ----- | ---------------------------------------- |
| IamService | Register                | RegisterRequest                | AuthResponse                 | 50052 | api-gateway（REST 透传）/ admin-portal   |
| IamService | Login                   | LoginRequest                   | AuthResponse                 | 50052 | api-gateway（REST 透传）/ admin-portal   |
| IamService | RefreshToken            | RefreshTokenRequest            | TokenPair                    | 50052 | api-gateway（REST 透传）                 |
| IamService | Logout                  | LogoutRequest                  | LogoutResponse               | 50052 | api-gateway（REST 透传）                 |
| IamService | GetUserInfo             | GetUserInfoRequest             | UserInfo                     | 50052 | teacher-bff / student-bff / parent-bff  |
| IamService | BatchGetUsers           | BatchGetUsersRequest           | BatchGetUsersResponse        | 50052 | teacher-bff / admin-portal               |
| IamService | GetEffectivePermissions | GetEffectivePermissionsRequest | EffectivePermissionsResponse | 50052 | teacher-bff / student-bff / parent-bff  |
| IamService | GetEffectiveAccess      | GetEffectiveAccessRequest      | EffectiveAccessResponse      | 50052 | teacher-bff / student-bff / parent-bff  |
| IamService | GetEffectiveDataScope   | GetEffectiveDataScopeRequest   | DataScopeResponse            | 50052 | teacher-bff / data-ana                   |
| IamService | GetViewports            | GetViewportsRequest            | ViewportsResponse            | 50052 | teacher-bff / student-bff / parent-bff  |
| IamService | GetPublicKey            | GetPublicKeyRequest            | PublicKeyResponse            | 50052 | api-gateway（JWKS 验签）                 |
| IamService | GetChildrenByParent     | GetChildrenByParentRequest     | ChildrenResponse             | 50052 | parent-bff                               |

### 1.2 HTTP 端点（供 gateway 透传 + admin-portal 直连）

> REST 与 gRPC 双入口并存（president §2.16）。REST 路径统一加 `/v1` 前缀（I7 裁决）。gateway 路由 `/iam/v1/*` → iam 服务 `/v1/iam/*`（透传不改路径）。

| Method | Path                              | 权限              | 说明                                   | 消费方               |
| ------ | --------------------------------- | ----------------- | -------------------------------------- | -------------------- |
| POST   | `/iam/v1/register`                | 公开              | 注册 + 自动分配 teacher 角色           | api-gateway / admin  |
| POST   | `/iam/v1/login`                   | 公开              | 登录，返回 accessToken + refreshToken  | api-gateway          |
| POST   | `/iam/v1/refresh`                 | 公开              | 刷新令牌（轮换 + 旧 token 黑名单）     | api-gateway          |
| POST   | `/iam/v1/logout`                  | `IAM_USER_READ`   | 登出（refresh token 加黑名单）         | api-gateway          |
| GET    | `/iam/v1/me`                      | `IAM_USER_READ`   | 当前用户信息                           | api-gateway / admin  |
| GET    | `/iam/v1/viewports`               | `IAM_USER_READ`   | 当前用户视口（L1 导航）                | api-gateway          |
| GET    | `/iam/v1/permissions/effective`   | `IAM_USER_READ`   | 当前用户有效权限（I8 统一路径）        | api-gateway          |
| GET    | `/iam/v1/.well-known/jwks.json`   | 公开              | RS256 公钥 JWK Set（Gateway 拉取验签） | api-gateway          |
| GET    | `/iam/v1/children`                | `IAM_USER_READ`   | 当前家长的孩子列表（I6 裁决）          | api-gateway / parent |
| GET    | `/iam/v1/roles`                   | `IAM_ROLE_MANAGE` | 角色列表（管理端）                     | admin-portal         |
| GET    | `/iam/v1/permissions`             | `IAM_ROLE_MANAGE` | 权限点列表（管理端）                   | admin-portal         |
| GET    | `/healthz`                        | 无                | liveness                               | k8s / 监控           |
| GET    | `/readyz`                         | 无                | readiness（5 依赖检查）                | k8s / 监控           |
| GET    | `/metrics`                        | 无                | Prometheus 指标                        | Prometheus           |

### 1.3 GraphQL schema（如 BFF）

不适用。iam 是业务服务，不暴露 GraphQL（GraphQL 由 BFF 层提供）。

### 1.4 Kafka 事件发布（如有）

| Topic                 | Event                                                                     | 消费方                                                    |
| --------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------- |
| edu.iam.user.events   | UserEvent（action: created/updated/deleted/role_changed）                 | core-edu / msg / push-gateway / teacher-bff / student-bff |
| edu.iam.role.events   | RoleEvent（action: created/updated）                                      | core-edu / teacher-bff                                    |
| edu.iam.audit.created | AuditEvent（action: create/update/delete/login/logout/permission_change） | admin-portal                                              |

### 1.5 错误码前缀

`IAM_`（如 IAM_UNAUTHORIZED、IAM_USER_NOT_FOUND、IAM_PERMISSION_DENIED）

---

## §2 我消费什么（依赖上游）

### 2.1 gRPC 调用（同步）

无。iam 是身份根服务，不依赖其他业务服务。

### 2.2 Kafka 事件订阅（异步）

无。

### 2.3 HTTP 调用（如有）

无。

---

## §3 就绪信号

### 3.1 我依赖的上游就绪标志

iam 是身份根服务，无业务上游依赖。但依赖以下基础设施契约（coord 提供）：

| 依赖项 | 提供方 | 就绪标志 | 状态 |
| ------ | ------ | -------- | ---- |
| iam.proto 补全至 12 RPC | coord | proto 含 12 RPC + 全部 message | ❌ 仅 4 RPC（ISSUE-005） |
| events.proto 补全 UserEvent/RoleEvent/AuditEvent | coord | proto 含 3 个 message | ❌ 缺失（ISSUE-002） |
| shared-ts Outbox 工具包 | coord | outbox.service.ts 可导入 | ✅ 已就绪 |

### 3.2 我的就绪标志（供下游消费）

- [ ] iam gRPC 50052 启用（HealthService.Check 返回 SERVING）
- [ ] IamService 12 RPC 全部可调用（Register/Login/RefreshToken/Logout/GetUserInfo/BatchGetUsers/GetEffectivePermissions/GetEffectiveAccess/GetEffectiveDataScope/GetViewports/GetPublicKey/GetChildrenByParent）
- [ ] IamService.GetPublicKey 可用（返回 RS256 PEM 公钥，供 api-gateway 验签）
- [ ] IamService.GetChildrenByParent 可用（供 parent-bff 查孩子列表）
- [ ] edu.iam.user.events / edu.iam.role.events / edu.iam.audit.created topic 可发布
- [ ] JWT RS256 签发链路打通（access_token 15min + refresh_token 7day 轮换）
- [ ] /iam/v1/* REST 端点可用（供 gateway 透传 + admin-portal 直连，双入口策略）

---

## §4 Mock 策略

### 4.1 我提供的 mock

在 iam 真实服务就绪前，为下游（api-gateway / 各 BFF / admin-portal）提供以下 mock（双入口）：

- **gRPC mock**（供 BFF）：使用 grpc-mock 拦截 50052 端口，Register/Login 返回固定 AuthResponse（user.id="mock-user-001", tokens.access_token="mock-access-token"）
- **REST mock**（供 gateway / admin-portal）：MSW 或 express mock 拦截 /iam/v1/* 路径，返回与 gRPC mock 一致的结构
- **GetPublicKey mock**：返回固定 RS256 公钥 PEM（与 mock 私钥配对），供 api-gateway 验签 mock JWT
- **GetChildrenByParent mock**：返回固定 ChildInfo 列表（2 个孩子）
- **JWKS mock**：GET /iam/v1/.well-known/jwks.json 返回固定 JWK Set
- **Kafka mock**：iam 服务就绪前不发布真实事件，下游订阅方使用本地 stub

### 4.2 我消费的 mock

不适用（iam 是身份根服务，无业务上游依赖）。
