From 08fe051269785530f08adac00709aefa89f8fb8c Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Tue, 4 Nov 2025 06:09:26 -0500 Subject: [PATCH] Improved sanatization for server-side templating. --- src/controllers/adminPanelController.js | 6 +++- src/controllers/channelSettingsController.js | 5 +++- src/controllers/indexController.js | 5 +++- src/controllers/panel/profileController.js | 5 +++- src/controllers/profileController.js | 9 ++++-- src/controllers/tooltip/altListController.js | 3 +- src/controllers/tooltip/profileController.js | 7 +++-- src/schemas/tokebot/tokeSchema.js | 2 +- src/views/adminPanel.ejs | 4 +-- src/views/channelSettings.ejs | 8 +++--- src/views/index.ejs | 10 +++---- src/views/partial/adminPanel/channelList.ejs | 18 ++++++------ src/views/partial/adminPanel/permList.ejs | 12 ++++---- src/views/partial/adminPanel/userList.ejs | 26 ++++++++--------- src/views/partial/channelSettings/info.ejs | 6 ++-- .../partial/channelSettings/permList.ejs | 7 +++-- .../partial/channelSettings/settings.ejs | 6 ++-- src/views/partial/panels/profile.ejs | 28 +++++++++++-------- src/views/partial/profile/bio.ejs | 16 +++++++++-- src/views/partial/profile/date.ejs | 2 +- src/views/partial/profile/image.ejs | 2 +- src/views/partial/profile/pronouns.ejs | 4 +-- src/views/partial/profile/settings.ejs | 2 +- src/views/partial/profile/signature.ejs | 4 +-- src/views/partial/profile/status.ejs | 6 ++-- src/views/partial/profile/tokeCount.ejs | 6 ++-- src/views/partial/tooltip/altList.ejs | 12 ++++---- src/views/partial/tooltip/profile.ejs | 10 +++---- src/views/profile.ejs | 16 +++++------ www/css/panel/profile.css | 8 +++++- 30 files changed, 151 insertions(+), 104 deletions(-) diff --git a/src/controllers/adminPanelController.js b/src/controllers/adminPanelController.js index 2d5e72f..ca1a74c 100644 --- a/src/controllers/adminPanelController.js +++ b/src/controllers/adminPanelController.js @@ -17,6 +17,9 @@ along with this program. If not, see .*/ //Config const config = require('../../config.json'); +//NPM Imports +const validator = require('validator');//No express here, so regular validator it is! + //Local Imports const {userModel} = require('../schemas/user/userSchema'); const permissionModel = require('../schemas/permissionSchema'); @@ -45,7 +48,8 @@ module.exports.get = async function(req, res){ chanGuide: chanGuide, userList: userList, permList: permList, - csrfToken: csrfUtils.generateToken(req) + csrfToken: csrfUtils.generateToken(req), + unescape: validator.unescape }); }catch(err){ diff --git a/src/controllers/channelSettingsController.js b/src/controllers/channelSettingsController.js index 5b20aba..e310773 100644 --- a/src/controllers/channelSettingsController.js +++ b/src/controllers/channelSettingsController.js @@ -17,6 +17,9 @@ along with this program. If not, see .*/ //Config const config = require('../../config.json'); +//NPM Imports +const validator = require('validator');//No express here, so regular validator it is! + //local imports const channelModel = require('../schemas/channel/channelSchema'); const permissionModel = require('../schemas/permissionSchema'); @@ -39,7 +42,7 @@ module.exports.get = async function(req, res){ throw loggerUtils.exceptionSmith("Channel not found.", "queue"); } - return res.render('channelSettings', {instance: config.instanceName, user: req.session.user, channel: chanDB, reqRank, rankEnum: permissionModel.rankEnum, csrfToken: csrfUtils.generateToken(req)}); + return res.render('channelSettings', {instance: config.instanceName, user: req.session.user, channel: chanDB, reqRank, rankEnum: permissionModel.rankEnum, csrfToken: csrfUtils.generateToken(req), unescape: validator.unescape}); }catch(err){ return exceptionHandler(res, err); } diff --git a/src/controllers/indexController.js b/src/controllers/indexController.js index bbf1915..30e9682 100644 --- a/src/controllers/indexController.js +++ b/src/controllers/indexController.js @@ -17,6 +17,9 @@ along with this program. If not, see .*/ //Config const config = require('../../config.json'); +//NPM Imports +const validator = require('validator');//No express here, so regular validator it is! + //local imports const channelModel = require('../schemas/channel/channelSchema'); const csrfUtils = require('../utils/csrfUtils'); @@ -26,7 +29,7 @@ const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); 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, csrfToken: csrfUtils.generateToken(req)}); + return res.render('index', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide, csrfToken: csrfUtils.generateToken(req), unescape: validator.unescape}); }catch(err){ return exceptionHandler(res, err); } diff --git a/src/controllers/panel/profileController.js b/src/controllers/panel/profileController.js index 4a58c7d..f24d1dc 100644 --- a/src/controllers/panel/profileController.js +++ b/src/controllers/panel/profileController.js @@ -17,6 +17,9 @@ along with this program. If not, see .*/ //NPM Imports const {validationResult, matchedData} = require('express-validator'); +//NPM Imports +const validator = require('validator');//No express here, so regular validator it is! + //local imports const presenceUtils = require('../../utils/presenceUtils'); const {userModel} = require('../../schemas/user/userSchema'); @@ -34,7 +37,7 @@ module.exports.get = async function(req, res){ //Pull presence (should be quick since everyone whos been on since last startup will be backed in RAM) const presence = await presenceUtils.getPresence(profile.user); - return res.render('partial/panels/profile', {profile, presence}); + return res.render('partial/panels/profile', {profile, presence, unescape: validator.unescape}); }else{ res.status(400); return res.send({errors: validResult.array()}) diff --git a/src/controllers/profileController.js b/src/controllers/profileController.js index fd13cac..67d1895 100644 --- a/src/controllers/profileController.js +++ b/src/controllers/profileController.js @@ -20,6 +20,9 @@ const csrfUtils = require('../utils/csrfUtils'); const presenceUtils = require('../utils/presenceUtils'); const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); +//NPM Imports +const validator = require('validator');//No express here, so regular validator it is! + //Config const config = require('../../config.json'); @@ -44,7 +47,8 @@ module.exports.get = async function(req, res){ profile, selfProfile, presence, - csrfToken: csrfUtils.generateToken(req) + csrfToken: csrfUtils.generateToken(req), + unescape: validator.unescape }); }else{ res.render('profile', { @@ -53,7 +57,8 @@ module.exports.get = async function(req, res){ profile: null, selfProfile: false, presence: null, - csrfToken: csrfUtils.generateToken(req) + csrfToken: csrfUtils.generateToken(req), + unescape: validator.unescape }); } }catch(err){ diff --git a/src/controllers/tooltip/altListController.js b/src/controllers/tooltip/altListController.js index a037cd0..d470114 100644 --- a/src/controllers/tooltip/altListController.js +++ b/src/controllers/tooltip/altListController.js @@ -16,6 +16,7 @@ along with this program. If not, see .*/ //NPM Imports const {validationResult, matchedData} = require('express-validator'); +const validator = require('validator');//Because sometimes one isn't enough... //local imports const {userModel} = require('../../schemas/user/userSchema'); @@ -34,7 +35,7 @@ module.exports.get = async function(req, res){ return errorHandler(res, 'Cannot get alts for non-existant user!'); } - return res.render('partial/tooltip/altList', {alts: await userDB.getAltProfiles()}); + return res.render('partial/tooltip/altList', {alts: await userDB.getAltProfiles(), unescape: validator.unescape}); }else{ res.status(400); return res.send({errors: validResult.array()}) diff --git a/src/controllers/tooltip/profileController.js b/src/controllers/tooltip/profileController.js index 18f9cff..e4b4a0c 100644 --- a/src/controllers/tooltip/profileController.js +++ b/src/controllers/tooltip/profileController.js @@ -17,6 +17,9 @@ along with this program. If not, see .*/ //NPM Imports const {validationResult, matchedData} = require('express-validator'); +//NPM Imports +const validator = require('validator');//No express here, so regular validator it is! + //local imports const {userModel} = require('../../schemas/user/userSchema'); const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils'); @@ -30,10 +33,10 @@ module.exports.get = async function(req, res){ const data = matchedData(req); const profile = await userModel.findProfile({user: data.user}); - return res.render('partial/tooltip/profile', {profile}); + return res.render('partial/tooltip/profile', {profile, unescape: validator.unescape}); }else{ res.status(400); - return res.send({errors: validResult.array()}) + return res.send({errors: validResult.array()}); } }catch(err){ diff --git a/src/schemas/tokebot/tokeSchema.js b/src/schemas/tokebot/tokeSchema.js index ca186af..7b86913 100644 --- a/src/schemas/tokebot/tokeSchema.js +++ b/src/schemas/tokebot/tokeSchema.js @@ -91,7 +91,7 @@ tokeSchema.statics.calculateTokeMap = async function(){ //Display calculated toke sats for funsies if(config.verbose){ - console.log(`Processed ${this.commandCount} toke command callouts accross ${await this.estimatedDocumentCount()} tokes.`); + console.log(`Processed ${this.commandCount} toke command callouts accross ${this.count} tokes, averaging ${(this.commandCount/this.count).toFixed(3)} tokers per toke.`); } } diff --git a/src/views/adminPanel.ejs b/src/views/adminPanel.ejs index d34c773..b49d7ea 100644 --- a/src/views/adminPanel.ejs +++ b/src/views/adminPanel.ejs @@ -25,8 +25,8 @@ along with this program. If not, see . %> <%- include('partial/navbar', {user}); %>

