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