chore: pure-random 32-char secret, no default source, legacy migration

- genSecret: drop SeaReport- prefix, plain 32-char random hex
- users.source: no default anywhere (register/admin/external), ADD COLUMN
  migration now VARCHAR DEFAULT '' (was ENUM netease default)
- legacy upgrade path kept: ENUM->VARCHAR MODIFY + user_identities MODIFY
  + sources seeded netease/skin idempotently
- docs updated (credential format, no prefix)
This commit is contained in:
2026-08-19 20:16:16 +08:00
parent 65edbaf157
commit 3441eb9478
4 changed files with 7 additions and 7 deletions

View File

@@ -353,7 +353,7 @@ async function migrateAdditions(db) {
try { await db.execute("ALTER TABLE tickets ADD COLUMN parent_ticket_id INT"); } catch {} try { await db.execute("ALTER TABLE tickets ADD COLUMN parent_ticket_id INT"); } catch {}
try { await db.execute("ALTER TABLE tickets MODIFY type ENUM('report','suggestion','appeal','result_appeal') NOT NULL"); } catch {} try { await db.execute("ALTER TABLE tickets MODIFY type ENUM('report','suggestion','appeal','result_appeal') NOT NULL"); } catch {}
try { await db.execute("ALTER TABLE tickets MODIFY status ENUM('pending','processing','awaiting_info','appealing','resolved','rejected','closed') NOT NULL"); } catch {} try { await db.execute("ALTER TABLE tickets MODIFY status ENUM('pending','processing','awaiting_info','appealing','resolved','rejected','closed') NOT NULL"); } catch {}
try { await db.execute("ALTER TABLE users ADD COLUMN source ENUM('netease','skin') NOT NULL DEFAULT 'netease'"); } catch {} try { await db.execute("ALTER TABLE users ADD COLUMN source VARCHAR(20) NOT NULL DEFAULT ''"); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS user_servers (user_id INT NOT NULL, group_name VARCHAR(100) NOT NULL, server_name VARCHAR(100) NOT NULL, UNIQUE KEY uk_user_server (user_id, group_name, server_name), INDEX idx_user (user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {} try { await db.execute(`CREATE TABLE IF NOT EXISTS user_servers (user_id INT NOT NULL, group_name VARCHAR(100) NOT NULL, server_name VARCHAR(100) NOT NULL, UNIQUE KEY uk_user_server (user_id, group_name, server_name), INDEX idx_user (user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS bans (id INT AUTO_INCREMENT PRIMARY KEY, player_name VARCHAR(50) NOT NULL, player_uid VARCHAR(50), source VARCHAR(10) DEFAULT '', reason TEXT, type ENUM('ban','mute','warn','other') NOT NULL DEFAULT 'ban', duration VARCHAR(20) DEFAULT '', ticket_id INT, created_by INT, status ENUM('active','expired','appealed','lifted') NOT NULL DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {} try { await db.execute(`CREATE TABLE IF NOT EXISTS bans (id INT AUTO_INCREMENT PRIMARY KEY, player_name VARCHAR(50) NOT NULL, player_uid VARCHAR(50), source VARCHAR(10) DEFAULT '', reason TEXT, type ENUM('ban','mute','warn','other') NOT NULL DEFAULT 'ban', duration VARCHAR(20) DEFAULT '', ticket_id INT, created_by INT, status ENUM('active','expired','appealed','lifted') NOT NULL DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute("ALTER TABLE bans ADD COLUMN source VARCHAR(10) DEFAULT ''"); } catch {} try { await db.execute("ALTER TABLE bans ADD COLUMN source VARCHAR(10) DEFAULT ''"); } catch {}

View File

@@ -24,8 +24,8 @@ function genClientId() {
} }
function genSecret() { function genSecret() {
// SeaReport- + 32 位随机字符串 // 32 位随机字符串(hex = 32 chars)
return 'SeaReport-' + crypto.randomBytes(16).toString('hex'); // 16 bytes = 32 hex chars return crypto.randomBytes(16).toString('hex');
} }
async function createSession(clientId) { async function createSession(clientId) {

View File

@@ -350,7 +350,7 @@ Base URL: `http://<host>:3100/api`
| 阶段 | 请求头 | 说明 | | 阶段 | 请求头 | 说明 |
|------|--------|------| |------|--------|------|
| 换取 SESSION | `x-api-client-id` + `x-api-secret` | 仅 `POST /external/auth/session` 使用;ID 为 16 位随机数字,Secret 为 `SeaReport-` + 32 位 | | 换取 SESSION | `x-api-client-id` + `x-api-secret` | 仅 `POST /external/auth/session` 使用;ID 为 16 位随机数字,Secret 为 32 位随机字符串 |
| 后续请求 | `Authorization: Bearer <SESSION>` | 所有其余接口;24 小时有效,重换即旧 SESSION 失效,客户端停用立即失效 | | 后续请求 | `Authorization: Bearer <SESSION>` | 所有其余接口;24 小时有效,重换即旧 SESSION 失效,客户端停用立即失效 |
### 子服务器定位(`server` 参数) ### 子服务器定位(`server` 参数)

View File

@@ -14,7 +14,7 @@
```text ```text
Client ID: 1829473056482917 # 16 位纯数字, 随机生成 Client ID: 1829473056482917 # 16 位纯数字, 随机生成
Secret: SeaReport-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4 # SeaReport- + 32 位 Secret: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 # 32 位全随机字符串
``` ```
> ⚠️ Secret 只在创建时显示一次,请立即保存。支持创建多个客户端、单独停用/删除,互不影响。 > ⚠️ Secret 只在创建时显示一次,请立即保存。支持创建多个客户端、单独停用/删除,互不影响。
@@ -70,7 +70,7 @@ BASE = "https://你的域名/api/external"
# 1) 用 ID + Secret 换取 SESSION # 1) 用 ID + Secret 换取 SESSION
r = requests.post(f"{BASE}/auth/session", headers={ r = requests.post(f"{BASE}/auth/session", headers={
"x-api-client-id": "1829473056482917", "x-api-client-id": "1829473056482917",
"x-api-secret": "SeaReport-a1b2c3d4...", "x-api-secret": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
}) })
session = r.json()["session_token"] session = r.json()["session_token"]
HEADERS = {"Authorization": f"Bearer {session}", "Content-Type": "application/json"} HEADERS = {"Authorization": f"Bearer {session}", "Content-Type": "application/json"}
@@ -96,7 +96,7 @@ const BASE = 'https://你的域名/api/external';
// 1) 换取 SESSION // 1) 换取 SESSION
const authRes = await fetch(`${BASE}/auth/session`, { const authRes = await fetch(`${BASE}/auth/session`, {
method: 'POST', method: 'POST',
headers: { 'x-api-client-id': '1829473056482917', 'x-api-secret': 'SeaReport-a1b2c3d4...' }, headers: { 'x-api-client-id': '1829473056482917', 'x-api-secret': 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6' },
}); });
const { session_token } = await authRes.json(); const { session_token } = await authRes.json();
const H = { Authorization: `Bearer ${session_token}`, 'Content-Type': 'application/json' }; const H = { Authorization: `Bearer ${session_token}`, 'Content-Type': 'application/json' };