<%= instance %> Admin Panel

- <%- include('partial/adminPanel/channelList', {chanGuide}) %> - <%- include('partial/adminPanel/userList', {user, userList, rankEnum}) %> + <%- include('partial/adminPanel/channelList', {chanGuide, unescape}) %> + <%- include('partial/adminPanel/userList', {user, userList, rankEnum, unescape}) %> <%- include('partial/adminPanel/permList', {permList, rankEnum}) %> <%- include('partial/adminPanel/userBanList') %> <%- include('partial/adminPanel/tokeCommandList') %> diff --git a/src/views/channelSettings.ejs b/src/views/channelSettings.ejs index d94e44f..81b4e59 100644 --- a/src/views/channelSettings.ejs +++ b/src/views/channelSettings.ejs @@ -24,13 +24,13 @@ along with this program. If not, see . %> <%- include('partial/navbar', {user}); %> -

<%- channel.name %> - Channel Settings

+

<%= unescape(channel.name) %> - Channel Settings

- <%- include('partial/channelSettings/info.ejs', {channel}); %> + <%- include('partial/channelSettings/info.ejs', {unescape, channel}); %> <%- include('partial/channelSettings/userList.ejs'); %> <%- include('partial/channelSettings/banList.ejs'); %> - <%- include('partial/channelSettings/settings.ejs'); %> - <%- include('partial/channelSettings/permList.ejs'); %> + <%- include('partial/channelSettings/settings.ejs', {unescape, channel}); %> + <%- include('partial/channelSettings/permList.ejs', {channel}); %> <%- include('partial/channelSettings/tokeCommandList.ejs'); %> <%- include('partial/channelSettings/emoteList.ejs'); %>
diff --git a/src/views/index.ejs b/src/views/index.ejs index dc47f6d..4ee326c 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -26,11 +26,11 @@ along with this program. If not, see . %>

