diff --git a/src/app/channel/channelManager.js b/src/app/channel/channelManager.js index 4b594ad..b36d7b5 100644 --- a/src/app/channel/channelManager.js +++ b/src/app/channel/channelManager.js @@ -22,6 +22,7 @@ const channelModel = require('../../schemas/channel/channelSchema'); const emoteModel = require('../../schemas/emoteSchema'); const {userModel} = require('../../schemas/user/userSchema'); const userBanModel = require('../../schemas/user/userBanSchema'); +const socketUtils = require('../../utils/socketUtils'); const loggerUtils = require('../../utils/loggerUtils'); const csrfUtils = require('../../utils/csrfUtils'); const presenceUtils = require('../../utils/presenceUtils'); @@ -68,7 +69,7 @@ class channelManager{ async handleConnection(socket){ try{ //ensure unbanned ip and valid CSRF token - if(!(await this.validateSocket(socket))){ + if(!(await socketUtils.validateSocket(socket))){ socket.disconnect(); return; } @@ -76,7 +77,7 @@ class channelManager{ //Prevent logged out connections and authenticate socket if(socket.request.session.user != null){ //Authenticate socket - const userDB = await this.authSocket(socket); + const userDB = await socketUtils.authSocket(socket); //Get the active channel based on the socket var {activeChan, chanDB} = await this.getActiveChan(socket); @@ -146,71 +147,7 @@ class channelManager{ //Flip a table if something fucks up return loggerUtils.socketCriticalExceptionHandler(socket, err); } - } - - /** - * Global server-side validation logic for new connections to any channel - * @param {Socket} socket - Requesting Socket - * @returns {Boolean} true on success - */ - async validateSocket(socket){ - //If we're proxied use passthrough IP - const ip = config.proxied ? socket.handshake.headers['x-forwarded-for'] : socket.handshake.address; - - //Look for ban by IP - const ipBanDB = await userBanModel.checkBanByIP(ip); - - //If this ip is randy bobandy - if(ipBanDB != null){ - //Make the number a little prettier despite the lack of precision since we're not doing calculations here :P - const expiration = ipBanDB.getDaysUntilExpiration() < 1 ? 0 : ipBanDB.getDaysUntilExpiration(); - - //If the ban is permanent - if(ipBanDB.permanent){ - //tell it to fuck off - socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been permanently banned. Your cleartext IP has been saved to the database. Any associated accounts will be nuked in ${expiration} day(s).`}); - //Otherwise - }else{ - //tell it to fuck off - socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been temporarily banned. Your cleartext IP has been saved to the database until the ban expires in ${expiration} day(s).`}); - } - - - return false; - } - - - //Check for Cross-Site Request Forgery - if(!csrfUtils.isRequestValid(socket.request)){ - socket.emit("kick", {type: "disconnected", reason: "Invalid CSRF Token!"}); - return false; - } - - - return true; - } - - /** - * Global server-side authorization logic for new connections to any channel - * @param {Socket} socket - Requesting Socket - * @returns {Mongoose.Document} - Authorized User Document upon success - */ - async authSocket(socket){ - //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}); - - if(userDB == null){ - throw loggerUtils.exceptionSmith("User not found!", "unauthorized"); - } - - //Set socket user and channel values - socket.user = { - id: userDB.id, - user: userDB.user, - }; - - return userDB; - } + } /** * Gets active channel from a given socket diff --git a/src/utils/presenceUtils.js b/src/utils/presenceUtils.js index 0096b69..310db6c 100644 --- a/src/utils/presenceUtils.js +++ b/src/utils/presenceUtils.js @@ -16,7 +16,7 @@ along with this program. If not, see .*/ //local includes const server = require('../server'); -const userSchema = require('../schemas/user/userSchema'); +const {userModel} = require('../schemas/user/userSchema'); //User activity map to keep us from constantly reading off of the DB let activityMap = new Map(); @@ -47,7 +47,7 @@ module.exports.getPresence = async function(user, userDB){ //If we wheren't handed a free user doc if(userDB == null){ //Pull one from the username - userDB = await userSchema.userModel.findOne({user: user}); + userDB = await userModel.findOne({user: user}); } //If for some reason we can't find a user doc @@ -119,7 +119,7 @@ module.exports.handlePresence = async function(user, userDB, noSave = false){ //If we wheren't handed a free user doc if(userDB == null){ //Pull one from the username - userDB = await userSchema.userModel.findOne({user: user}); + userDB = await userModel.findOne({user: user}); } //Set last active in user's DB document diff --git a/src/utils/socketUtils.js b/src/utils/socketUtils.js new file mode 100644 index 0000000..68fc46a --- /dev/null +++ b/src/utils/socketUtils.js @@ -0,0 +1,97 @@ +/*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 .*/ + +const config = require('../../config.json'); +const csrfUtils = require('./csrfUtils'); +const {userModel} = require('../schemas/user/userSchema'); +const userBanModel = require('../schemas/user/userBanSchema'); + +module.exports.validateSocket = async function(socket, quiet = false){ + //If we're proxied use passthrough IP + const ip = config.proxied ? socket.handshake.headers['x-forwarded-for'] : socket.handshake.address; + + //Look for ban by IP + const ipBanDB = await userBanModel.checkBanByIP(ip); + + //If this ip is randy bobandy + if(ipBanDB != null){ + //Make the number a little prettier despite the lack of precision since we're not doing calculations here :P + const expiration = ipBanDB.getDaysUntilExpiration() < 1 ? 0 : ipBanDB.getDaysUntilExpiration(); + + if(quiet){ + socket.disconnect(); + }else{ + //If the ban is permanent + if(ipBanDB.permanent){ + //tell it to fuck off + socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been permanently banned. Your cleartext IP has been saved to the database. Any associated accounts will be nuked in ${expiration} day(s).`}); + //Otherwise + }else{ + //tell it to fuck off + socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been temporarily banned. Your cleartext IP has been saved to the database until the ban expires in ${expiration} day(s).`}); + } + } + + return false; + } + + + //Check for Cross-Site Request Forgery + if(!csrfUtils.isRequestValid(socket.request)){ + if(quiet){ + socket.disconnect(); + }else{ + socket.emit("kick", {type: "disconnected", reason: "Invalid CSRF Token!"}); + } + + return false; + } + + + return true; +} + +//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; + + //Set socket user and channel values + socket.user = { + id: user.id, + user: user.user, + }; + + //return user object from session + return user; +} + +module.exports.authSocket = async function(socket){ + //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}); + + if(userDB == null){ + throw loggerUtils.exceptionSmith("User not found!", "unauthorized"); + } + + //Set socket user and channel values + socket.user = { + id: userDB.id, + user: userDB.user, + }; + + return userDB; +} \ No newline at end of file diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index f7955d1..0377d1e 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -89,11 +89,11 @@ class channel{ this.socket.on("kick", async (data) => { if(data.reason == "Invalid CSRF Token!"){ - //Reload the CSRF token - await utils.ajax.reloadCSRFToken(); + //Warn the user + new canopyUXUtils.popup('Invalid CSRF Token detected, reloading client...'); - //Retry the connection - this.connect(); + //Just reload the fucker + setTimeout(()=>{location.reload();}, 1000); }else{ new canopyUXUtils.popup(`You have been ${data.type} from the channel for the following reason:
${data.reason}`); }