From e5dd3ade512b64aba31de28fc754018ed4e281fb Mon Sep 17 00:00:00 2001 From: CrescentLeaf Date: Sun, 21 Sep 2025 12:28:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=AA=A2=E9=A9=97=E7=94=A8=E6=88=B6?= =?UTF-8?q?=E7=9A=84=20=E8=A8=AD=E5=82=99=20ID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/Data.ts | 3 ++- client/api/Client.ts | 11 ++++++--- server/api/ApiManager.ts | 22 ++++++++++++++--- server/api/BaseApi.ts | 10 +++----- server/api/ChatApi.ts | 12 ++++----- server/api/Token.ts | 1 + server/api/TokenManager.ts | 11 +++++++-- server/api/UserApi.ts | 33 ++++++++++++++----------- server/typedef/EventCallbackFunction.ts | 2 +- 9 files changed, 67 insertions(+), 38 deletions(-) diff --git a/client/Data.ts b/client/Data.ts index 6f93135..3fa9985 100644 --- a/client/Data.ts +++ b/client/Data.ts @@ -21,9 +21,10 @@ const _data_cached = JSON.parse(_dec) declare global { interface Window { data: { - split_sizes: number[]; + split_sizes: number[] apply(): void access_token?: string + device_id: string } } } diff --git a/client/api/Client.ts b/client/api/Client.ts index e26a9c9..a9ae69b 100644 --- a/client/api/Client.ts +++ b/client/api/Client.ts @@ -2,19 +2,24 @@ import { io, Socket } from 'socket.io-client' import { CallMethod, ClientEvent } from './ApiDeclare.ts' import ApiCallbackMessage from './ApiCallbackMessage.ts' import User from "./client_data/User.ts" -import data from "../Data.ts"; +import data from "../Data.ts" type UnknownObject = { [key: string]: unknown } class Client { static myUserProfile?: User static socket?: Socket - static events: { [key: string]: (data: UnknownObject) => UnknownObject } = {} + static events: { [key: string]: (data: UnknownObject) => UnknownObject | undefined } = {} static connect() { + if (data.device_id == null) + data.device_id = crypto.randomUUID() this.socket?.disconnect() this.socket && delete this.socket this.socket = io({ - transports: ['websocket'] + transports: ['websocket'], + auth: { + device_id: data.device_id + }, }) this.socket!.on("The_White_Silk", (name: string, data: UnknownObject, callback: (ret: UnknownObject) => void) => { try { diff --git a/server/api/ApiManager.ts b/server/api/ApiManager.ts index ba7f93f..a7ca3bd 100644 --- a/server/api/ApiManager.ts +++ b/server/api/ApiManager.ts @@ -34,10 +34,22 @@ export default class ApiManager { } static initEvents() { const io = this.socketIoServer + io.on('connection', (socket) => { + // TODO: fix ip == undefined + // https://github.com/denoland/deno/blob/7938d5d2a448b876479287de61e9e3b8c6109bc8/ext/node/polyfills/net.ts#L1713 + const ip = socket.conn.remoteAddress + + const deviceId = socket.handshake.auth.device_id as string + + socket.on('disconnect', (_reason) => { + console.log(chalk.yellow('[斷]') + ` ${ip} disconnected`) + }) + console.log(chalk.green('[連]') + ` ${ip} connected`) + socket.on("The_White_Silk", (name: string, args: { [key: string]: unknown }, callback_: (ret: ApiCallbackMessage) => void) => { function callback(ret: ApiCallbackMessage) { - console.log(chalk.blue('[發]') + ` ${socket.request.socket.remoteAddress} <- ${ret.code == 200 ? chalk.green(ret.msg) : chalk.red(ret.msg)} [${ret.code}]${ ret.data ? (' ') : ''}`) + console.log(chalk.blue('[發]') + ` ${ip} <- ${ret.code == 200 ? chalk.green(ret.msg) : chalk.red(ret.msg)} [${ret.code}]${ret.data ? (' ') : ''}`) return callback_(ret) } try { @@ -45,9 +57,11 @@ export default class ApiManager { msg: "Invalid request.", code: 400 }) - console.log(chalk.red('[收]') + ` ${socket.request.socket.remoteAddress} -> ${chalk.yellow(name)} `) + console.log(chalk.red('[收]') + ` ${ip} -> ${chalk.yellow(name)} `) - return callback(this.event_listeners[name]?.(args) || { + return callback(this.event_listeners[name]?.(args, { + deviceId + }) || { code: 501, msg: "Not implmented", }) @@ -59,7 +73,7 @@ export default class ApiManager { code: err instanceof DataWrongError ? 400 : 500, msg: "錯誤: " + err.message }) - } catch(_e) {} + } catch (_e) { } } }) }) diff --git a/server/api/BaseApi.ts b/server/api/BaseApi.ts index 8d58903..69d476f 100644 --- a/server/api/BaseApi.ts +++ b/server/api/BaseApi.ts @@ -22,14 +22,12 @@ export default abstract class BaseApi { return true return false } - checkUserToken(user: User, token: Token) { - if (!this.checkToken(token)) return false - if (token.author != user.bean.id) return false - return true - } - checkToken(token: Token) { + checkToken(token: Token, deviceId: string) { if (token.expired_time < Date.now()) return false if (!User.findById(token.author)) return false + if (deviceId != null) + if (token.device_id != deviceId) + return false return true } registerEvent(name: CallMethod, func: EventCallbackFunction) { diff --git a/server/api/ChatApi.ts b/server/api/ChatApi.ts index 55315f0..130d747 100644 --- a/server/api/ChatApi.ts +++ b/server/api/ChatApi.ts @@ -15,14 +15,14 @@ export default class ChatApi extends BaseApi { * @param token 令牌 * @param target 目標對話 */ - this.registerEvent("Chat.getInfo", (args) => { + this.registerEvent("Chat.getInfo", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['token', 'target'])) return { msg: "參數缺失", code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } @@ -59,14 +59,14 @@ export default class ChatApi extends BaseApi { * @param target 目標對話 * @param */ - this.registerEvent("Chat.sendMessage", (args) => { + this.registerEvent("Chat.sendMessage", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['token', 'target'])) return { msg: "參數缺失", code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } @@ -82,14 +82,14 @@ export default class ChatApi extends BaseApi { * @param target 目標對話 * @param page 頁面 */ - this.registerEvent("Chat.getMessageHistory", (args) => { + this.registerEvent("Chat.getMessageHistory", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['token', 'target', 'page'])) return { msg: "參數缺失", code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } diff --git a/server/api/Token.ts b/server/api/Token.ts index a5c67dc..e843468 100644 --- a/server/api/Token.ts +++ b/server/api/Token.ts @@ -3,4 +3,5 @@ export default interface Token { auth: string made_time: number expired_time: number + device_id: string } \ No newline at end of file diff --git a/server/api/TokenManager.ts b/server/api/TokenManager.ts index 484cbd1..0eb2d13 100644 --- a/server/api/TokenManager.ts +++ b/server/api/TokenManager.ts @@ -22,22 +22,29 @@ export default class TokenManager { ).toString('hex') } static decode(token: string) { + if (token == null) throw new Error('令牌為空!') return JSON.parse(crypto.createDecipheriv("aes-256-gcm", normalizeKey(config.aes_key), '01234567890123456').update( Buffer.from(token, 'hex') ).toString()) as Token } - static make(user: User, time: number = Date.now()) { + static make(user: User, time_: number | null | undefined, device_id: string) { + const time = (time_ || Date.now()) return this.encode({ author: user.bean.id, auth: this.makeAuth(user), made_time: time, expired_time: time + (1 * 1000 * 60 * 60 * 24), + device_id: device_id }) } + /** + * 獲取新令牌 + * 注意: 只驗證用戶, 不驗證令牌有效性! + */ static makeNewer(user: User, token: string) { if (this.check(user, token)) - return this.make(user, Date.now() + (1 * 1000 * 60 * 60 * 24)) + return this.make(user, Date.now() + (1 * 1000 * 60 * 60 * 24), this.decode(token).device_id) } static check(user: User, token: string) { const tk = this.decode(token) diff --git a/server/api/UserApi.ts b/server/api/UserApi.ts index c2be4c8..ab8d854 100644 --- a/server/api/UserApi.ts +++ b/server/api/UserApi.ts @@ -11,7 +11,7 @@ export default class UserApi extends BaseApi { } override onInit(): void { // 驗證 - this.registerEvent("User.auth", (args) => { + this.registerEvent("User.auth", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['access_token'])) return { msg: "參數缺失", code: 400, @@ -23,11 +23,14 @@ export default class UserApi extends BaseApi { msg: "登錄令牌失效", code: 401, } - if (!User.findById(access_token.author)) return { msg: "賬號不存在", code: 401, } + if (access_token.device_id != deviceId) return { + msg: "驗證失敗", + code: 401, + } return { msg: "成功", @@ -45,7 +48,7 @@ export default class UserApi extends BaseApi { } }) // 登錄 - this.registerEvent("User.login", (args) => { + this.registerEvent("User.login", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['account', 'password'])) return { msg: "參數缺失", code: 400, @@ -65,7 +68,7 @@ export default class UserApi extends BaseApi { msg: "成功", code: 200, data: { - access_token: TokenManager.make(user) + access_token: TokenManager.make(user, null, deviceId) }, } @@ -75,7 +78,7 @@ export default class UserApi extends BaseApi { } }) // 注冊 - this.registerEvent("User.register", (args) => { + this.registerEvent("User.register", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['nickname', 'password'])) return { msg: "參數缺失", code: 400, @@ -105,7 +108,7 @@ export default class UserApi extends BaseApi { * ================================================ */ // 更新頭像 - this.registerEvent("User.setAvatar", (args) => { + this.registerEvent("User.setAvatar", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['avatar', 'token'])) return { msg: "參數缺失", code: 400, @@ -115,7 +118,7 @@ export default class UserApi extends BaseApi { code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } @@ -130,14 +133,14 @@ export default class UserApi extends BaseApi { } }) // 更新資料 - this.registerEvent("User.updateProfile", (args) => { + this.registerEvent("User.updateProfile", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['token'])) return { msg: "參數缺失", code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } @@ -154,14 +157,14 @@ export default class UserApi extends BaseApi { } }) // 獲取用戶信息 - this.registerEvent("User.getMyInfo", (args) => { + this.registerEvent("User.getMyInfo", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['token'])) return { msg: "參數缺失", code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } @@ -180,14 +183,14 @@ export default class UserApi extends BaseApi { } }) // 獲取聯絡人列表 - this.registerEvent("User.getMyContacts", (args) => { + this.registerEvent("User.getMyContacts", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['token'])) return { msg: "參數缺失", code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } @@ -212,14 +215,14 @@ export default class UserApi extends BaseApi { } }) // 添加聯絡人 - this.registerEvent("User.addContact", (args) => { + this.registerEvent("User.addContact", (args, { deviceId }) => { if (this.checkArgsMissing(args, ['token', 'contact_chat_id'])) return { msg: "參數缺失", code: 400, } const token = TokenManager.decode(args.token as string) - if (!this.checkToken(token)) return { + if (!this.checkToken(token, deviceId)) return { code: 401, msg: "令牌無效", } diff --git a/server/typedef/EventCallbackFunction.ts b/server/typedef/EventCallbackFunction.ts index a1e8f29..7378cdb 100644 --- a/server/typedef/EventCallbackFunction.ts +++ b/server/typedef/EventCallbackFunction.ts @@ -1,5 +1,5 @@ import ApiCallbackMessage from "../api/ApiCallbackMessage.ts" -type EventCallbackFunction = (args: { [key: string]: unknown }) => ApiCallbackMessage +type EventCallbackFunction = (args: { [key: string]: unknown }, client: { deviceId: string }) => ApiCallbackMessage export default EventCallbackFunction