From 6e785dc21182cb32daccbeb20b802717c047ce3a Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Mon, 23 Dec 2024 15:17:07 -0500 Subject: [PATCH] Database now stores hashes of recently used IP's for all users. They are retained for seven days AFTER their LAST use. --- src/app/channel/activeChannel.js | 7 +-- src/app/channel/connectedUser.js | 13 +++++ src/schemas/userSchema.js | 91 +++++++++++++++++++++++++++++++- src/utils/scheduler.js | 7 +++ src/utils/sessionUtils.js | 5 +- www/js/channel/cpanel.js | 1 - 6 files changed, 112 insertions(+), 12 deletions(-) diff --git a/src/app/channel/activeChannel.js b/src/app/channel/activeChannel.js index 452f363..5a6db64 100644 --- a/src/app/channel/activeChannel.js +++ b/src/app/channel/activeChannel.js @@ -52,12 +52,7 @@ module.exports = class{ //if everything looks good, admit the connection to the channel socket.join(socket.chan); - //Make sure the client receives important client-info before userlist - //await this.sendClientMetadata(userDB, socket); - await userObj.sendClientMetadata(); - await userObj.sendSiteEmotes(); - await userObj.sendChanEmotes(chanDB); - await userObj.sendPersonalEmotes(userDB); + await userObj.handleConnection(userDB, chanDB, socket) //Send out the userlist this.broadcastUserList(socket.chan); diff --git a/src/app/channel/connectedUser.js b/src/app/channel/connectedUser.js index fc7ff43..3e40868 100644 --- a/src/app/channel/connectedUser.js +++ b/src/app/channel/connectedUser.js @@ -33,6 +33,19 @@ module.exports = class{ this.sockets = [socket.id]; } + async handleConnection(userDB, chanDB, socket){ + //send metadata to client + await this.sendClientMetadata(); + + //Send out emotes + await this.sendSiteEmotes(); + await this.sendChanEmotes(chanDB); + await this.sendPersonalEmotes(userDB); + + //Tattoo hashed IP address to user account for seven days + await userDB.tattooIPRecord(socket.handshake.address); + } + socketCrawl(cb){ //Crawl through user's sockets (lol) this.sockets.forEach((sockid) => { diff --git a/src/schemas/userSchema.js b/src/schemas/userSchema.js index eb34147..6878414 100644 --- a/src/schemas/userSchema.js +++ b/src/schemas/userSchema.js @@ -14,6 +14,10 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ + +//Built-In Imports +const crypto = require('crypto'); + //NPM Imports const {mongoose} = require('mongoose'); @@ -115,6 +119,22 @@ const userSchema = new mongoose.Schema({ enum: emoteModel.typeEnum, default: emoteModel.typeEnum[0] } + }], + recentIPs: [{ + ipHash: { + type: mongoose.SchemaTypes.String, + required: true + }, + firstLog: { + type: mongoose.SchemaTypes.Date, + required: true, + default: new Date() + }, + lastLog: { + type: mongoose.SchemaTypes.Date, + required: true, + default: new Date() + } }] }); @@ -143,7 +163,9 @@ userSchema.pre('save', async function (next){ this.flair = flairDB._id; } + //If rank was changed if(this.isModified("rank")){ + //force a full log-out await this.killAllSessions("Your site-wide rank has changed. Sign-in required."); } @@ -302,6 +324,30 @@ userSchema.statics.getUserList = async function(fullList = false){ return userList; } +userSchema.statics.processAgedIPRecords = async function(){ + //Pull full userlist + const users = await this.find({}); + + //for every user + users.forEach((userDB) => { + //For every recent ip within the user + userDB.recentIPs.forEach((record, recordI) => { + //Check how long it's been since we've last seen the IP + const daysSinceLastUse = ((new Date() - record.lastLog) / (1000 * 60 * 60 * 24)).toFixed(1); + + //If it's been more than a week + if(daysSinceLastUse >= 7){ + //Splice out the IP record + userDB.recentIPs.splice(recordI, 1); + //No reason to wait on this since we're done with this user + userDB.save(); + } + }); + }); +} + + + //methods userSchema.methods.checkPass = function(pass){ return hashUtil.comparePassword(pass, this.pass); @@ -328,9 +374,7 @@ userSchema.methods.getAuthenticatedSessions = async function(){ } }); - resolve(returnArr); - }); }); @@ -412,6 +456,49 @@ userSchema.methods.deleteEmote = async function(name){ } } +userSchema.methods.tattooIPRecord = async function(ip){ + //Create hash + const hashObj = crypto.createHash('md5'); + + //add IP to the hash + hashObj.update(ip); + + //Store the IP hash as a string + const ipHash = hashObj.digest('hex'); + + //Look for a pre-existing entry for this ipHash + const foundIndex = this.recentIPs.findIndex(checkHash); + + //If there is no entry + if(foundIndex == -1){ + //create record object + const record = { + ipHash: ipHash, + firstLog: new Date(), + lastLog: new Date() + }; + + //Pop it into place + this.recentIPs.push(record); + + //Save the user doc + await this.save(); + //Otherwise, if we already have a record for this IP + }else{ + //Update the last logged date for the found record + this.recentIPs[foundIndex].lastLog = new Date(); + + //Save the user doc + await this.save(); + } + + //Look for matching ip record + function checkHash(ipRecord){ + //return matching records + return ipRecord.ipHash == ipHash; + } +} + //note: if you gotta call this from a request authenticated by it's user, make sure to kill that session first! userSchema.methods.killAllSessions = async function(reason = "A full log-out from all devices was requested for your account."){ //get authenticated sessions diff --git a/src/utils/scheduler.js b/src/utils/scheduler.js index 840ee14..83803b4 100644 --- a/src/utils/scheduler.js +++ b/src/utils/scheduler.js @@ -18,15 +18,22 @@ along with this program. If not, see .*/ const cron = require('node-cron'); //Local Imports +const {userModel} = require('../schemas/userSchema'); const userBanModel = require('../schemas/userBanSchema'); const channelModel = require('../schemas/channel/channelSchema'); module.exports.schedule = function(){ + //Process hashed IP Records that haven't been recorded in a week or more + cron.schedule('0 0 * * *', ()=>{userModel.processAgedIPRecords()},{scheduled: true, timezone: "UTC"}); //Process expired global bans every night at midnight cron.schedule('0 0 * * *', ()=>{userBanModel.processExpiredBans()},{scheduled: true, timezone: "UTC"}); + //Process expired channel bans every night at midnight + cron.schedule('0 0 * * *', ()=>{channelModel.processExpiredBans()},{scheduled: true, timezone: "UTC"}); } module.exports.kickoff = function(){ + //Process Hashed IP Records that haven't been recorded in a week or more + userModel.processAgedIPRecords(); //Process expired global bans that may have expired since last restart userBanModel.processExpiredBans() //Process expired channel bans that may haven ot expired since last restart diff --git a/src/utils/sessionUtils.js b/src/utils/sessionUtils.js index b71a3b0..8f42ab3 100644 --- a/src/utils/sessionUtils.js +++ b/src/utils/sessionUtils.js @@ -48,9 +48,8 @@ module.exports.authenticateSession = async function(user, pass, req){ rank: userDB.rank } - - //userDB.activeSessions.push(sessionData); - await userDB.save(); + //Tattoo hashed IP address to user account for seven days + userDB.tattooIPRecord(req.ip); //return user return userDB.user; diff --git a/www/js/channel/cpanel.js b/www/js/channel/cpanel.js index 560be45..d451d68 100644 --- a/www/js/channel/cpanel.js +++ b/www/js/channel/cpanel.js @@ -165,7 +165,6 @@ class panelObj{ } closer(){ - console.log('closer'); } }