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 emoteModel = require('../../schemas/emoteSchema');
|
||||||
const {userModel} = require('../../schemas/user/userSchema');
|
const {userModel} = require('../../schemas/user/userSchema');
|
||||||
const userBanModel = require('../../schemas/user/userBanSchema');
|
const userBanModel = require('../../schemas/user/userBanSchema');
|
||||||
|
const socketUtils = require('../../utils/socketUtils');
|
||||||
const loggerUtils = require('../../utils/loggerUtils');
|
const loggerUtils = require('../../utils/loggerUtils');
|
||||||
const csrfUtils = require('../../utils/csrfUtils');
|
const csrfUtils = require('../../utils/csrfUtils');
|
||||||
const presenceUtils = require('../../utils/presenceUtils');
|
const presenceUtils = require('../../utils/presenceUtils');
|
||||||
|
|
@ -68,7 +69,7 @@ class channelManager{
|
||||||
async handleConnection(socket){
|
async handleConnection(socket){
|
||||||
try{
|
try{
|
||||||
//ensure unbanned ip and valid CSRF token
|
//ensure unbanned ip and valid CSRF token
|
||||||
if(!(await this.validateSocket(socket))){
|
if(!(await socketUtils.validateSocket(socket))){
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +77,7 @@ class channelManager{
|
||||||
//Prevent logged out connections and authenticate socket
|
//Prevent logged out connections and authenticate socket
|
||||||
if(socket.request.session.user != null){
|
if(socket.request.session.user != null){
|
||||||
//Authenticate socket
|
//Authenticate socket
|
||||||
const userDB = await this.authSocket(socket);
|
const userDB = await socketUtils.authSocket(socket);
|
||||||
|
|
||||||
//Get the active channel based on the socket
|
//Get the active channel based on the socket
|
||||||
var {activeChan, chanDB} = await this.getActiveChan(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
|
* Gets active channel from a given socket
|
||||||
* @param {Socket} socket - Socket to check
|
* @param {Socket} socket - Socket to check
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
//local includes
|
//local includes
|
||||||
const server = require('../server');
|
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
|
//User activity map to keep us from constantly reading off of the DB
|
||||||
let activityMap = new Map();
|
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 we wheren't handed a free user doc
|
||||||
if(userDB == null){
|
if(userDB == null){
|
||||||
//Pull one from the username
|
//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
|
//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 we wheren't handed a free user doc
|
||||||
if(userDB == null){
|
if(userDB == null){
|
||||||
//Pull one from the username
|
//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
|
//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) => {
|
this.socket.on("kick", async (data) => {
|
||||||
if(data.reason == "Invalid CSRF Token!"){
|
if(data.reason == "Invalid CSRF Token!"){
|
||||||
//Reload the CSRF token
|
//Warn the user
|
||||||
await utils.ajax.reloadCSRFToken();
|
new canopyUXUtils.popup('Invalid CSRF Token detected, reloading client...');
|
||||||
|
|
||||||
//Retry the connection
|
//Just reload the fucker
|
||||||
this.connect();
|
setTimeout(()=>{location.reload();}, 1000);
|
||||||
}else{
|
}else{
|
||||||
new canopyUXUtils.popup(`You have been ${data.type} from the channel for the following reason:<br>${data.reason}`);
|
new canopyUXUtils.popup(`You have been ${data.type} from the channel for the following reason:<br>${data.reason}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue