Skip to content

Instantly share code, notes, and snippets.

@aslamanver
Last active December 4, 2020 06:58
Show Gist options
  • Save aslamanver/e051a5d9671483732a15076259baca32 to your computer and use it in GitHub Desktop.
Save aslamanver/e051a5d9671483732a15076259baca32 to your computer and use it in GitHub Desktop.
Pure JavaScript WebSocket Server with Ping-Pong, Handshake Upgrade, AES Encrypt, Decrypt and Authentication functionalities.
const express = require("express")
const WebSocket = require('ws')
const app = express()
const server = require('http').createServer(app)
const wss = new WebSocket.Server({ noServer: true })
const wschandler = require('./wschandler').from(wss, server)
wss.on('connection', (ws, req) => {
console.log('connection', req.socket.remoteAddress, ws.data.token)
ws.on('close', (reason) => {
console.log('close', req.socket.remoteAddress, ws.data.token)
})
ws.on('message', (message) => {
console.log('message', message, ws.data.token)
})
})
app.get('/', (req, res) => {
res.sendStatus(200)
})
server.listen(3002, () => console.log(`Server is running at 0.0.0.0:3002`))
const url = require('url')
const CryptoJS = require("crypto-js")
class WSCHandler {
constructor() {
this.wss
this.server
}
from(wss, server) {
this.wss = wss
this.server = server
this.server.on('upgrade', (req, socket, head) => {
console.log('upgrade', socket.remoteAddress, req.url)
this.wssauthenticate(req, (client, error) => {
if (error || !client) {
let headers = 'HTTP/1.1 401 Unauthorized\r\n'
if (error === 429) headers = 'HTTP/1.1 429 Too Many Requests\r\n'
headers += 'Connection: close\r\n'
socket.write(headers + '\r\n')
socket.destroy()
} else {
this.wss.handleUpgrade(req, socket, head, (ws) => {
ws.data = client
ws.isAlive = true;
ws.on('pong', () => {
console.log('pong-from', JSON.stringify(ws.data))
ws.isAlive = true
})
this.wss.emit('connection', ws, req)
})
}
})
})
setInterval(() => {
console.log('ping-clients', this.wss.clients.size)
this.wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate()
ws.isAlive = false
ws.ping(() => { })
})
}, 10000)
}
wssauthenticate(req, callback) {
const query = url.parse(req.url, true).query;
const device = this.decrypt(query.device)
const token = this.decrypt(query.token)
const pos = query.pos
const client = { token, device, pos }
console.log('wssauthenticate', client)
if (client.token !== 'AUTH4ECR2PAYABLE') {
callback(null, 401)
} else if (Array.from(this.wss.clients).findIndex(ws => ws.data.pos === client.pos && ws.data.device === client.device && ws.data.token === client.token) > -1) {
callback(null, 429)
} else {
callback(client)
}
}
encrypt(data) {
if (!data) return null
try {
return CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse("NdRgUkXp2s5v8y/A"), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString();
} catch {
return null
}
}
decrypt(data) {
if (!data) return null
try {
return CryptoJS.AES.decrypt(data, CryptoJS.enc.Utf8.parse("NdRgUkXp2s5v8y/A"), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8)
} catch {
return null
}
}
}
module.exports = new WSCHandler();
// const encrypt = (data) => {
// if (!data) return null
// try {
// return CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse("NdRgUkXp2s5v8y/A"), {
// mode: CryptoJS.mode.ECB,
// padding: CryptoJS.pad.Pkcs7
// }).toString();
// } catch {
// return null
// }
// }
// const decrypt = (data) => {
// if (!data) return null
// try {
// return CryptoJS.AES.decrypt(data, CryptoJS.enc.Utf8.parse("NdRgUkXp2s5v8y/A"), {
// mode: CryptoJS.mode.ECB,
// padding: CryptoJS.pad.Pkcs7
// }).toString(CryptoJS.enc.Utf8)
// } catch {
// return null
// }
// }
// const wssauthenticate = (req, callback) => {
// const query = url.parse(req.url, true).query;
// const device = decrypt(query.device)
// const token = decrypt(query.token)
// const pos = query.pos
// const client = { token, device, pos }
// console.log('wssauthenticate', client)
// if (client.token !== 'AUTH4ECR2PAYABLE') {
// callback(null, 401)
// } else if (Array.from(wss.clients).findIndex(ws => ws.data.pos === client.pos && ws.data.device === client.device && ws.data.token === client.token) > -1) {
// callback(null, 429)
// } else {
// callback(client)
// }
// }
// const ping = setInterval(() => {
// console.log('ping-clients', wss.clients.size)
// wss.clients.forEach((ws) => {
// if (ws.isAlive === false) return ws.terminate()
// ws.isAlive = false
// ws.ping(() => { })
// })
// }, 10000)
// server.on('upgrade', (req, socket, head) => {
// console.log('upgrade', socket.remoteAddress, req.url)
// wssauthenticate(req, (client, error) => {
// if (error || !client) {
// let headers = 'HTTP/1.1 401 Unauthorized\r\n'
// if (error === 429) headers = 'HTTP/1.1 429 Too Many Requests\r\n'
// headers += 'Connection: close\r\n'
// socket.write(headers + '\r\n')
// socket.destroy()
// } else {
// wss.handleUpgrade(req, socket, head, (ws) => {
// ws.data = client
// ws.isAlive = true;
// ws.on('pong', () => {
// console.log('pong-from', JSON.stringify(ws.data))
// ws.isAlive = true
// })
// wss.emit('connection', ws, req)
// })
// }
// })
// })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment