From 83f76af6e8290a330d377cd3c33aeeeb199ceb27 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 29 Dec 2024 15:02:37 -0500 Subject: [PATCH] Added CSRF tokens to non-partial templates. --- package.json | 1 + src/controllers/adminPanelController.js | 6 ++++- src/controllers/channelController.js | 5 +++- src/controllers/channelSettingsController.js | 5 ++-- src/controllers/indexController.js | 5 ++-- src/controllers/loginController.js | 9 ++++--- src/controllers/newChannelController.js | 3 ++- src/controllers/passwordResetController.js | 5 ++-- src/controllers/profileController.js | 12 ++++++--- src/controllers/registerController.js | 3 ++- src/schemas/user/userSchema.js | 7 ++++-- src/utils/csrfUtils.js | 26 ++++++++++++++++++++ src/utils/sessionUtils.js | 3 +-- src/views/adminPanel.ejs | 1 + src/views/channel.ejs | 1 + src/views/channelSettings.ejs | 1 + src/views/index.ejs | 1 + src/views/lockedAccount.ejs | 1 + src/views/login.ejs | 1 + src/views/newChannel.ejs | 1 + src/views/partial/csrfToken.ejs | 16 ++++++++++++ src/views/passwordReset.ejs | 1 + src/views/profile.ejs | 1 + src/views/register.ejs | 1 + 24 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 src/utils/csrfUtils.js create mode 100644 src/views/partial/csrfToken.ejs diff --git a/package.json b/package.json index 01ab99a..a5561b7 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "bcrypt": "^5.1.1", "bootstrap-icons": "^1.11.3", "connect-mongo": "^5.1.0", + "csrf-sync": "^4.0.3", "ejs": "^3.1.10", "express": "^4.18.2", "express-session": "^1.18.0", diff --git a/src/controllers/adminPanelController.js b/src/controllers/adminPanelController.js index d2ed500..1df81c8 100644 --- a/src/controllers/adminPanelController.js +++ b/src/controllers/adminPanelController.js @@ -16,9 +16,12 @@ along with this program. If not, see .*/ //Config const config = require('../../config.json'); + +//Local Imports const {userModel} = require('../schemas/user/userSchema'); const permissionModel = require('../schemas/permissionSchema'); const channelModel = require('../schemas/channel/channelSchema'); +const csrfUtils = require('../utils/csrfUtils'); const {exceptionHandler, errorHandler} = require("../utils/loggerUtils"); //register page functions @@ -41,7 +44,8 @@ module.exports.get = async function(req, res){ rankEnum: permissionModel.rankEnum, chanGuide: chanGuide, userList: userList, - permList: permList + permList: permList, + csrfToken: csrfUtils.generateToken(req) }); }catch(err){ diff --git a/src/controllers/channelController.js b/src/controllers/channelController.js index 5f3fa6d..f6cf627 100644 --- a/src/controllers/channelController.js +++ b/src/controllers/channelController.js @@ -17,7 +17,10 @@ along with this program. If not, see .*/ //Config const config = require('../../config.json'); +//Local Imports +const csrfUtils = require('../utils/csrfUtils'); + //channel functions module.exports.get = function(req, res){ - res.render('channel', {instance: config.instanceName, user: req.session.user}); + res.render('channel', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/controllers/channelSettingsController.js b/src/controllers/channelSettingsController.js index 4aa024f..b97cdc0 100644 --- a/src/controllers/channelSettingsController.js +++ b/src/controllers/channelSettingsController.js @@ -18,9 +18,10 @@ along with this program. If not, see .*/ const config = require('../../config.json'); //local imports -const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); const channelModel = require('../schemas/channel/channelSchema'); const permissionModel = require('../schemas/permissionSchema'); +const csrfUtils = require('../utils/csrfUtils'); +const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); //root index functions module.exports.get = async function(req, res){ @@ -38,7 +39,7 @@ module.exports.get = async function(req, res){ throw new Error("Channel not found."); } - return res.render('channelSettings', {instance: config.instanceName, user: req.session.user, channel: chanDB, reqRank, rankEnum: permissionModel.rankEnum}); + return res.render('channelSettings', {instance: config.instanceName, user: req.session.user, channel: chanDB, reqRank, rankEnum: permissionModel.rankEnum, csrfToken: csrfUtils.generateToken(req)}); }catch(err){ return exceptionHandler(res, err); } diff --git a/src/controllers/indexController.js b/src/controllers/indexController.js index aa8dadf..ac330ab 100644 --- a/src/controllers/indexController.js +++ b/src/controllers/indexController.js @@ -18,14 +18,15 @@ along with this program. If not, see .*/ const config = require('../../config.json'); //local imports -const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); const channelModel = require('../schemas/channel/channelSchema'); +const csrfUtils = require('../utils/csrfUtils'); +const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); //root index functions module.exports.get = async function(req, res){ try{ const chanGuide = await channelModel.getChannelList(); - return res.render('index', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide}); + return res.render('index', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide, csrfToken: csrfUtils.generateToken(req)}); }catch(err){ return exceptionHandler(res, err); } diff --git a/src/controllers/loginController.js b/src/controllers/loginController.js index 565542f..16fa137 100644 --- a/src/controllers/loginController.js +++ b/src/controllers/loginController.js @@ -23,6 +23,7 @@ const {validationResult, matchedData} = require('express-validator'); //Local Imports const sessionUtils = require('../utils/sessionUtils'); const altchaUtils = require('../utils/altchaUtils'); +const csrfUtils = require('../utils/csrfUtils'); //register page functions module.exports.get = async function(req, res){ @@ -44,7 +45,7 @@ module.exports.get = async function(req, res){ //if we have previous attempts for this user if(attempts != null){ if(attempts.count > sessionUtils.maxAttempts){ - return res.render('lockedAccount', {instance: config.instanceName, user: req.session.user}); + return res.render('lockedAccount', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); } //If the users login's are being throttled @@ -55,16 +56,16 @@ module.exports.get = async function(req, res){ const challenge = await altchaUtils.genCaptcha(difficulty, user); //Render page - return res.render('login', {instance: config.instanceName, user: req.session.user, challenge}); + return res.render('login', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); } //otherwise }else{ //Render generic page - return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null}); + return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)}); } //if we received invalid input }else{ //Render pretend nothing happened, send out a generic page - return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null}); + return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)}); } } \ No newline at end of file diff --git a/src/controllers/newChannelController.js b/src/controllers/newChannelController.js index 8531941..7d90a7e 100644 --- a/src/controllers/newChannelController.js +++ b/src/controllers/newChannelController.js @@ -16,6 +16,7 @@ along with this program. If not, see .*/ //Local Imports const altchaUtils = require('../utils/altchaUtils'); +const csrfUtils = require('../utils/csrfUtils'); //Config const config = require('../../config.json'); @@ -26,5 +27,5 @@ module.exports.get = async function(req, res){ const challenge = await altchaUtils.genCaptcha(); //render the page - return res.render('newChannel', {instance: config.instanceName, user: req.session.user, challenge}); + return res.render('newChannel', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/controllers/passwordResetController.js b/src/controllers/passwordResetController.js index 4f4cf78..a475d02 100644 --- a/src/controllers/passwordResetController.js +++ b/src/controllers/passwordResetController.js @@ -22,6 +22,7 @@ const {validationResult, matchedData} = require('express-validator'); //Local Imports const altchaUtils = require('../utils/altchaUtils'); +const csrfUtils = require('../utils/csrfUtils'); //register page functions module.exports.get = async function(req, res){ @@ -46,11 +47,11 @@ module.exports.get = async function(req, res){ */ //Render page - return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token}); + return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token, csrfToken: csrfUtils.generateToken(req)}); //If we didn't get a valid token }else{ //otherwise render generic page - return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token: null}); + return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token: null, csrfToken: csrfUtils.generateToken(req)}); } }catch(err){ return exceptionHandler(res, err); diff --git a/src/controllers/profileController.js b/src/controllers/profileController.js index 5315b0c..a7e9da5 100644 --- a/src/controllers/profileController.js +++ b/src/controllers/profileController.js @@ -16,6 +16,7 @@ along with this program. If not, see .*/ //Local Imports const {userModel} = require('../schemas/user/userSchema'); +const csrfUtils = require('../utils/csrfUtils'); const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); //Config @@ -27,18 +28,21 @@ module.exports.get = async function(req, res){ try{ var profileName = req.url.slice(1) == '' ? (req.session.user ? req.session.user.user : null) : req.url.slice(1); - const profile = await userModel.findProfile({user: profileName}) + const profile = await userModel.findProfile({user: profileName}); if(profile){ res.render('profile', { instance: config.instanceName, user: req.session.user, - profile + profile, + csrfToken: csrfUtils.generateToken(req) }); }else{ - res.render('profile', {instance: config.instanceName, + res.render('profile', { + instance: config.instanceName, user: req.session.user, - profile: null + profile: null, + csrfToken: csrfUtils.generateToken(req) }); } }catch(err){ diff --git a/src/controllers/registerController.js b/src/controllers/registerController.js index dc303ed..414867d 100644 --- a/src/controllers/registerController.js +++ b/src/controllers/registerController.js @@ -19,6 +19,7 @@ const config = require('../../config.json'); //Local Imports const altchaUtils = require('../utils/altchaUtils'); +const csrfUtils = require('../utils/csrfUtils'); //register page functions module.exports.get = async function(req, res){ @@ -26,5 +27,5 @@ module.exports.get = async function(req, res){ const challenge = await altchaUtils.genCaptcha(); //Render page - return res.render('register', {instance: config.instanceName, user: req.session.user, challenge}); + return res.render('register', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/schemas/user/userSchema.js b/src/schemas/user/userSchema.js index ea6d1f7..07aa6fa 100644 --- a/src/schemas/user/userSchema.js +++ b/src/schemas/user/userSchema.js @@ -269,10 +269,13 @@ userSchema.statics.authenticate = async function(user, pass){ } userSchema.statics.findProfile = async function(user){ + //Catch null users + if(user == null || user.user == null){ + return null; //If someone's looking for tokebot - if(user.user.toLowerCase() == "tokebot"){ + }else if(user.user.toLowerCase() == "tokebot"){ //fake a profile hashtable for tokebot - profile = { + const profile = { id: -420, user: "Tokebot", date: (await statModel.getStats()).firstLaunch, diff --git a/src/utils/csrfUtils.js b/src/utils/csrfUtils.js new file mode 100644 index 0000000..7915244 --- /dev/null +++ b/src/utils/csrfUtils.js @@ -0,0 +1,26 @@ +/*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 { csrfSync } = require('csrf-sync'); + +//Pull needed methods from csrfSync +const {generateToken, revokeToken, csrfSynchronisedProtection,} = csrfSync(); + +//Export them per csrfSync documentation +module.exports.generateToken = generateToken; +module.exports.revokeToken = revokeToken; +module.exports.csrfSynchronisedProtection = csrfSynchronisedProtection; \ No newline at end of file diff --git a/src/utils/sessionUtils.js b/src/utils/sessionUtils.js index 17896dd..6857df9 100644 --- a/src/utils/sessionUtils.js +++ b/src/utils/sessionUtils.js @@ -14,7 +14,7 @@ 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 .*/ -//local imports +//Local Imports const {userModel} = require('../schemas/user/userSchema'); const userBanModel = require('../schemas/user/userBanSchema') const altchaUtils = require('../utils/altchaUtils'); @@ -71,7 +71,6 @@ module.exports.authenticateSession = async function(user, pass, req){ //Not sure if this is just how connect-mongo is implemented or if it's an express issue, but connect-mongodb-session seems to not implement the all() function what so ever... req.session.seshid = req.session.id; req.session.authdate = new Date(); - req.session.authip = req.ip; req.session.user = { user: userDB.user, id: userDB.id, diff --git a/src/views/adminPanel.ejs b/src/views/adminPanel.ejs index 75c6970..861fd96 100644 --- a/src/views/adminPanel.ejs +++ b/src/views/adminPanel.ejs @@ -17,6 +17,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> - Admin Panel diff --git a/src/views/channel.ejs b/src/views/channel.ejs index 912e98f..2472e80 100644 --- a/src/views/channel.ejs +++ b/src/views/channel.ejs @@ -18,6 +18,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> - *DISCONNECTED* diff --git a/src/views/channelSettings.ejs b/src/views/channelSettings.ejs index d44a231..ba3a8d0 100644 --- a/src/views/channelSettings.ejs +++ b/src/views/channelSettings.ejs @@ -17,6 +17,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> - <%= channel.name %> - Channel Settings diff --git a/src/views/index.ejs b/src/views/index.ejs index 3525346..438029f 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -17,6 +17,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> diff --git a/src/views/lockedAccount.ejs b/src/views/lockedAccount.ejs index d45c185..54afe4e 100644 --- a/src/views/lockedAccount.ejs +++ b/src/views/lockedAccount.ejs @@ -18,6 +18,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> - Account Locked! diff --git a/src/views/login.ejs b/src/views/login.ejs index d6fa715..939029d 100644 --- a/src/views/login.ejs +++ b/src/views/login.ejs @@ -18,6 +18,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> - Log-In diff --git a/src/views/newChannel.ejs b/src/views/newChannel.ejs index c4955e9..fcbd9a5 100644 --- a/src/views/newChannel.ejs +++ b/src/views/newChannel.ejs @@ -17,6 +17,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> - New Channel diff --git a/src/views/partial/csrfToken.ejs b/src/views/partial/csrfToken.ejs new file mode 100644 index 0000000..4e5ce45 --- /dev/null +++ b/src/views/partial/csrfToken.ejs @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/src/views/passwordReset.ejs b/src/views/passwordReset.ejs index d69ed4b..14d72bf 100644 --- a/src/views/passwordReset.ejs +++ b/src/views/passwordReset.ejs @@ -17,6 +17,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> diff --git a/src/views/profile.ejs b/src/views/profile.ejs index d0cdb8d..7f92f85 100644 --- a/src/views/profile.ejs +++ b/src/views/profile.ejs @@ -17,6 +17,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <% var selfProfile = user ? profile ? profile.user == user.user : false : false %> <% if(profile){ %> diff --git a/src/views/register.ejs b/src/views/register.ejs index 8e6eaca..1ccc22d 100644 --- a/src/views/register.ejs +++ b/src/views/register.ejs @@ -18,6 +18,7 @@ along with this program. If not, see .--> <%- include('partial/styles', {instance, user}); %> + <%- include('partial/csrfToken', {csrfToken}); %> <%= instance %> - Account Registration