From 61deb1afa72328d8d50c7aacd45ce463c04594a9 Mon Sep 17 00:00:00 2001 From: knotteye Date: Sat, 7 Dec 2019 21:23:50 -0600 Subject: [PATCH] Minor improvements to socket.io chat, including banning and unbanning per room, and spam detection and server bans --- .gitignore | 1 + package-lock.json | 15 ++++++- package.json | 1 + src/http.ts | 99 +++++++++++++++++++++++++++++++++++++++------ templates/chat.html | 18 +++++++++ 5 files changed, 120 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index fad7bdd..116395e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ site/live config/local.toml config/jwt.pem config/generated.toml +config/bans.db install/db_setup.sql build/** diff --git a/package-lock.json b/package-lock.json index 9c53b90..379f6ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "satyr", - "version": "0.4.4", + "version": "0.5.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2043,6 +2043,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2712,6 +2717,14 @@ } } }, + "socket-anti-spam": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/socket-anti-spam/-/socket-anti-spam-2.0.0.tgz", + "integrity": "sha512-glCDT8LrqwSY+tQJtvaz3YwTw1HL6bgWVvaQFumkClOcF+Jbg0NlAImqQabowNJcrCxr1dibKRoAvIfN98FKVw==", + "requires": { + "moment": "^2.21.0" + } + }, "socket.io": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", diff --git a/package.json b/package.json index f98013f..82dcedf 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "node-media-server": ">=2.1.3 <3.0.0", "nunjucks": "^3.2.0", "recursive-readdir": "^2.2.2", + "socket-anti-spam": "^2.0.0", "socket.io": "^2.3.0", "strftime": "^0.10.0", "toml": "^3.0.0", diff --git a/src/http.ts b/src/http.ts index 47b00e6..7f05cd7 100644 --- a/src/http.ts +++ b/src/http.ts @@ -5,6 +5,7 @@ import * as socketio from "socket.io"; import * as http from "http"; import * as cookies from "cookie-parser"; import * as dirty from "dirty"; +import * as socketSpam from "socket-anti-spam"; import * as api from "./api"; import * as db from "./database"; import * as irc from "./irc"; @@ -18,6 +19,8 @@ const app = express(); const server = http.createServer(app); const io = socketio(server); const store = dirty(); +var banlist; +var ircconf; var jwkey; try{ jwkey = JWK.asKey(readFileSync('./config/jwt.pem')); @@ -28,7 +31,8 @@ try{ } var njkconf; -async function init(satyr: any, http: object, ircconf: any){ +async function init(satyr: any, http: object, irc: any){ + ircconf = irc; njk.configure('templates', { autoescape : true, express : app, @@ -65,19 +69,22 @@ async function init(satyr: any, http: object, ircconf: any){ else res.status(404).render('404.njk', njkconf); //res.status(404).render('404.njk', njkconf); }); - await initChat(ircconf); + banlist = new dirty('./config/bans.db').on('load', () => {initChat()}); server.listen(http['port']); } -async function newNick(socket, skip?: boolean) { +async function newNick(socket, skip?: boolean, i?: number) { if(socket.handshake.headers['cookie'] && !skip){ let c = await parseCookie(socket.handshake.headers['cookie']); let t = await validToken(c['Authorization']); - if(t) return t['username']; + if(t) { + store.set(t, socket.id); + return t['username']; + } } - //i just realized how shitty of an idea this is - let n: string = 'Guest'+Math.floor(Math.random() * Math.floor(1000)); - if(store.get(n)) return newNick(socket, true); + if(!i) i = 10; + let n: string = 'Guest'+Math.floor(Math.random() * Math.floor(i)); + if(store.get(n)) return newNick(socket, true, Math.floor(i * 10)); else { store.set(n, socket.id); return n; @@ -90,7 +97,7 @@ async function chgNick(socket, nick, f?: boolean) { io.to(rooms[i]).emit('ALERT', socket.nick+' is now known as '+nick); } if(store.get(socket.nick)) store.rm(socket.nick); - if (!f) store.set(nick, socket.id); + store.set(nick, socket.id); socket.nick = nick; } @@ -328,7 +335,7 @@ async function initSite(openReg) { }); } -async function initChat(ircconf: any) { +async function initChat() { //irc peering if(ircconf.enable){ await irc.connect({ @@ -349,6 +356,15 @@ async function initChat(ircconf: any) { socket.on('JOINROOM', async (data) => { let t: any = await db.query('select username from users where username='+db.raw.escape(data)); if(t[0]){ + if(banlist.get(data) && banlist.get(data)[socket.ip]){ + if(Math.floor(banlist.get(data)[socket.ip]['time'] + (banlist.get(data)[socket.ip]['length'] * 60)) < Math.floor(Date.now() / 1000)){ + banlist.set('data', Object.assign(banlist['data'], {[socket.ip]: null})); + } + else { + socket.emit('ALERT', 'You are banned from that room'); + return; + } + } socket.join(data); io.to(data).emit('JOINED', {nick: socket.nick}); if(ircconf.enable) irc.join(socket.nick, data); @@ -386,10 +402,6 @@ async function initChat(ircconf: any) { }); socket.on('NICK', async (data) => { data.nick = data.nick.replace(' ',''); - if(store.get(data.nick)){ - socket.emit('ALERT', 'Nickname is already in use'); - return false; - } let user = await db.query('select username from users where username='+db.raw.escape(data.nick)); if(user[0]){ if(!data.password){ @@ -402,6 +414,10 @@ async function initChat(ircconf: any) { else socket.emit('ALERT','Incorrect username or password'); } else { + if(store.get(data.nick)){ + socket.emit('ALERT', 'Nickname is already in use'); + return false; + } chgNick(socket, data.nick); } }); @@ -423,6 +439,63 @@ async function initChat(ircconf: any) { } else socket.emit('ALERT', 'Not authorized to do that.'); }); + socket.on('BAN', (data: Object) => { + if(socket.nick === data['room']){ + let id: string = store.get(data['nick']); + if(id){ + let target = io.sockets.connected[id]; + if(typeof(data['time']) === 'number' && (data['time'] !== 0 || data['time'] !== NaN)) banlist.set(data['room'], Object.assign({[target.ip]: {time: Math.floor(Date.now() / 1000), length: data['time']}}, banlist.get(data['room']))); + else banlist.set(data['room'], Object.assign({[target.ip]: {time: Math.floor(Date.now() / 1000), length: 30}}, banlist.get(data['room']))); + target.disconnect(true); + io.to(data['room']).emit('ALERT', target.nick+' was banned.'); + } + else socket.emit('ALERT', 'No such user found.'); + } + else socket.emit('ALERT', 'Not authorized to do that.'); + }); + socket.on('UNBAN', (data: Object) => { + if(socket.nick === data['room']){ + if(banlist.get(data['room']) && banlist.get(data['room'])[data['ip']]){ + banlist.set(data['room'], Object.assign(banlist.get(data['room']), {[data['ip']]: null})); + socket.emit('ALERT', data['ip']+' was unbanned.'); + } + else + socket.emit('ALERT', 'That IP is not banned.'); + } + else socket.emit('ALERT', 'Not authorized to do that.'); + }); + socket.on('LISTBAN', (data: Object) => { + if(socket.nick === data['room']){ + if(banlist.get(data['room'])) { + let bans = Object.keys(banlist.get(data['room'])); + let str = ''; + for(let i=0;i { + let rooms = Object.keys(socket.rooms); + for(let i=1;i