Start a new channel...

<% chanGuide.forEach((channel) => { %> -
- -

<%- channel.name %>

- -

<%- channel.description %> +

+ +

<%= unescape(channel.name) %>

+ +

<%= unescape(channel.description) %>

<% }); %> diff --git a/src/views/partial/adminPanel/channelList.ejs b/src/views/partial/adminPanel/channelList.ejs index 53311f7..e6e53ec 100644 --- a/src/views/partial/adminPanel/channelList.ejs +++ b/src/views/partial/adminPanel/channelList.ejs @@ -29,19 +29,19 @@ along with this program. If not, see . %> <% chanGuide.forEach((channel) => { %> - - - - + + + + - - - <%- channel.name %> + + + <%= unescape(channel.name) %> - - <%- channel.description %> + + <%= unescape(channel.description) %> <% }); %> diff --git a/src/views/partial/adminPanel/permList.ejs b/src/views/partial/adminPanel/permList.ejs index d0e9ce1..40bce34 100644 --- a/src/views/partial/adminPanel/permList.ejs +++ b/src/views/partial/adminPanel/permList.ejs @@ -20,10 +20,10 @@ along with this program. If not, see . %> <% Object.keys(permList).forEach((key)=>{ %> <% if(key != "channelOverrides"){ %> - - <%rankEnum.slice().reverse().forEach((rank)=>{ %> - + <% }); %> @@ -33,10 +33,10 @@ along with this program. If not, see . %> <% Object.keys(permList.channelOverrides).forEach((key)=>{ %> <% if(key != "channelOverrides"){ %> - - <%rankEnum.slice().reverse().forEach((rank)=>{ %> - + <% }); %> diff --git a/src/views/partial/adminPanel/userList.ejs b/src/views/partial/adminPanel/userList.ejs index d94142a..df54d21 100644 --- a/src/views/partial/adminPanel/userList.ejs +++ b/src/views/partial/adminPanel/userList.ejs @@ -41,42 +41,42 @@ along with this program. If not, see . %> <% userList.forEach((curUser) => { %> - + - - + + - - <%- curUser.id %> + + <%= curUser.id %> - - <%- curUser.user %> + + <%= unescape(curUser.user) %> <% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%> - <%rankEnum.slice().reverse().forEach((rank)=>{ %> - + <% }); %> <% }else{ %> - <%- curUser.rank %> + <%= curUser.rank %> <% } %> - <%- curUser.email ? curUser.email : "N/A" %> + <%= unescape(curUser.email) ? curUser.email : "N/A" %> - <%- curUser.date.toUTCString() %> + <%= unescape(curUser.date.toUTCString()) %> <%# It's either this or add whitespce >:( %> - + <% }); %> diff --git a/src/views/partial/channelSettings/info.ejs b/src/views/partial/channelSettings/info.ejs index a2f073f..11aeafb 100644 --- a/src/views/partial/channelSettings/info.ejs +++ b/src/views/partial/channelSettings/info.ejs @@ -19,13 +19,13 @@ along with this program. If not, see . %>

Thumbnail:

- - + +

Description:

-

<%= channel.description %>

+

<%= unescape(channel.description) %>

\ No newline at end of file diff --git a/src/views/partial/channelSettings/permList.ejs b/src/views/partial/channelSettings/permList.ejs index 80ca3e4..0c9cb20 100644 --- a/src/views/partial/channelSettings/permList.ejs +++ b/src/views/partial/channelSettings/permList.ejs @@ -20,10 +20,11 @@ along with this program. If not, see . %> <% Object.keys(channel.permissions.toObject()).forEach((key)=>{ %> <% if(key != "channelOverrides"){ %> - - <%rankEnum.slice().reverse().forEach((rank)=>{ %> - + <% }); %> diff --git a/src/views/partial/channelSettings/settings.ejs b/src/views/partial/channelSettings/settings.ejs index 9ddc294..1ec464c 100644 --- a/src/views/partial/channelSettings/settings.ejs +++ b/src/views/partial/channelSettings/settings.ejs @@ -19,13 +19,13 @@ along with this program. If not, see . %>
<% Object.keys(channel.settings).forEach((key) => { %> - + <% switch(typeof channel.settings[key]){ case "string": %> - + <% break; default: %> - checked <% } %>> + checked <% } %>> <% break; } %> diff --git a/src/views/partial/panels/profile.ejs b/src/views/partial/panels/profile.ejs index b5728c6..63fc487 100644 --- a/src/views/partial/panels/profile.ejs +++ b/src/views/partial/panels/profile.ejs @@ -18,16 +18,22 @@ along with this program. If not, see . %> <% if(profile == null){ %>

Profile not found!

<% }else{ %> - View Full Profile -

<%- profile.user %>

- <%- include('../profile/status', {profile, presence, auxClass:"panel"}); %> - -

Toke Count: <%- profile.tokeCount %>

- <% if(profile.pronouns != '' && profile.pronouns != null){ %> -

Pronouns: <%- profile.pronouns %>

- <% } %> -

Signature: <%- profile.signature %>

-

Bio:

-

<%- profile.bio %>

+ <% const splitBio = profile.bio.split('\n'); %> + View Full Profile +

<%= unescape(profile.user) %>

+ <%- include('../profile/status', {profile, presence, auxClass:"panel", unescape}); %> + +
+

Toke Count: <%= profile.tokeCount %>

+ <% if(profile.pronouns != '' && profile.pronouns != null){ %> +

Pronouns: <%= unescape(profile.pronouns) %>

+ <% } %> +

Signature: <%= unescape(profile.signature) %>

+

+ <% for(const line of splitBio){ %> + <%= unescape(line) %>
+ <% } %> +

+
<% } %>
\ No newline at end of file diff --git a/src/views/partial/profile/bio.ejs b/src/views/partial/profile/bio.ejs index 0cc620b..3d28720 100644 --- a/src/views/partial/profile/bio.ejs +++ b/src/views/partial/profile/bio.ejs @@ -15,11 +15,23 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . %>

Bio:

+ <% + //Split bio by newline + const splitBio = profile.bio.split('\n'); + %> <% if(selfProfile){ %> <%# Make sure to convert newlines to br so they display proepr %> -

<%- profile.bio.replaceAll('\n','
') %>

+

+ <% for(const line of splitBio){ %> + <%= unescape(line) %>
+ <% } %> +

<% }else{ %> -

<%- profile.bio.replaceAll('\n','
') %>

+

+ <% for(const line of splitBio){ %> + <%= unescape(line) %>
+ <% } %> +

<% } %>
\ No newline at end of file diff --git a/src/views/partial/profile/date.ejs b/src/views/partial/profile/date.ejs index 27b21aa..55c175b 100644 --- a/src/views/partial/profile/date.ejs +++ b/src/views/partial/profile/date.ejs @@ -14,5 +14,5 @@ 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 . %> -

Joined: <%- profile.date.toLocaleDateString(); %>

+

Joined: <%= profile.date.toLocaleDateString(); %>

\ No newline at end of file diff --git a/src/views/partial/profile/image.ejs b/src/views/partial/profile/image.ejs index 7665509..30f4afe 100644 --- a/src/views/partial/profile/image.ejs +++ b/src/views/partial/profile/image.ejs @@ -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 . %>
- + <% if(selfProfile){ %> <% } %> diff --git a/src/views/partial/profile/pronouns.ejs b/src/views/partial/profile/pronouns.ejs index 3d427f2..cd35da2 100644 --- a/src/views/partial/profile/pronouns.ejs +++ b/src/views/partial/profile/pronouns.ejs @@ -24,10 +24,10 @@ along with this program. If not, see . %> <% }else if(profile.pronouns != null && profile.pronouns != ""){ %> <% if(selfProfile){ %> -

Pronouns: <%- profile.pronouns %>

+

Pronouns: <%= unescape(profile.pronouns) %>

<% }else{ %> -

Pronouns: <%- profile.pronouns %>

+

Pronouns: <%= unescape(profile.pronouns) %>

<% } %>
<% } %> \ No newline at end of file diff --git a/src/views/partial/profile/settings.ejs b/src/views/partial/profile/settings.ejs index 84a0ac8..3135653 100644 --- a/src/views/partial/profile/settings.ejs +++ b/src/views/partial/profile/settings.ejs @@ -17,7 +17,7 @@ along with this program. If not, see . %> <% if(profile.email){ %> - + <% } %>