refactor: session-based external auth, dynamic sources, drop netease UID

Auth (external API):
- ID: 16-digit random (non-sequential); Secret: SeaReport- + 32 hex
- POST /auth/session: ID+Secret -> Bearer SESSION (24h, single-session,
  old session invalidated on re-issue, disabled client invalidates)
- clientAuth now validates Bearer SESSION via api_sessions JOIN api_clients

Sources (dynamic, no default, open-source friendly):
- sources table + CRUD route (/api/sources, owner; delete guarded by usage)
- users/user_identities.source ENUM -> VARCHAR, seeded netease/skin
- register/admin create/identity bind: validate against enabled sources
- UI: 来源管理 page; source dropdowns loaded dynamically everywhere
  (register, dashboard identity, users admin, bans), labels dynamic

UID removal:
- game_uid/reporter_game_uid no longer required (db default '', validations
  dropped, frontend fields optional)

Docs: EXTERNAL-API.md session flow + new credential format; API.md updated
Verified: 37 checks (syntax, session logic, source CRUD, UID removal, docs)
This commit is contained in:
2026-08-19 20:08:06 +08:00
parent 6056153f57
commit 65edbaf157
19 changed files with 497 additions and 103 deletions

View File

@@ -2,31 +2,60 @@
适用对象:QQ 官方机器人、游戏服务器插件、统计面板等**无法做网页登录**的外部系统。
- Base URL:`https://<你的域名>/api/external`
- Base URL:`https://report.sea-studio.top/api/external`
- 数据格式:JSON(`Content-Type: application/json`)
- 本文档所有接口**不需要用户登录**,只需要客户端凭据(见下)
- 本文档所有接口需要客户端凭据(见下)
---
## 一、鉴权(唯一方式:ID + Secret)
## 一、鉴权流程(ID + Secret 换取 SESSION)
在站点后台 →「外部API」页面创建客户端,获得一对凭据:
```text
Client ID: c_1a2b3c4d5e6f7a8b9c0d1e2f
Secret: s_9f8e7d6c5b4a39281726354a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e
Client ID: 1829473056482917 # 16 位纯数字, 随机生成
Secret: SeaReport-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4 # SeaReport- + 32 位
```
> ⚠️ Secret 只在创建时显示一次,请立即保存。支持创建多个客户端、单独停用/删除,互不影响。
> 无凭据 / 凭据错误 / 客户端停用 → 一律返回 `401`。
调用时在请求头携带:
### 第一步:换取 SESSION(唯一使用 ID+Secret 的接口)
```
POST /api/external/auth/session
```
请求头:
| 请求头 | 值 |
|--------|-----|
| `x-api-client-id` | 你的 Client ID |
| `x-api-secret` | 你的 Secret |
**成功响应 200:**
```json
{
"session_token": "5f8a2b3c...96位十六进制",
"expires_in": 86400,
"client_id": "1829473056482917",
"message": "SESSION 有效期 24 小时, 请用 Authorization: Bearer <session> 访问其余接口"
}
```
### 第二步:后续请求携带 SESSION
所有其余接口(工单/封禁/统计等)使用:
| 请求头 | 值 |
|--------|-----|
| `Authorization` | `Bearer <session_token>` |
- SESSION 有效期 **24 小时**;过期后重新调用第一步换取。
- 同一客户端重新换取时,**旧 SESSION 立即失效**(单会话)。
- 客户端被停用 → 已发 SESSION 立即失效(401)。
- 无 SESSION / SESSION 无效或过期 → 一律 `401`
---
## 二、快速开始(Python / Node 示例)
@@ -37,20 +66,21 @@ Secret: s_9f8e7d6c5b4a39281726354a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e
import requests
BASE = "https://你的域名/api/external"
HEADERS = {
"x-api-client-id": "c_1a2b3c4d5e6f7a8b9c0d1e2f",
"x-api-secret": "s_9f8e7d6c...",
"Content-Type": "application/json",
}
# 提交举报工单
# 1) 用 ID + Secret 换取 SESSION
r = requests.post(f"{BASE}/auth/session", headers={
"x-api-client-id": "1829473056482917",
"x-api-secret": "SeaReport-a1b2c3d4...",
})
session = r.json()["session_token"]
HEADERS = {"Authorization": f"Bearer {session}", "Content-Type": "application/json"}
# 2) 提交举报工单(带 SESSION)
r = requests.post(f"{BASE}/tickets", headers=HEADERS, json={
"type": "report",
"title": "恶意破坏",
"reporter_game_name": "Steve",
"reporter_game_uid": "df273bda10b94aa18db345574d5a1e1d",
"target_game_name": "Alex",
"target_game_uid": "SKIN_UUID_002",
"reason": "刷屏+破坏他人建筑",
"description": "多次警告无效",
"server": "survival", # 可选: 子服别名
@@ -62,13 +92,16 @@ print(r.json()) # {"id": 123, "tracking_token": "..."}
```javascript
const BASE = 'https://你的域名/api/external';
const H = {
'x-api-client-id': 'c_1a2b3c...',
'x-api-secret': 's_9f8e7d6c...',
'Content-Type': 'application/json',
};
// 查询工单进度(从提交到结束全程可查)
// 1) 换取 SESSION
const authRes = await fetch(`${BASE}/auth/session`, {
method: 'POST',
headers: { 'x-api-client-id': '1829473056482917', 'x-api-secret': 'SeaReport-a1b2c3d4...' },
});
const { session_token } = await authRes.json();
const H = { Authorization: `Bearer ${session_token}`, 'Content-Type': 'application/json' };
// 2) 查询工单进度(从提交到结束全程可查)
const res = await fetch(`${BASE}/tickets/track?token=你的tracking_token`, { headers: H });
console.log(await res.json());
```
@@ -91,6 +124,12 @@ console.log(await res.json());
## 四、接口清单
### 0. 换取 SESSION(见第一节, 其余接口均需 Bearer SESSION)
```
POST /api/external/auth/session
```
### 1. 提交工单
```
@@ -102,7 +141,7 @@ POST /api/external/tickets
| type | string | ✅ | `report` / `suggestion` / `appeal` |
| title | string | ✅ | 标题 |
| reporter_game_name | string | ✅ | 提交人游戏名 |
| reporter_game_uid | string | | 提交人 UID / UUID |
| reporter_game_uid | string | | 提交人 UID / UUID(选填) |
| target_game_name | string | 条件 | 被举报人游戏名(report 建议填) |
| target_game_uid | string | 条件 | 被举报人 UID |
| reason | string | 条件 | 举报原因(report/appeal 必填) |