feat: 檢驗用戶的 設備 ID
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ? (' <extras: ' + JSON.stringify(ret.data) + '>') : ''}`)
|
||||
console.log(chalk.blue('[發]') + ` ${ip} <- ${ret.code == 200 ? chalk.green(ret.msg) : chalk.red(ret.msg)} [${ret.code}]${ret.data ? (' <extras: ' + JSON.stringify(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)} <args: ${JSON.stringify(args)}>`)
|
||||
console.log(chalk.red('[收]') + ` ${ip} -> ${chalk.yellow(name)} <args: ${JSON.stringify(args)}>`)
|
||||
|
||||
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) { }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: "令牌無效",
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ export default interface Token {
|
||||
auth: string
|
||||
made_time: number
|
||||
expired_time: number
|
||||
device_id: string
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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: "令牌無效",
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user