优化&修复同步机制

pull/1295/head
lyswhut 2023-03-30 17:46:37 +08:00
parent 667fc8c8a6
commit 5d72d092fc
2 changed files with 117 additions and 88 deletions

View File

@ -7,94 +7,114 @@ import { aesDecrypt, aesEncrypt, getComputerName, rsaEncrypt } from '../utils'
const requestIps = new Map<string, number>() const requestIps = new Map<string, number>()
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) => { export const authCode = async(req: http.IncomingMessage, res: http.ServerResponse, password: string) => {
let code = 401 let code = 401
let msg: string = SYNC_CODE.msgAuthFailed let msg: string = SYNC_CODE.msgAuthFailed
let ip = getIP(req)
// console.log(req.headers) // console.log(req.headers)
if (typeof req.headers.m == 'string') { let ip = getAvailableIP(req)
if (ip && (requestIps.get(ip) ?? 0) < 10) { if (ip) {
if (req.headers.m) { if (typeof req.headers.m == 'string' && req.headers.m) {
label: const userId = req.headers.i
if (req.headers.i) { // key验证 const _msg = typeof userId == 'string' && userId
if (typeof req.headers.i != 'string') break label ? verifyByKey(req.headers.m, userId)
const keyInfo = getClientKeyInfo(req.headers.i) : verifyByCode(req.headers.m, password)
if (!keyInfo) break label if (_msg != null) {
let text msg = _msg
try { code = 200
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)
}
}
} }
} 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.writeHead(code)
res.end(msg) 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) => { export const authConnect = async(req: http.IncomingMessage) => {
const query = querystring.parse((req.url as string).split('?')[1]) let ip = getAvailableIP(req)
const i = query.i if (ip) {
const t = query.t const query = querystring.parse((req.url as string).split('?')[1])
label: const i = query.i
if (typeof i == 'string' && typeof t == 'string') { const t = query.t
const keyInfo = getClientKeyInfo(i) if (typeof i == 'string' && typeof t == 'string' && verifyConnection(t, i)) return
if (!keyInfo) break label
let text const num = requestIps.get(ip) ?? 0
try { requestIps.set(ip, num + 1)
text = aesDecrypt(t, keyInfo.key)
} catch (err) {
break label
}
// console.log(text)
if (text == SYNC_CODE.msgConnect) return
} }
throw new Error('failed') throw new Error('failed')
} }

View File

@ -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 handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingMessage) => {
const queryData = url.parse(request.url as string, true).query as Record<string, string> const queryData = url.parse(request.url as string, true).query as Record<string, string>
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) // // if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
const keyInfo = getClientKeyInfo(queryData.i) const keyInfo = getClientKeyInfo(queryData.i)
if (!keyInfo) { if (!keyInfo) {
@ -60,6 +62,8 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
saveClientKeyInfo(keyInfo) saveClientKeyInfo(keyInfo)
// // socket.lx_keyInfo = keyInfo // // socket.lx_keyInfo = keyInfo
socket.keyInfo = keyInfo socket.keyInfo = keyInfo
checkDuplicateClient(socket)
try { try {
await syncList(wss as LX.Sync.Server.SocketServer, socket) await syncList(wss as LX.Sync.Server.SocketServer, socket)
} catch (err) { } catch (err) {
@ -68,6 +72,11 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
return return
} }
status.devices.push(keyInfo) 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) // handleConnection(io, socket)
sendServerStatus(status) sendServerStatus(status)
@ -216,15 +225,15 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
}) })
const interval = setInterval(() => { const interval = setInterval(() => {
wss?.clients.forEach(ws => { wss?.clients.forEach(socket => {
if (ws.isAlive == false) { if (socket.isAlive == false) {
ws.terminate() socket.terminate()
return return
} }
ws.isAlive = false socket.isAlive = false
ws.ping(noop) socket.ping(noop)
if (ws.keyInfo.isMobile) ws.send('ping', noop) if (socket.keyInfo.isMobile) socket.send('ping', noop)
}) })
}, 30000) }, 30000)