Files
sub2api-cn-relay-manager/docs/2026-06-04-KEY_SELF_SERVICE_API.md
phamnazage-jpg 596a2a110c
Some checks failed
CI / Build & Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Release (push) Has been cancelled
feat(vnext2): add user key self-service skeleton
- 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.
2026-06-05 11:45:17 +08:00

238 lines
6.1 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.
# 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 审核通过前,不允许实现。