diff --git a/defaultFlair.json b/defaultFlair.json new file mode 100644 index 0000000..6f6e608 --- /dev/null +++ b/defaultFlair.json @@ -0,0 +1,10 @@ +[ + { + "name": "glitter", + "rank": "gold" + }, + { + "name": "negativeGlitter", + "ranke": "gold" + } +] \ No newline at end of file diff --git a/src/app/channel/channelManager.js b/src/app/channel/channelManager.js index 00805ab..9cf2273 100644 --- a/src/app/channel/channelManager.js +++ b/src/app/channel/channelManager.js @@ -16,6 +16,8 @@ along with this program. If not, see .*/ //Local Imports const channelModel = require('../../schemas/channelSchema'); +const flairModel = require('../../schemas/flairSchema'); +const userModel = require('../../schemas/userSchema'); const activeChannel = require('./activeChannel'); const chatHandler = require('./chatHandler'); @@ -26,8 +28,17 @@ module.exports.handleConnection = async function(io, socket){ //Prevent logged out connections and authenticate socket if(socket.request.session.user != null){ try{ + //Find the user in the Database since the session won't store enough data to fulfill our needs :P + const userDB = await userModel.findOne({user: socket.request.session.user.user}); + //Set socket user and channel values - socket.user = socket.request.session.user; + socket.user = { + id: userDB.id, + user: userDB.user, + rank: userDB.rank, + flair: userDB.flair + }; + socket.chanName = socket.handshake.headers.referer.split('/c/')[1]; //Check if channel exists diff --git a/src/app/channel/chatHandler.js b/src/app/channel/chatHandler.js index e8f1734..fe23bec 100644 --- a/src/app/channel/chatHandler.js +++ b/src/app/channel/chatHandler.js @@ -16,9 +16,11 @@ along with this program. If not, see .*/ //NPM Imports const validator = require('validator');//No express here, so regular validator it is! +const loggerUtils = require('../../utils/loggerUtils'); +const userModel = require('../../schemas/userSchema'); module.exports.defineListeners = function(io, socket){ - socket.on("chat-message", (data) => { + socket.on("chatMessage", (data) => { //Trim and Sanatize for XSS const msg = validator.trim(validator.escape(data.msg)); //make sure high is an int @@ -34,6 +36,22 @@ module.exports.defineListeners = function(io, socket){ return; } - io.in(socket.chan).emit("chat-message", {user: socket.user.user, msg, high}); + io.in(socket.chan).emit("chatMessage", {user: socket.user.user, flair: socket.user.flair, high, msg}); + }); + + socket.on("setFlair", async (data) => { + const userDB = await userModel.findOne({user: socket.user.user}); + + if(userDB){ + try{ + //We can take this data raw since our schema checks it against existing flairs, and mongoose sanatizes queries + userDB.flair = data.flair; + await userDB.save(); + }catch(err){ + return loggerUtils.socketExceptionHandler(socket, err); + } + } + + }); } \ No newline at end of file diff --git a/src/schemas/flairSchema.js b/src/schemas/flairSchema.js new file mode 100644 index 0000000..ec43073 --- /dev/null +++ b/src/schemas/flairSchema.js @@ -0,0 +1,62 @@ +/*Canopy - The next generation of stoner streaming software +Copyright (C) 2024 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 .*/ + +//NPM Imports +const {mongoose} = require('mongoose'); + +//Local Imports +const permissionModel = require("./permissionSchema"); +const defaultFlair = require("../../defaultFlair.json"); + +const flairSchema = new mongoose.Schema({ + name:{ + type: mongoose.SchemaTypes.String, + required: true + }, + rank: { + type: mongoose.SchemaTypes.String, + enum: permissionModel.rankEnum, + default: "user", + required: true + } +}); + +flairSchema.statics.loadDefaults = async function(){ + //For each entry in the defaultFlair.json file + defaultFlair.forEach(async (flair) => { + try{ + //Look for flair matching the one from our file + + const foundFlair = await this.findOne({name: flair.name}); + + //if the flair doesn't exist + if(!foundFlair){ + const flairDB = await this.create(flair); + console.log(`Loading default flair '${flair.name} into DB from defaultFlair.json`); + } + + }catch(err){ + if(flair != null){ + console.log(`Error loading flair '${flair.name}':`); + }else{ + console.log("Error, null flair:"); + } + console.log(err); + } + }); +} + +module.exports = mongoose.model("flair", flairSchema); \ No newline at end of file diff --git a/src/schemas/userSchema.js b/src/schemas/userSchema.js index 11608fb..4dc56f6 100644 --- a/src/schemas/userSchema.js +++ b/src/schemas/userSchema.js @@ -19,8 +19,9 @@ const {mongoose} = require('mongoose'); //local imports const server = require('../server.js'); -const statSchema = require('./statSchema.js'); -const permissionSchema = require('./permissionSchema.js'); +const statModel = require('./statSchema.js'); +const flairModel = require('./flairSchema.js'); +const permissionModel = require('./permissionSchema.js'); const hashUtil = require('../utils/hashUtils'); @@ -48,7 +49,7 @@ const userSchema = new mongoose.Schema({ rank: { type: mongoose.SchemaTypes.String, required: true, - enum: permissionSchema.rankEnum, + enum: permissionModel.rankEnum, default: "user" }, tokes: { @@ -75,15 +76,39 @@ const userSchema = new mongoose.Schema({ type: mongoose.SchemaTypes.String, required: true, default: "Signature not set!" - } + }, + flair: { + type: mongoose.SchemaTypes.String, + required: false, + default: "" + } }); //This is one of those places where you really DON'T want to use an arrow function over an anonymous one! userSchema.pre('save', async function (next){ + + //If the password was changed if(this.isModified("pass")){ + //Hash that sunnovabitch, no questions asked. this.pass = hashUtil.hashPassword(this.pass); } + //If the flair was changed + if(this.isModified("flair")){ + //If we're not disabling flair + if(this.flair != ""){ + //Look for the flair that was set + const foundFlair = await flairModel.findOne({name: this.flair}); + + //If new flair value doesn't corrispond to an existing flair + if(!foundFlair){ + //Throw a shit fit. Do not pass go. Do not collect $200. + throw new Error("Invalid flair!"); + } + } + } + + //All is good, continue on saving. next(); }); @@ -97,7 +122,7 @@ userSchema.statics.register = async function(userObj){ if(userDB){ throw new Error("User name/email already taken!"); }else{ - const id = await statSchema.incrementUserCount(); + const id = await statModel.incrementUserCount(); const newUser = await this.create({id, user, pass, email}); } }else{ diff --git a/src/server.js b/src/server.js index cc3d9e8..d9515c7 100644 --- a/src/server.js +++ b/src/server.js @@ -25,6 +25,7 @@ const mongoose = require('mongoose'); //Define Local Imports const statModel = require('./schemas/statSchema'); +const flairModel = require('./schemas/flairSchema'); const channelManager = require('./app/channel/channelManager'); const indexRouter = require('./routers/indexRouter'); const registerRouter = require('./routers/registerRouter'); @@ -105,6 +106,9 @@ app.use(express.static(path.join(__dirname, '../www'))); //Increment launch counter statModel.incrementLaunchCount(); +//Load flairs +flairModel.loadDefaults(); + //Hand over general-namespace socket.io connections to the channel manager io.on("connection", (socket) => {channelManager.handleConnection(io, socket)} ); diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js index 5a3f65b..86fd372 100644 --- a/src/utils/loggerUtils.js +++ b/src/utils/loggerUtils.js @@ -19,4 +19,9 @@ module.exports.exceptionHandler = function(res, err){ //if not yell at the browser for fucking up, and tell it what it did wrong. res.status(400); return res.send({errors: [{type: "Caught Exception", msg: err.message, date: new Date()}]}); +} + +module.exports.socketExceptionHandler = function(socket, err){ + //if not yell at the browser for fucking up, and tell it what it did wrong. + return socket.emit("error", {errors: [{type: "Caught Exception", msg: err.message, date: new Date()}]}); } \ No newline at end of file diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index 5aa4ec5..5622942 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -42,6 +42,10 @@ class channel{ this.socket.on("connect", () => { document.title = `${this.channelName} - Connected` }); + + this.socket.on("error", (data) => { + console.log(data); + }); } } diff --git a/www/js/channel/chat.js b/www/js/channel/chat.js index 153a46c..19cdda2 100644 --- a/www/js/channel/chat.js +++ b/www/js/channel/chat.js @@ -64,7 +64,7 @@ class chatBox{ } defineListeners(){ - this.client.socket.on("chat-message", (data) => { + this.client.socket.on("chatMessage", (data) => { this.displayChat(data); }); } @@ -147,7 +147,7 @@ class chatBox{ async send(event){ if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){ - this.client.socket.emit("chat-message",{msg: this.chatPrompt.value, high: this.highLevel.value}); + this.client.socket.emit("chatMessage",{msg: this.chatPrompt.value, high: this.highLevel.value}); this.chatPrompt.value = ""; } }