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