Seperated out socket validation/authorization from channel mangement logic.
This commit is contained in:
parent
d541dce8c4
commit
7da07c8717
|
|
@ -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);
|
||||
|
|
@ -148,70 +149,6 @@ class channelManager{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {Socket} socket - Socket to check
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
|
||||
//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
|
||||
|
|
|
|||
97
src/utils/socketUtils.js
Normal file
97
src/utils/socketUtils.js
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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:<br>${data.reason}`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue