优化&修复同步机制
parent
667fc8c8a6
commit
5d72d092fc
|
@ -7,94 +7,114 @@ import { aesDecrypt, aesEncrypt, getComputerName, rsaEncrypt } from '../utils'
|
|||
|
||||
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) => {
|
||||
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')
|
||||
}
|
||||
|
|
|
@ -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<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)
|
||||
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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue