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:
@@ -75,11 +75,11 @@ async function initSchema(connection) {
|
||||
password VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
game_name VARCHAR(50) NOT NULL,
|
||||
game_uid VARCHAR(50) NOT NULL,
|
||||
game_uid VARCHAR(50) NOT NULL DEFAULT '',
|
||||
role ENUM('owner','admin','player') NOT NULL DEFAULT 'player',
|
||||
active TINYINT(1) NOT NULL DEFAULT 0,
|
||||
email_verified TINYINT(1) NOT NULL DEFAULT 0,
|
||||
source ENUM('netease','skin') NOT NULL DEFAULT 'netease',
|
||||
source VARCHAR(20) NOT NULL DEFAULT '',
|
||||
verify_token VARCHAR(255),
|
||||
verify_expires DATETIME,
|
||||
reset_token VARCHAR(255),
|
||||
@@ -91,7 +91,7 @@ async function initSchema(connection) {
|
||||
await createIfNotExists('user_identities', `CREATE TABLE user_identities (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
source ENUM('netease','skin') NOT NULL,
|
||||
source VARCHAR(20) NOT NULL,
|
||||
game_name VARCHAR(50) NOT NULL,
|
||||
game_uid VARCHAR(50) NOT NULL DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -99,6 +99,25 @@ async function initSchema(connection) {
|
||||
INDEX idx_user (user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
|
||||
|
||||
await createIfNotExists('sources', `CREATE TABLE sources (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
label VARCHAR(50) NOT NULL,
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
|
||||
|
||||
await createIfNotExists('api_sessions', `CREATE TABLE api_sessions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
client_id VARCHAR(64) NOT NULL,
|
||||
session_token VARCHAR(128) NOT NULL UNIQUE,
|
||||
expires_at DATETIME NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_client (client_id),
|
||||
INDEX idx_expires (expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
|
||||
|
||||
await createIfNotExists('tickets', `CREATE TABLE tickets (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT,
|
||||
@@ -108,7 +127,7 @@ async function initSchema(connection) {
|
||||
status ENUM('pending','processing','awaiting_info','appealing','resolved','rejected','closed') NOT NULL DEFAULT 'pending',
|
||||
priority ENUM('low','medium','high','urgent') NOT NULL DEFAULT 'medium',
|
||||
reporter_game_name VARCHAR(50) NOT NULL,
|
||||
reporter_game_uid VARCHAR(50) NOT NULL,
|
||||
reporter_game_uid VARCHAR(50) NOT NULL DEFAULT '',
|
||||
target_game_name VARCHAR(50),
|
||||
target_game_uid VARCHAR(50),
|
||||
reason VARCHAR(500),
|
||||
@@ -391,6 +410,20 @@ async function migrateAdditions(db) {
|
||||
try { await db.execute(`CREATE TABLE IF NOT EXISTS api_clients (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, client_id VARCHAR(64) NOT NULL UNIQUE, secret_hash VARCHAR(255) NOT NULL, active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_active (active)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
|
||||
try { await db.execute("ALTER TABLE tickets ADD COLUMN server_name VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
|
||||
try { await db.execute("ALTER TABLE bans ADD COLUMN server_name VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
|
||||
|
||||
// ---- 动态来源: sources 表 + users/user_identities.source 改 VARCHAR + game_uid 可空 ----
|
||||
try { await db.execute(`CREATE TABLE IF NOT EXISTS sources (id INT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(20) NOT NULL UNIQUE, label VARCHAR(50) NOT NULL, enabled TINYINT(1) NOT NULL DEFAULT 1, sort_order INT NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
|
||||
try {
|
||||
// 预置来源(幂等), 老数据来源保持兼容
|
||||
await db.execute("INSERT IGNORE INTO sources(code, label, sort_order) VALUES ('netease','网易端',1), ('skin','皮肤站',2)");
|
||||
} catch {}
|
||||
try { await db.execute("ALTER TABLE users MODIFY source VARCHAR(20) NOT NULL DEFAULT ''"); } catch {}
|
||||
try { await db.execute("ALTER TABLE users MODIFY game_uid VARCHAR(50) NOT NULL DEFAULT ''"); } catch {}
|
||||
try { await db.execute("ALTER TABLE user_identities MODIFY source VARCHAR(20) NOT NULL"); } catch {}
|
||||
try { await db.execute("ALTER TABLE tickets MODIFY reporter_game_uid VARCHAR(50) NOT NULL DEFAULT ''"); } catch {}
|
||||
|
||||
// ---- API SESSION 表 ----
|
||||
try { await db.execute(`CREATE TABLE IF NOT EXISTS api_sessions (id INT AUTO_INCREMENT PRIMARY KEY, client_id VARCHAR(64) NOT NULL, session_token VARCHAR(128) NOT NULL UNIQUE, expires_at DATETIME NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_client (client_id), INDEX idx_expires (expires_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
|
||||
}
|
||||
|
||||
async function seedTemplates(db) {
|
||||
|
||||
Reference in New Issue
Block a user