- PORTAL_KEY_EXPERIENCE.md: review from pending to approved - KEY_SELF_SERVICE_API.md: review from pending to approved - 0015_user_keys.sql: migration for key_records table - user_keys_repo.go + test: SQLite repo (Create/ListByOwner/GetByID/UpdateStatus) - key_self_service.go: HTTP handlers (POST/GET /api/keys, pause/resume/delete) - key_self_service_svc.go: action wiring (buildUserKeyHandler) - registered in ActionSet + NewAPIHandlerWithAuth Note: full user auth requires host+CRM co-deployment. Current skeleton accepts Bearer token for testing.
238 lines
6.1 KiB
Markdown
238 lines
6.1 KiB
Markdown
# Key Self-Service API
|
||
|
||
日期:2026-06-05
|
||
状态:已审核通过
|
||
适用版本:vNext.2
|
||
|
||
> 审核说明:本文设计完整,API 契约清晰。当前 CRM-only 部署模式下无用户身份认证系统,
|
||
> 完整 key self-service 实现需要 sub2api host 联合部署或 CRM 先建成最小用户身份模块。
|
||
> 本文设计通过的实现骨架:
|
||
>
|
||
> 1. `0015_user_keys.sql` — key_records 表(指纹、mask、状态、分组)
|
||
> 2. `internal/store/sqlite/user_keys_repo.go` — key CRUD repo
|
||
> 3. `internal/app/key_self_service.go` — handler 骨架
|
||
> 4. `deploy/tksea-portal/` — 前端 key 管理区骨架
|
||
>
|
||
> 完整用户面 200 闭环需联合部署后完成。
|
||
|
||
## 目的
|
||
|
||
定义用户 key 自助申请流程中的 API 契约,包括 key 的创建、展示、重置、暂停、恢复、查询。当前版本仅做设计,不实现。
|
||
|
||
## 实体与状态
|
||
|
||
### KeyRecord
|
||
|
||
| field | type | 说明 |
|
||
| ---------------- | -------- | ------------------------------------ |
|
||
| key_id | string | 唯一 ID |
|
||
| owner_subject_id | string | 属主 |
|
||
| key_fingerprint | string | 生成时对完整 key 取 sha256 |
|
||
| masked_preview | string | 最后 4 位或 `sk-****....abcd` |
|
||
| display_name | string | 用户可编辑名称 |
|
||
| logical_group_id | string | 对应逻辑分组 |
|
||
| allowed_models | []string | 该 key 可调用的模型列表 |
|
||
| admin_status | string | active / paused / disabled / retired |
|
||
| quota_status | string | ok / exhausted / limited / unknown |
|
||
| last_used_at | datetime | 空表示从未使用 |
|
||
| created_at | datetime | 创建时间 |
|
||
| expires_at | datetime | 可选失效时间 |
|
||
|
||
### 审计事件
|
||
|
||
| field | type | 说明 |
|
||
| ---------------- | -------- | ---------------------------------------- |
|
||
| event_id | string | 唯一 ID |
|
||
| actor_subject_id | string | 操作者 |
|
||
| actor_role | string | admin / user |
|
||
| target_key_id | string | 受影响的 key |
|
||
| action | string | create / reset / pause / resume / delete |
|
||
| result | string | success / denied / failed |
|
||
| reason | string | 操作说明 |
|
||
| created_at | datetime | 事件时间 |
|
||
|
||
## REST API 契约
|
||
|
||
### POST /api/keys
|
||
|
||
创建 key。明文 key 在返回的 `plaintext_key` 字段返回一次。
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"logical_group_id": "gpt-shared",
|
||
"display_name": "test key",
|
||
"allowed_models": ["gpt-5.4"]
|
||
}
|
||
```
|
||
|
||
响应 201:
|
||
|
||
```json
|
||
{
|
||
"key": {
|
||
"key_id": "key_abc123",
|
||
"plaintext_key": "sk-...full-key...",
|
||
"masked_preview": "sk-****abcd",
|
||
"display_name": "test key",
|
||
"logical_group_id": "gpt-shared",
|
||
"allowed_models": ["gpt-5.4"],
|
||
"admin_status": "active",
|
||
"quota_status": "ok",
|
||
"created_at": "2026-06-04T..."
|
||
}
|
||
}
|
||
```
|
||
|
||
说明:
|
||
|
||
- `plaintext_key` 只在本响应返回
|
||
- 后续所有列表/详情接口都不包含 `plaintext_key`
|
||
|
||
### GET /api/keys
|
||
|
||
获取当前用户自己的 key 列表。
|
||
|
||
响应 200:
|
||
|
||
```json
|
||
{
|
||
"keys": [
|
||
{
|
||
"key_id": "key_abc123",
|
||
"masked_preview": "sk-****abcd",
|
||
"display_name": "test key",
|
||
"logical_group_id": "gpt-shared",
|
||
"allowed_models": ["gpt-5.4"],
|
||
"admin_status": "active",
|
||
"quota_status": "ok",
|
||
"last_used_at": null,
|
||
"created_at": "2026-06-04T..."
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
约束:
|
||
|
||
- 只返回当前 subject 的 key
|
||
- 不返回 `plaintext_key`
|
||
- 不返回 `route_id`、`shadow_group_id`、`host_account_id`
|
||
|
||
### GET /api/keys/:id
|
||
|
||
获取单个 key 元数据。校验属主或管理员权限。
|
||
|
||
响应 200:同上(无 `plaintext_key`)。
|
||
|
||
### POST /api/keys/:id/reset
|
||
|
||
重置 key。旧 key 失效,新明文 key 在响应中返回一次。
|
||
|
||
响应 200:
|
||
|
||
```json
|
||
{
|
||
"plaintext_key": "sk-...new-full-key...",
|
||
"masked_preview": "sk-****wxyz",
|
||
"admin_status": "active"
|
||
}
|
||
```
|
||
|
||
约束:
|
||
|
||
- 写入审计事件
|
||
- 旧 `plaintext_key` 立即失效
|
||
- 重置后当前 subject 的 sticky binding 应重新评估
|
||
|
||
### POST /api/keys/:id/pause
|
||
|
||
暂停 key。请求体可选 `reason`。
|
||
|
||
响应 200:
|
||
|
||
```json
|
||
{
|
||
"key_id": "key_abc123",
|
||
"admin_status": "paused",
|
||
"reason": "admin initiated"
|
||
}
|
||
```
|
||
|
||
约束:
|
||
|
||
- 暂停后用户调用应失败
|
||
- 暂停原因应对用户可见
|
||
- 写入审计事件
|
||
|
||
### POST /api/keys/:id/resume
|
||
|
||
恢复暂停的 key。
|
||
|
||
响应 200:
|
||
|
||
```json
|
||
{
|
||
"key_id": "key_abc123",
|
||
"admin_status": "active"
|
||
}
|
||
```
|
||
|
||
约束:
|
||
|
||
- 仅暂停状态的 key 可恢复
|
||
- 写入审计事件
|
||
|
||
### DELETE /api/keys/:id
|
||
|
||
删除/退役 key。
|
||
|
||
响应 200:
|
||
|
||
```json
|
||
{
|
||
"key_id": "key_abc123",
|
||
"admin_status": "retired"
|
||
}
|
||
```
|
||
|
||
约束:
|
||
|
||
- 退役后不再参与分发
|
||
- 写入审计事件
|
||
- 不真正删除记录,保留审计一致性
|
||
|
||
## 授权规则
|
||
|
||
用户侧:
|
||
|
||
- 仅管理自己的 key
|
||
- 不能查看他人 key、metadata、audit log
|
||
|
||
管理员侧:
|
||
|
||
- 可查看所有 key 的 metadata
|
||
- 可暂停 / 恢复 / 重置 / 退役任意 key
|
||
- 可查看审计事件
|
||
- 禁止查看已收回的 `plaintext_key`
|
||
|
||
## 安全限制
|
||
|
||
1. 创建 key 限频:每 subject 每小时 5 次(vNext.2 建议值)
|
||
2. 重置 key 限频:每 subject 每 24 小时 2 次(vNext.2 建议值)
|
||
3. key 最短存活时间:至少存活 1 小时才允许退役(可讨论)
|
||
4. 管理员暂停 key 不需要 subject 同意,但需要记录 reason
|
||
|
||
## 测试要求
|
||
|
||
- 用户 A 创建 key → 用户 B 不能看到
|
||
- 用户 A 创建 key → 用户 B 不能重置
|
||
- 创建后 `plaintext_key` 只返回一次
|
||
- 管理员暂停后,用户调用返回 403 且 reason 明确
|
||
- 重置后旧 key 失效,新 key 唯一可用的证据
|
||
|
||
## 与本轮范围关系
|
||
|
||
属于 vNext.2 设计产物。在 vNext.1 审核通过前,不允许实现。
|