Files
iam-service/docs/SCALAR_GUIDE.md
2026-01-31 17:23:56 +08:00

513 lines
14 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.

# IAM Service — Scalar 调用顺序指南v0.1.0
## Authentication认证方式
本服务使用 **JWT Bearer Token**
- 登录成功后拿到 `access_token`
- 后续请求在 Header 中带:
- `Authorization: Bearer <access_token>`
- 租户上下文:
- 保护接口默认从 Token claim 的 `tenant_id` 推导租户
- 可选兼容 `X-Tenant-ID: <uuid>`,若同时提供 Header 与 Token则必须一致否则返回 403
访问令牌JWT`tenant_id/user_id/roles/permissions` 外,还包含:
- `apps`:租户已开通应用列表(如 `["cms","tms"]`
- `apps_version`:租户 enabled_apps 版本号(用于客户端判断是否需要刷新会话)
## OpenAPI/Scalar 可配置参数(环境变量)
为方便本地调试与演示,服务会在生成 OpenAPI 文档时,将部分 Header 参数的示例值按环境变量动态注入(影响 `/scalar` 展示,不影响服务鉴权逻辑):
- `IAM_DOCS_DEFAULT_TENANT_ID`:默认租户 ID用于 `X-Tenant-ID` Header 的 example
- 未设置时默认:`11111111-1111-1111-1111-111111111111`
- 可选强制:`IAM_DOCS_REQUIRE_TENANT_ID=1`(未设置则启动时直接失败)
- `IAM_DOCS_DEFAULT_TOKEN`:默认 Token用于 `Authorization` Header 的 example
- 可填写裸 JWT 或 `Bearer <jwt>`;裸 JWT 会自动补齐 `Bearer `
- 未设置时默认:`Bearer <access_token>`
- 可选强制:`IAM_DOCS_REQUIRE_TOKEN=1`(未设置则启动时直接失败)
## 通用响应结构
成功响应:
```json
{ "code": 0, "message": "Success|Created|Accepted", "data": {}, "trace_id": null }
```
错误响应(示例):
```json
{ "code": 20006, "message": "Missing authorization header", "details": null, "trace_id": null }
```
常见错误码(节选):
- 20006缺少必要 Header如 Authorization
- 20003无权限403
- 20005账号或密码错误
- 30000请求参数错误400
- 30002资源不存在404
- 30003资源冲突409
- 40000请求过于频繁429
- 10001数据库错误500
## Step-by-step可复制流程
### Step -1数据库初始化 / 迁移(开发/测试/生产)
本服务新增了“租户已开通应用enabled_apps”与“平台超级管理员SuperAdmin”能力对应数据库新增表
- `apps`
- `tenant_entitlements`
- `tenant_enabled_apps_history`
推荐使用版本化迁移脚本初始化与升级:
- 执行迁移:`scripts/db/migrate.sh`
- 执行校验:`scripts/db/verify.sh`
- 回滚(按版本):`scripts/db/rollback.sh`
本仓库同时保留开发用的一键重建脚本:`scripts/db/rebuild_iam_db.sh`(会清库重建,不适合生产)。
### Step 0创建租户可选
**POST** `/tenants/register`
- Tag`Tenant`
- Header
- 二次验证(可选但建议生产启用):
- 若设置环境变量 `IAM_SENSITIVE_ACTION_TOKEN`,则必须传 Header`X-Sensitive-Token: <token>`,否则返回 403。
- Body
```json
{ "name": "Tenant A", "config": { "theme": { "primary": "#1d4ed8" } } }
```
成功201`data.id` 取出租户 ID
```json
{ "code": 0, "message": "Created", "data": { "id": "<tenant_id>", "name": "Tenant A", "status": "active", "config": {} }, "trace_id": null }
```
下一步依赖:`tenant_id`(用于注册/登录时的 `X-Tenant-ID`)。
### Step 0.1:平台超级管理员(可选,但推荐先完成)
数据库已内置平台租户Platform tenant
- `tenant_id = 00000000-0000-0000-0000-000000000001`
在平台租户下注册首个用户,将自动获得平台级权限(用于管理各租户 enabled_apps
**POST** `/auth/register`
- Tag`Auth`
- Header`X-Tenant-ID: 00000000-0000-0000-0000-000000000001`
- Body
```json
{ "email": "superadmin@example.com", "password": "securePassword123" }
```
### Step 1注册用户
**POST** `/auth/register`
- Tag`Auth`
- 必需 Header`X-Tenant-ID: <tenant_id>`
- Body
```json
{ "email": "user@example.com", "password": "securePassword123" }
```
成功201`data.id` 取出 `user_id`(后续可用于用户管理接口):
```json
{ "code": 0, "message": "Created", "data": { "id": "<user_id>", "email": "user@example.com" }, "trace_id": null }
```
### Step 2登录获取访问令牌Authentication 入口)
**POST** `/auth/login`
- Tag`Auth`
- 必需 Header`X-Tenant-ID: <tenant_id>`
- Body
```json
{ "email": "user@example.com", "password": "securePassword123" }
```
成功200`data.access_token` 取出访问令牌:
```json
{ "code": 0, "message": "Success", "data": { "access_token": "<jwt>", "refresh_token": "<opaque>", "token_type": "Bearer", "expires_in": 900 }, "trace_id": null }
```
下一步依赖:`access_token`
(可选)使用 refresh_token 自动续期:
- **POST** `/auth/refresh`
- Tag`Auth`
- Header
- Body
```json
{ "refresh_token": "<opaque>" }
```
### Step 3获取当前租户信息Tenant
**GET** `/tenants/me`
- Tag`Tenant`
- 必需 Header`Authorization: Bearer <access_token>`
- 可选 Header`X-Tenant-ID: <tenant_id>`(如提供必须与 token tenant_id 一致)
成功200
```json
{ "code": 0, "message": "Success", "data": { "id": "<tenant_id>", "name": "Tenant A", "status": "active", "config": {} }, "trace_id": null }
```
### Step 4查看当前用户权限Me
**GET** `/me/permissions`
- Tag`Me`
- 必需 Header`Authorization: Bearer <access_token>`
成功200
```json
{ "code": 0, "message": "Success", "data": ["tenant:read","tenant:write"], "trace_id": null }
```
下一步依赖:确认具备目标权限(例如 `user:read` / `role:read`)。
### Step 4.1平台层设置租户已开通应用SuperAdmin
该能力仅允许拥有平台级权限的用户调用:
- `iam:tenant:enabled_apps:read`
- `iam:tenant:enabled_apps:write`
#### 4.1.1 查询某租户 enabled_apps
**GET** `/platform/tenants/{tenant_id}/enabled-apps`
- Tag`Tenant`
- Header`Authorization: Bearer <access_token>`(平台租户下登录得到的 token
#### 4.1.2 设置某租户 enabled_apps全量覆盖幂等
**PUT** `/platform/tenants/{tenant_id}/enabled-apps`
- Tag`Tenant`
- Header`Authorization: Bearer <access_token>`
- Body
```json
{ "enabled_apps": ["cms", "tms"], "expected_version": 0 }
```
说明:
- `expected_version` 可选,用于并发控制;不匹配会返回 409。
- 登录签发 token 时会自动把 `apps/apps_version` 注入到 JWT并对 `permissions` 按 enabled_apps 过滤。
- `enabled_apps` 必须是“应用注册表 apps 表”中存在且 `status=active` 的应用 ID若传入未知/已下线应用(例如 `dms`),接口会返回 400。
enabled_apps 维护建议:
- 新增应用:通过数据库迁移向 `apps(id,name,description,status)` 插入一行(`id` 推荐全小写短标识,如 `cms` / `tms`)。
- 下线应用:将 `apps.status` 置为非 `active`(例如 `disabled`),之后将无法再被设置进任何租户的 enabled_apps。
- 查询可用应用(示例 SQL`SELECT id, name, status FROM apps ORDER BY id;`
### Step 4.2:平台层 App 生命周期管理SuperAdmin
用于维护“允许的 App 注册表”,并提供应用上下线(审批 + 生效时间)能力。
权限要求(平台级):
- `iam:app:read`
- `iam:app:write`
- `iam:app:approve`
- `iam:app:delete`
#### 4.2.1 新增 App
**POST** `/platform/apps`
- Tag`App`
- Header`Authorization: Bearer <access_token>`(平台租户下登录得到的 token
- Body示例
```json
{ "id": "dms", "name": "DMS", "description": "Document Management System", "app_type": "product", "owner": "team-a" }
```
#### 4.2.2 查询 App 列表(分页/筛选/排序)
**GET** `/platform/apps?page=1&page_size=20&status=active&app_type=product&sort_by=created_at&sort_order=desc`
- Tag`App`
- Header`Authorization: Bearer <access_token>`
#### 4.2.3 更新 App 基础信息
**PATCH** `/platform/apps/{app_id}`
- Tag`App`
- Header`Authorization: Bearer <access_token>`
- Body示例
```json
{ "description": "DMS v2", "owner": "team-b" }
```
#### 4.2.4 申请 App 上下线(需要审批,可设置生效时间)
**POST** `/platform/apps/{app_id}/status-change-requests`
- Tag`App`
- Header`Authorization: Bearer <access_token>`
- Body示例立即禁用
```json
{ "to_status": "disabled", "reason": "security patch" }
```
- Body示例延迟生效
```json
{ "to_status": "disabled", "effective_at": "2026-02-01T00:00:00Z", "reason": "maintenance window" }
```
#### 4.2.5 审批上下线申请单
**GET** `/platform/app-status-change-requests?status=pending&page=1&page_size=20`
**POST** `/platform/app-status-change-requests/{request_id}/approve`
**POST** `/platform/app-status-change-requests/{request_id}/reject?reason=...`
#### 4.2.6 删除 App软删除
**DELETE** `/platform/apps/{app_id}`
- Tag`App`
- Header`Authorization: Bearer <access_token>`
- 二次验证(可选但建议生产启用):
- 若设置环境变量 `IAM_SENSITIVE_ACTION_TOKEN`,则必须同时传 Header`X-Sensitive-Token: <token>`,否则返回 403。
### Step 5列出用户User
**GET** `/users?page=1&page_size=20`
- Tag`User`
- 必需 Header`Authorization: Bearer <access_token>`
- 分页规则:
- `page` 默认 1必须 >= 1
- `page_size` 默认 20范围 1..=200
成功200
```json
{ "code": 0, "message": "Success", "data": [{ "id": "<user_id>", "email": "user@example.com" }], "trace_id": null }
```
### Step 6列出角色Role
**GET** `/roles`
- Tag`Role`
- 必需 Header`Authorization: Bearer <access_token>`
成功200
```json
{ "code": 0, "message": "Success", "data": [{ "id": "<role_id>", "name": "Admin", "description": "..." }], "trace_id": null }
```
## Role & Permission Management
本节补齐“角色Role与权限Permission管理”的完整指引适用于横向扩展到多个应用`cms` / `tms` / `iam` 等)。
### 权限模型(可扩展到十几个应用)
权限编码规范:
- `permission.code` 统一采用:`${app_code}:${resource}:${action}`
- 示例:`cms:article:publish``tms:task:assign``iam:tenant:enabled_apps:write`
- `app_code` 必须与应用注册表app_registry中的应用标识一致本项目对应表为 `apps.id`)。
- 支持通配符(需要在 `permissions.code` 中显式存储通配符权限并分配到角色):
- `cms:*:*`CMS 全权限
- `cms:article:*`:文章相关全权限
> 说明:鉴权时支持通配符匹配(例如持有 `cms:article:*` 可满足 `cms:article:publish`),但只有当该通配符权限被写入 `permissions` 并绑定到角色时才会生效。
数据库关系ER 图):
```mermaid
erDiagram
apps {
VARCHAR id PK
VARCHAR name
VARCHAR status
}
permissions {
UUID id PK
VARCHAR code
TEXT description
VARCHAR resource
VARCHAR action
}
users {
UUID id PK
UUID tenant_id FK
VARCHAR email
}
roles {
UUID id PK
UUID tenant_id FK
VARCHAR name
BOOLEAN is_system
}
role_permissions {
UUID role_id FK
UUID permission_id FK
}
user_roles {
UUID user_id FK
UUID role_id FK
}
apps ||--o{ permissions : namespaces
roles ||--o{ role_permissions : grants
permissions ||--o{ role_permissions : assigned
users ||--o{ user_roles : has
roles ||--o{ user_roles : assigned
```
应用区分方式:
- 通过 `apps`app_registry维护“允许的应用标识”并要求权限码的 `app_code``apps.id` 一致。
- 在租户侧通过 `enabled_apps` 控制某租户开通哪些应用;登录/权限查询会按 `enabled_apps` 过滤应用级权限(例如 `cms:*:*` 会在租户未开通 `cms` 时被过滤)。
### 1) 为 CMS 添加最小必要权限SQL
脚本位置:`scripts/db/migrations/0006_cms_permissions.sql`
包含权限项:
- 文章:创建、编辑、发布
- 栏目:管理
- 媒体库:管理
- 系统配置:管理
### 2) 查询权限列表(分页/搜索)
**GET** `/permissions?page=1&page_size=20&app_code=cms&search=article`
- Tag`Permission`
- Header`Authorization: Bearer <access_token>`
- 需要权限:`role:read`
### 3) 角色 CRUDRole
创建角色:
- **POST** `/roles`
查询角色详情:
- **GET** `/roles/{id}`
更新角色:
- **PATCH** `/roles/{id}`
删除角色:
- **DELETE** `/roles/{id}`
说明:
- 系统内置角色(`is_system=true`,如 `Admin`)不允许通过 API 修改/删除。
### 4) 为角色批量绑定/解绑权限
绑定权限(批量):
- **POST** `/roles/{id}/permissions/grant`
- Body
```json
{ "permission_codes": ["cms:article:create", "cms:article:publish"] }
```
解绑权限(批量):
- **POST** `/roles/{id}/permissions/revoke`
### 5) 批量给用户授予/回收角色
批量授予:
- **POST** `/roles/{id}/users/grant`
- Body
```json
{ "user_ids": ["<user_id_1>", "<user_id_2>"] }
```
批量回收:
- **POST** `/roles/{id}/users/revoke`
审计说明:
- 角色创建/更新/删除、角色-权限绑定/解绑、角色-用户授予/回收都会写入 `audit_logs`
### Step 7用户-角色绑定User
用户注册后默认无角色;通常由具备 `user:write` 的管理员进行角色分配。
#### 7.1 查询用户角色列表(需要 user:read
**GET** `/users/{id}/roles`
- Tag`User`
- Header`Authorization: Bearer <access_token>`
#### 7.2 设置用户角色(全量覆盖,幂等;需要 user:write
**PUT** `/users/{id}/roles`
- Tag`User`
- Header`Authorization: Bearer <access_token>`
- Body
```json
{ "role_ids": ["<role_id_1>", "<role_id_2>"] }
```
说明:
- `role_ids` 必须全部属于当前租户,否则返回 400。
## 限流说明Auth
- `/auth/login`:约 2 req/sburst 10同一 IP
- `/auth/register`:约 1 req/sburst 5同一 IP
- 触发后返回HTTP 429 + `code=40000`
## 密码重置User
用户自助重置(需要旧密码):
- **POST** `/users/me/password/reset`
- Tag`User`
- Header`Authorization: Bearer <access_token>`
- Body
```json
{ "current_password": "oldPassword123", "new_password": "newPassword456" }
```
租户管理员重置任意用户(生成临时密码):
- **POST** `/users/{id}/password/reset`
- Tag`User`
- Header`Authorization: Bearer <access_token>`
- 权限:需要 `user:password:reset:any`
- 二次验证(可选但建议生产启用):
- 若设置环境变量 `IAM_SENSITIVE_ACTION_TOKEN`,则必须传 Header`X-Sensitive-Token: <token>`,否则返回 403。