From 67edef9035932b0ae5c6e383329acbf4b0d170c7 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Fri, 19 Sep 2025 03:47:19 -0400 Subject: [PATCH] Base implementation of PM back-end started. --- src/app/pm/message.js | 8 +-- src/app/pm/pmHandler.js | 125 +++++++++++++++++++++++++++++++++++- src/utils/loggerUtils.js | 29 ++++++++- src/utils/socketUtils.js | 8 ++- www/js/channel/pmHandler.js | 24 +++++++ 5 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 www/js/channel/pmHandler.js diff --git a/src/app/pm/message.js b/src/app/pm/message.js index eef6658..f2c216c 100644 --- a/src/app/pm/message.js +++ b/src/app/pm/message.js @@ -20,20 +20,20 @@ along with this program. If not, see .*/ class message{ /** * Instantiates a chat message object - * @param {connectedUser} sender - User who sent the message - * @param {Array} recipients - Array of connected users who are supposed to receive the message + * @param {String} sender - Name of user who sent the message + * @param {Array} recipients - Array of usernames who are supposed to receive the message * @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers * @param {Array} links - Array of URLs/Links included in the message. */ constructor(sender, recipients, msg, links){ /** - * User who sent the message + * Name of user who sent the message */ this.sender = sender; /** - * Array of strings containing usernames to send message to + * Array of usernames who are supposed to receive the message */ this.recipients = recipients; diff --git a/src/app/pm/pmHandler.js b/src/app/pm/pmHandler.js index 29b4ad1..01af129 100644 --- a/src/app/pm/pmHandler.js +++ b/src/app/pm/pmHandler.js @@ -14,10 +14,13 @@ 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 .*/ +//NPM Imports +const validator = require('validator');//No express here, so regular validator it is! + //local includes -const config = require("../../../config.json"); -const csrfUtils = require("../../utils/csrfUtils"); -const userBanModel = require("../../schemas/user/userBanSchema"); +const loggerUtils = require("../../utils/loggerUtils"); +const socketUtils = require("../../utils/socketUtils"); +const message = require("./message"); /** * Class containg global server-side private message relay logic @@ -43,7 +46,123 @@ class pmHandler{ } async handleConnection(socket){ + try{ + //ensure unbanned ip and valid CSRF token + if(!(await socketUtils.validateSocket(socket))){ + socket.disconnect(); + return; + } + //If the socket wasn't authorized + if(await socketUtils.authSocketLite(socket) == null){ + socket.disconnect(); + return; + } + + //Throw socket into room named after it's user + socket.join(socket.user.user); + + //Define network related event listeners against socket + this.defineListeners(socket); + }catch(err){ + //Flip a table if something fucks up + return loggerUtils.socketCriticalExceptionHandler(socket, err); + } + } + + defineListeners(socket){ + socket.on("pm", (data)=>{this.handlePM(data, socket)}); + } + + async handlePM(data, socket){ + try{ + //Create empty list of recipients + let recipients = []; + + //For each requested recipient + for(let user of data.recipients){ + //If the given user is online and didn't send the message + if(this.checkPresence(user) && user != socket.user.user){ + //Add the recipient to the list + recipients.push(user); + } + } + + //If we don't have any valid recipients + if(recipients.length <= 0){ + //Drop that shit + return; + } + + //Sanatize Message + const msg = this.sanatizeMessage(data.msg); + + //If we have an invalid message + if(msg == null){ + //Drop that shit + return; + } + + //Create message object and relay it off to the recipients + this.relayPMObj(new message( + socket.user.user, + recipients, + msg, + [] + )); + + //If something fucked up + }catch(err){ + //Bitch and moan + return loggerUtils.socketExceptionHandler(socket, err); + } + } + + relayPMObj(msg){ + //For each recipient + for(let user of msg.recipients){ + //Send the message + this.namespace.to(user).emit("message", msg); + } + + //Acknowledge the sent message + this.namespace.to(msg.sender).emit("sent", msg); + } + + /** + * Basic function for checking presence + * This could be done using Channel Presence, but running off of bare Socket.io functionality makes this easier to implement outside the channel if need be + * @param {String} user - Username to check presence of + * @returns {Boolean} Whether or not the user is currently able to accept messages + */ + checkPresence(user){ + //Pull room map from the guts of socket.io and run a null check against the given username + return this.namespace.adapter.rooms.get(user) != null; + } + + /** + * Sanatizes and Validates a single message, Temporary until we get commandPreprocessor split up. + * @param {String} msg - message to validate/sanatize + * @returns {String} sanatized/validates message, returns null on validation failure + */ + sanatizeMessage(msg){ + //if msg is empty or null + if(msg == null || msg == ''){ + //Pimp slap that shit into fucking oblivion + return null; + } + + //Trim and Sanatize for XSS + msg = validator.trim(validator.escape(msg)); + + //Return whether or not the shit was long enough + if(validator.isLength(msg, {min: 1, max: 255})){ + //If it's valid return the message + return msg; + } + + //if not return nothing + return null; } } diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js index 4c6b2cf..a3100e8 100644 --- a/src/utils/loggerUtils.js +++ b/src/utils/loggerUtils.js @@ -173,17 +173,40 @@ module.exports.errorMiddleware = function(err, req, res, next){ * @param {Error} err - error to dump to file * @param {Date} date - Date of error, defaults to now */ -module.exports.dumpError = function(err, date = new Date()){ +module.exports.dumpError = async function(err, date = new Date()){ try{ - const content = `Error Date: ${date.toLocaleString()} (UTC-${date.getTimezoneOffset()/60})\nError Type: ${err.name}\nError Msg:${err.message}\nStack Trace:\n\n${err.stack}`; - const path = `log/crash/${date.getTime()}.log`; + //Crash directory + const dir = "./log/crash/" + //Double check crash folder exists + try{ + await fs.stat(dir); + //If we caught an error (most likely it's missing) + }catch(err){ + //Shout about it + module.exports.consoleWarn("Log folder missing, mking dir!") + + //Make it if doesn't + await fs.mkdir(dir, {recursive: true}); + } + + //Assemble log file path + const path = `${dir}${date.getTime()}.log`; + //Generate error file content + const content = `Error Date: ${date.toLocaleString()} (UTC-${date.getTimezoneOffset()/60})\nError Type: ${err.name}\nError Msg:${err.message}\nStack Trace:\n\n${err.stack}`; + + //Write content to file fs.writeFile(path, content); + //Whine about the error module.exports.consoleWarn(`Warning: Unexpected Server Crash gracefully dumped to '${path}'... SOMETHING MAY BE VERY BROKEN!!!!`); + //If somethine went really really wrong }catch(doubleErr){ + //Use humor to cope with the pain module.exports.consoleWarn("Yo Dawg, I herd you like errors, so I put an error in your error dump, so you can dump while you dump:"); + //Dump the original error to console module.exports.consoleWarn(err); + //Dump the error we had saving that error to file to console module.exports.consoleWarn(doubleErr); } } \ No newline at end of file diff --git a/src/utils/socketUtils.js b/src/utils/socketUtils.js index 68fc46a..765ea02 100644 --- a/src/utils/socketUtils.js +++ b/src/utils/socketUtils.js @@ -67,7 +67,11 @@ module.exports.validateSocket = async function(socket, quiet = false){ //socket.request.session is already trusted, we don't actually need to verify against DB for authorzation //It's just a useful place to grab the DB doc, and is mostly a stand-in from when the only socket-related code was in the channel folder module.exports.authSocketLite = async function(socket){ - const user = socket.request.session.user; + const user = socket.request.session.user; + + if(user == null){ + return null; + } //Set socket user and channel values socket.user = { @@ -84,7 +88,7 @@ module.exports.authSocket = async function(socket){ const userDB = await userModel.findOne({user: socket.request.session.user.user}); if(userDB == null){ - throw loggerUtils.exceptionSmith("User not found!", "unauthorized"); + return null; } //Set socket user and channel values diff --git a/www/js/channel/pmHandler.js b/www/js/channel/pmHandler.js new file mode 100644 index 0000000..94f392b --- /dev/null +++ b/www/js/channel/pmHandler.js @@ -0,0 +1,24 @@ +/*Canopy - The next generation of stoner streaming software +Copyright (C) 2024-2025 Rainbownapkin and the TTN Community + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +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 .*/ + +/** + * Class for handling incoming Private Messages + */ +class pmHandler{ + constructor(client){ + this.client = client; + } +} \ No newline at end of file