diff --git a/src/main/modules/sync/server/auth.ts b/src/main/modules/sync/server/auth.ts index fc094d71..7e153501 100644 --- a/src/main/modules/sync/server/auth.ts +++ b/src/main/modules/sync/server/auth.ts @@ -7,94 +7,114 @@ import { aesDecrypt, aesEncrypt, getComputerName, rsaEncrypt } from '../utils' const requestIps = new Map() + +const getAvailableIP = (req: http.IncomingMessage) => { + let ip = getIP(req) + return ip && (requestIps.get(ip) ?? 0) < 10 ? ip : null +} + +const verifyByKey = (encryptMsg: string, userId: string) => { + const keyInfo = getClientKeyInfo(userId) + if (!keyInfo) return null + let text + try { + text = aesDecrypt(encryptMsg, keyInfo.key) + } catch (err) { + return null + } + // console.log(text) + if (text.startsWith(SYNC_CODE.authMsg)) { + const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown' + if (deviceName != keyInfo.deviceName) { + keyInfo.deviceName = deviceName + saveClientKeyInfo(keyInfo) + } + return aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key) + } + return null +} + +const verifyByCode = (encryptMsg: string, password: string) => { + let key = ''.padStart(16, Buffer.from(password).toString('hex')) + // const iv = Buffer.from(key.split('').reverse().join('')).toString('base64') + key = Buffer.from(key).toString('base64') + // console.log(req.headers.m, authCode, key) + let text + try { + text = aesDecrypt(encryptMsg, key) + } catch (err) { + return null + } + // console.log(text) + if (text.startsWith(SYNC_CODE.authMsg)) { + const data = text.split('\n') + const publicKey = `-----BEGIN PUBLIC KEY-----\n${data[1]}\n-----END PUBLIC KEY-----` + const deviceName = data[2] || 'Unknown' + const isMobile = data[3] == 'lx_music_mobile' + const keyInfo = createClientKeyInfo(deviceName, isMobile) + return rsaEncrypt(Buffer.from(JSON.stringify({ + clientId: keyInfo.clientId, + key: keyInfo.key, + serverName: getComputerName(), + })), publicKey) + } + return null +} + export const authCode = async(req: http.IncomingMessage, res: http.ServerResponse, password: string) => { let code = 401 let msg: string = SYNC_CODE.msgAuthFailed - let ip = getIP(req) // console.log(req.headers) - if (typeof req.headers.m == 'string') { - if (ip && (requestIps.get(ip) ?? 0) < 10) { - if (req.headers.m) { - label: - if (req.headers.i) { // key验证 - if (typeof req.headers.i != 'string') break label - const keyInfo = getClientKeyInfo(req.headers.i) - if (!keyInfo) break label - let text - try { - text = aesDecrypt(req.headers.m, keyInfo.key) - } catch (err) { - break label - } - // console.log(text) - if (text.startsWith(SYNC_CODE.authMsg)) { - code = 200 - const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown' - if (deviceName != keyInfo.deviceName) { - keyInfo.deviceName = deviceName - saveClientKeyInfo(keyInfo) - } - msg = aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key) - } - } else { // 连接码验证 - let key = ''.padStart(16, Buffer.from(password).toString('hex')) - // const iv = Buffer.from(key.split('').reverse().join('')).toString('base64') - key = Buffer.from(key).toString('base64') - // console.log(req.headers.m, authCode, key) - let text - try { - text = aesDecrypt(req.headers.m, key) - } catch (err) { - break label - } - // console.log(text) - if (text.startsWith(SYNC_CODE.authMsg)) { - code = 200 - const data = text.split('\n') - const publicKey = `-----BEGIN PUBLIC KEY-----\n${data[1]}\n-----END PUBLIC KEY-----` - const deviceName = data[2] || 'Unknown' - const isMobile = data[3] == 'lx_music_mobile' - const keyInfo = createClientKeyInfo(deviceName, isMobile) - msg = rsaEncrypt(Buffer.from(JSON.stringify({ - clientId: keyInfo.clientId, - key: keyInfo.key, - serverName: getComputerName(), - })), publicKey) - } - } + let ip = getAvailableIP(req) + if (ip) { + if (typeof req.headers.m == 'string' && req.headers.m) { + const userId = req.headers.i + const _msg = typeof userId == 'string' && userId + ? verifyByKey(req.headers.m, userId) + : verifyByCode(req.headers.m, password) + if (_msg != null) { + msg = _msg + code = 200 } - } else { - code = 403 - msg = SYNC_CODE.msgBlockedIp } + + if (code != 200) { + const num = requestIps.get(ip) ?? 0 + // if (num > 20) return + requestIps.set(ip, num + 1) + } + } else { + code = 403 + msg = SYNC_CODE.msgBlockedIp } + res.writeHead(code) res.end(msg) - - if (ip && code != 200) { - const num = requestIps.get(ip) ?? 0 - if (num > 20) return - requestIps.set(ip, num + 1) - } } +const verifyConnection = (encryptMsg: string, userId: string) => { + const keyInfo = getClientKeyInfo(userId) + if (!keyInfo) return false + let text + try { + text = aesDecrypt(encryptMsg, keyInfo.key) + } catch (err) { + return false + } + // console.log(text) + return text == SYNC_CODE.msgConnect +} export const authConnect = async(req: http.IncomingMessage) => { - const query = querystring.parse((req.url as string).split('?')[1]) - const i = query.i - const t = query.t - label: - if (typeof i == 'string' && typeof t == 'string') { - const keyInfo = getClientKeyInfo(i) - if (!keyInfo) break label - let text - try { - text = aesDecrypt(t, keyInfo.key) - } catch (err) { - break label - } - // console.log(text) - if (text == SYNC_CODE.msgConnect) return + let ip = getAvailableIP(req) + if (ip) { + const query = querystring.parse((req.url as string).split('?')[1]) + const i = query.i + const t = query.t + if (typeof i == 'string' && typeof t == 'string' && verifyConnection(t, i)) return + + const num = requestIps.get(ip) ?? 0 + requestIps.set(ip, num + 1) } throw new Error('failed') } diff --git a/src/main/modules/sync/server/server.ts b/src/main/modules/sync/server/server.ts index 3961417e..8a597713 100644 --- a/src/main/modules/sync/server/server.ts +++ b/src/main/modules/sync/server/server.ts @@ -40,16 +40,18 @@ const codeTools: { }, } +const checkDuplicateClient = (newSocket: LX.Sync.Server.Socket) => { + for (const client of [...wss!.clients]) { + if (client === newSocket || client.keyInfo.clientId != newSocket.keyInfo.clientId) continue + console.log('duplicate client', client.keyInfo.deviceName) + client.isReady = false + client.close(SYNC_CLOSE_CODE.normal) + } +} + const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingMessage) => { const queryData = url.parse(request.url as string, true).query as Record - socket.onClose(() => { - // console.log('disconnect', reason) - status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1) - sendServerStatus(status) - }) - - // // if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true) const keyInfo = getClientKeyInfo(queryData.i) if (!keyInfo) { @@ -60,6 +62,8 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM saveClientKeyInfo(keyInfo) // // socket.lx_keyInfo = keyInfo socket.keyInfo = keyInfo + checkDuplicateClient(socket) + try { await syncList(wss as LX.Sync.Server.SocketServer, socket) } catch (err) { @@ -68,6 +72,11 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM return } status.devices.push(keyInfo) + socket.onClose(() => { + // console.log('disconnect', reason) + status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1) + sendServerStatus(status) + }) // handleConnection(io, socket) sendServerStatus(status) @@ -216,15 +225,15 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis }) const interval = setInterval(() => { - wss?.clients.forEach(ws => { - if (ws.isAlive == false) { - ws.terminate() + wss?.clients.forEach(socket => { + if (socket.isAlive == false) { + socket.terminate() return } - ws.isAlive = false - ws.ping(noop) - if (ws.keyInfo.isMobile) ws.send('ping', noop) + socket.isAlive = false + socket.ping(noop) + if (socket.keyInfo.isMobile) socket.send('ping', noop) }) }, 30000)