Improved sanatization for server-side templating.

This commit is contained in:
rainbow napkin 2025-11-04 06:09:26 -05:00
parent 35fd81e1b2
commit 08fe051269
30 changed files with 151 additions and 104 deletions

View file

@ -17,6 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//Config //Config
const config = require('../../config.json'); const config = require('../../config.json');
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//Local Imports //Local Imports
const {userModel} = require('../schemas/user/userSchema'); const {userModel} = require('../schemas/user/userSchema');
const permissionModel = require('../schemas/permissionSchema'); const permissionModel = require('../schemas/permissionSchema');
@ -45,7 +48,8 @@ module.exports.get = async function(req, res){
chanGuide: chanGuide, chanGuide: chanGuide,
userList: userList, userList: userList,
permList: permList, permList: permList,
csrfToken: csrfUtils.generateToken(req) csrfToken: csrfUtils.generateToken(req),
unescape: validator.unescape
}); });
}catch(err){ }catch(err){

View file

@ -17,6 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//Config //Config
const config = require('../../config.json'); const config = require('../../config.json');
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//local imports //local imports
const channelModel = require('../schemas/channel/channelSchema'); const channelModel = require('../schemas/channel/channelSchema');
const permissionModel = require('../schemas/permissionSchema'); const permissionModel = require('../schemas/permissionSchema');
@ -39,7 +42,7 @@ module.exports.get = async function(req, res){
throw loggerUtils.exceptionSmith("Channel not found.", "queue"); 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){ }catch(err){
return exceptionHandler(res, err); return exceptionHandler(res, err);
} }

View file

@ -17,6 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//Config //Config
const config = require('../../config.json'); const config = require('../../config.json');
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//local imports //local imports
const channelModel = require('../schemas/channel/channelSchema'); const channelModel = require('../schemas/channel/channelSchema');
const csrfUtils = require('../utils/csrfUtils'); const csrfUtils = require('../utils/csrfUtils');
@ -26,7 +29,7 @@ const {exceptionHandler, errorHandler} = require('../utils/loggerUtils');
module.exports.get = async function(req, res){ module.exports.get = async function(req, res){
try{ try{
const chanGuide = await channelModel.getChannelList(); 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){ }catch(err){
return exceptionHandler(res, err); return exceptionHandler(res, err);
} }

View file

@ -17,6 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//NPM Imports //NPM Imports
const {validationResult, matchedData} = require('express-validator'); const {validationResult, matchedData} = require('express-validator');
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//local imports //local imports
const presenceUtils = require('../../utils/presenceUtils'); const presenceUtils = require('../../utils/presenceUtils');
const {userModel} = require('../../schemas/user/userSchema'); 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) //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); 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{ }else{
res.status(400); res.status(400);
return res.send({errors: validResult.array()}) return res.send({errors: validResult.array()})

View file

@ -20,6 +20,9 @@ const csrfUtils = require('../utils/csrfUtils');
const presenceUtils = require('../utils/presenceUtils'); const presenceUtils = require('../utils/presenceUtils');
const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); const {exceptionHandler, errorHandler} = require('../utils/loggerUtils');
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//Config //Config
const config = require('../../config.json'); const config = require('../../config.json');
@ -44,7 +47,8 @@ module.exports.get = async function(req, res){
profile, profile,
selfProfile, selfProfile,
presence, presence,
csrfToken: csrfUtils.generateToken(req) csrfToken: csrfUtils.generateToken(req),
unescape: validator.unescape
}); });
}else{ }else{
res.render('profile', { res.render('profile', {
@ -53,7 +57,8 @@ module.exports.get = async function(req, res){
profile: null, profile: null,
selfProfile: false, selfProfile: false,
presence: null, presence: null,
csrfToken: csrfUtils.generateToken(req) csrfToken: csrfUtils.generateToken(req),
unescape: validator.unescape
}); });
} }
}catch(err){ }catch(err){

View file

@ -16,6 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//NPM Imports //NPM Imports
const {validationResult, matchedData} = require('express-validator'); const {validationResult, matchedData} = require('express-validator');
const validator = require('validator');//Because sometimes one isn't enough...
//local imports //local imports
const {userModel} = require('../../schemas/user/userSchema'); 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 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{ }else{
res.status(400); res.status(400);
return res.send({errors: validResult.array()}) return res.send({errors: validResult.array()})

View file

@ -17,6 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//NPM Imports //NPM Imports
const {validationResult, matchedData} = require('express-validator'); const {validationResult, matchedData} = require('express-validator');
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//local imports //local imports
const {userModel} = require('../../schemas/user/userSchema'); const {userModel} = require('../../schemas/user/userSchema');
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils'); const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
@ -30,10 +33,10 @@ module.exports.get = async function(req, res){
const data = matchedData(req); const data = matchedData(req);
const profile = await userModel.findProfile({user: data.user}); 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{ }else{
res.status(400); res.status(400);
return res.send({errors: validResult.array()}) return res.send({errors: validResult.array()});
} }
}catch(err){ }catch(err){

View file

@ -91,7 +91,7 @@ tokeSchema.statics.calculateTokeMap = async function(){
//Display calculated toke sats for funsies //Display calculated toke sats for funsies
if(config.verbose){ 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.`);
} }
} }

View file

@ -25,8 +25,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<%- include('partial/navbar', {user}); %> <%- include('partial/navbar', {user}); %>
<h1 class="panel-title"><%= instance %> Admin Panel</h1> <h1 class="panel-title"><%= instance %> Admin Panel</h1>
<div class="admin-panel-container-div"> <div class="admin-panel-container-div">
<%- include('partial/adminPanel/channelList', {chanGuide}) %> <%- include('partial/adminPanel/channelList', {chanGuide, unescape}) %>
<%- include('partial/adminPanel/userList', {user, userList, rankEnum}) %> <%- include('partial/adminPanel/userList', {user, userList, rankEnum, unescape}) %>
<%- include('partial/adminPanel/permList', {permList, rankEnum}) %> <%- include('partial/adminPanel/permList', {permList, rankEnum}) %>
<%- include('partial/adminPanel/userBanList') %> <%- include('partial/adminPanel/userBanList') %>
<%- include('partial/adminPanel/tokeCommandList') %> <%- include('partial/adminPanel/tokeCommandList') %>

View file

@ -24,13 +24,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
</head> </head>
<body> <body>
<%- include('partial/navbar', {user}); %> <%- include('partial/navbar', {user}); %>
<h1 class="panel-title"><%- channel.name %> - Channel Settings</h1> <h1 class="panel-title"><%= unescape(channel.name) %> - Channel Settings</h1>
<div class="admin-panel-container-div"> <div class="admin-panel-container-div">
<%- include('partial/channelSettings/info.ejs', {channel}); %> <%- include('partial/channelSettings/info.ejs', {unescape, channel}); %>
<%- include('partial/channelSettings/userList.ejs'); %> <%- include('partial/channelSettings/userList.ejs'); %>
<%- include('partial/channelSettings/banList.ejs'); %> <%- include('partial/channelSettings/banList.ejs'); %>
<%- include('partial/channelSettings/settings.ejs'); %> <%- include('partial/channelSettings/settings.ejs', {unescape, channel}); %>
<%- include('partial/channelSettings/permList.ejs'); %> <%- include('partial/channelSettings/permList.ejs', {channel}); %>
<%- include('partial/channelSettings/tokeCommandList.ejs'); %> <%- include('partial/channelSettings/tokeCommandList.ejs'); %>
<%- include('partial/channelSettings/emoteList.ejs'); %> <%- include('partial/channelSettings/emoteList.ejs'); %>
</div> </div>

View file

@ -26,11 +26,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<h3><a href="/newchannel">Start a new channel...</a></h3> <h3><a href="/newchannel">Start a new channel...</a></h3>
<div id="channel-guide-div" class="channel-guide"> <div id="channel-guide-div" class="channel-guide">
<% chanGuide.forEach((channel) => { %> <% chanGuide.forEach((channel) => { %>
<div id="channel-guide-entry-<%- channel.name %>" class="channel-guide-entry"> <div id="channel-guide-entry-<%= unescape(channel.name) %>" class="channel-guide-entry">
<a href="/c/<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><img id="channel-guide-entry-img-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item" src="<%- channel.thumbnail %>"></a> <a href="/c/<%= unescape(channel.name) %>" class="channel-guide-entry channel-guide-entry-item"><img id="channel-guide-entry-img-<%= unescape(channel.name) %>" class="channel-guide-entry channel-guide-entry-item" src="<%= encodeURI(unescape(channel.thumbnail)) %>"></a>
<h3 id="channel-guide-entry-name-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><a href="/c/<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><%- channel.name %></a></h3> <h3 id="channel-guide-entry-name-<%= unescape(channel.name) %>" class="channel-guide-entry channel-guide-entry-item"><a href="/c/<%= encodeURI(unescape(channel.name)) %>" class="channel-guide-entry channel-guide-entry-item"><%= unescape(channel.name) %></a></h3>
<span id="channel-guide-entry-description-span-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"> <span id="channel-guide-entry-description-span-<%= unescape(channel.name) %>" class="channel-guide-entry channel-guide-entry-item">
<p id="channel-guide-entry-description-<%- channel.name %>" class="channel-guide-entry channel-guide-entry-item"><%- channel.description %></h3> <p id="channel-guide-entry-description-<%= unescape(channel.name) %>" class="channel-guide-entry channel-guide-entry-item"><%= unescape(channel.description) %></h3>
</span> </span>
</div> </div>
<% }); %> <% }); %>

View file

@ -29,19 +29,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
</td> </td>
</tr> </tr>
<% chanGuide.forEach((channel) => { %> <% chanGuide.forEach((channel) => { %>
<tr id="admin-channel-list-entry-<%- channel.name %>" class=""> <tr id="admin-channel-list-entry-<%= unescape(channel.name) %>" class="">
<td id="admin-channel-list-entry-img-<%- channel.name %>" class=" admin-list-entry-item"> <td id="admin-channel-list-entry-img-<%= unescape(channel.name) %>" class=" admin-list-entry-item">
<a href="/c/<%- channel.name %>" class=" admin-list-entry-item"> <a href="/c/<%= encodeURI(unescape(channel.name)) %>" class=" admin-list-entry-item">
<img id="admin-channel-list-entry-img-<%- channel.name %>" class=" admin-list-entry-item" src="<%- channel.thumbnail %>"> <img id="admin-channel-list-entry-img-<%= unescape(channel.name) %>" class=" admin-list-entry-item" src="<%= encodeURI(unescape(channel.thumbnail)) %>">
</a> </a>
</td> </td>
<td id="admin-channel-list-entry-name-<%- channel.name %>" class=" admin-list-entry-item not-first-col"> <td id="admin-channel-list-entry-name-<%= unescape(channel.name) %>" class=" admin-list-entry-item not-first-col">
<a href="/c/<%- channel.name %>" class=" admin-list-entry-item"> <a href="/c/<%= unescape(channel.name) %>" class=" admin-list-entry-item">
<%- channel.name %> <%= unescape(channel.name) %>
</a> </a>
</td> </td>
<td id="admin-channel-list-entry-description-<%- channel.name %>" class="large admin-list-entry-item not-first-col"> <td id="admin-channel-list-entry-description-<%= unescape(channel.name) %>" class="large admin-list-entry-item not-first-col">
<%- channel.description %> <%= unescape(channel.description) %>
</td> </td>
</tr> </tr>
<% }); %> <% }); %>

View file

@ -20,10 +20,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(permList).forEach((key)=>{ %> <% Object.keys(permList).forEach((key)=>{ %>
<% if(key != "channelOverrides"){ %> <% if(key != "channelOverrides"){ %>
<span class="admin-list-field-container"> <span class="admin-list-field-container">
<label class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%- key %>"><%- key %>: </label> <label class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%= key %>"><%= key %>: </label>
<select name="admin-perm-list-rank-select-<%- key %>" data-key="<%- key %>" class="admin-list-select admin-perm-list-rank-select"> <select name="admin-perm-list-rank-select-<%= key %>" data-key="<%= key %>" class="admin-list-select admin-perm-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %> <%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(permList[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option> <option <%if(permList[key] == rank){%> selected <%}%> value="<%= rank %>"><%= rank %></option>
<% }); %> <% }); %>
</select> </select>
</span> </span>
@ -33,10 +33,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(permList.channelOverrides).forEach((key)=>{ %> <% Object.keys(permList.channelOverrides).forEach((key)=>{ %>
<% if(key != "channelOverrides"){ %> <% if(key != "channelOverrides"){ %>
<span class="admin-list-field-container"> <span class="admin-list-field-container">
<label class="admin-list-label admin-chan-perm-list" for="admin-chan-perm-list-rank-select-<%- key %>"><%- key %>: </label> <label class="admin-list-label admin-chan-perm-list" for="admin-chan-perm-list-rank-select-<%= key %>"><%= key %>: </label>
<select name="admin-chan-perm-list-rank-select-<%- key %>" data-key="<%- key %>" class="admin-list-select admin-chan-perm-list-rank-select"> <select name="admin-chan-perm-list-rank-select-<%= key %>" data-key="<%= key %>" class="admin-list-select admin-chan-perm-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %> <%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(permList.channelOverrides[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option> <option <%if(permList.channelOverrides[key] == rank){%> selected <%}%> value="<%= rank %>"><%= rank %></option>
<% }); %> <% }); %>
</select> </select>
</span> </span>

View file

@ -41,42 +41,42 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
</td> </td>
</tr> </tr>
<% userList.forEach((curUser) => { %> <% userList.forEach((curUser) => { %>
<tr id="admin-user-list-entry-<%- curUser.user %>" class="admin-list-entry" data-name="<%- curUser.user %>" > <tr id="admin-user-list-entry-<%= unescape(curUser.user) %>" class="admin-list-entry" data-name="<%= unescape(curUser.user) %>" >
<td class="admin-list-entry-item"> <td class="admin-list-entry-item">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item"> <a href="/profile/<%= unescape(curUser.user) %>" class="admin-list-entry-item">
<img class="admin-list-entry-item" src="<%- curUser.img %>"> <img class="admin-list-entry-item" src="<%= encodeURI(unescape(curUser.img)) %>">
</a> </a>
</td> </td>
<td class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item"> <a href="/profile/<%= encodeURI(unescape(curUser.user)) %>" class="admin-list-entry-item">
<%- curUser.id %> <%= curUser.id %>
</a> </a>
</td> </td>
<td class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item admin-user-list-name"> <a href="/profile/<%= encodeURI(unescape(curUser.user)) %>" class="admin-list-entry-item admin-user-list-name">
<%- curUser.user %> <%= unescape(curUser.user) %>
</a> </a>
</td> </td>
<td class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%> <% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%>
<select id="admin-user-list-rank-select-<%- curUser.user %>" class="admin-user-list-rank-select"> <select id="admin-user-list-rank-select-<%= unescape(curUser.user) %>" class="admin-user-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %> <%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(curUser.rank == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option> <option <%if(curUser.rank == rank){%> selected <%}%> value="<%= rank %>"><%= rank %></option>
<% }); %> <% }); %>
</select> </select>
<% }else{ %> <% }else{ %>
<%- curUser.rank %> <%= curUser.rank %>
<% } %> <% } %>
</td> </td>
<td class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<%- curUser.email ? curUser.email : "N/A" %> <%= unescape(curUser.email) ? curUser.email : "N/A" %>
</td> </td>
<td class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<%- curUser.date.toUTCString() %> <%= unescape(curUser.date.toUTCString()) %>
</td> </td>
<td class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<%# It's either this or add whitespce >:( %> <%# It's either this or add whitespce >:( %>
<i class="bi-radioactive admin-user-list-icon admin-user-list-nuke-icon" title="Nuke Account: <%- curUser.user %>"></i><i class="bi-fire admin-user-list-icon admin-user-list-ban-icon" title="Ban User: <%- curUser.user %>"></i><i class="bi-arrow-clockwise admin-user-list-icon admin-user-list-pw-reset-icon" title="Generate Password Reset Link for <%- curUser.user %>"></i> <i class="bi-radioactive admin-user-list-icon admin-user-list-nuke-icon" title="Nuke Account: <%= unescape(curUser.user) %>"></i><i class="bi-fire admin-user-list-icon admin-user-list-ban-icon" title="Ban User: <%= unescape(curUser.user) %>"></i><i class="bi-arrow-clockwise admin-user-list-icon admin-user-list-pw-reset-icon" title="Generate Password Reset Link for <%= unescape(curUser.user) %>"></i>
</td> </td>
</tr> </tr>
<% }); %> <% }); %>

View file

@ -19,13 +19,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<span class="channel-info-span" id="channel-info-thumbnail-span"> <span class="channel-info-span" id="channel-info-thumbnail-span">
<p class="channel-info-label">Thumbnail:</p> <p class="channel-info-label">Thumbnail:</p>
<div> <div>
<input value="<%= channel.thumbnail %>" placeholder="Thumbnail URL" style="display: none;" id="channel-info-thumbnail-prompt"> <input value="<%= encodeURI(unescape(channel.thumbnail)) %>" placeholder="Thumbnail URL" style="display: none;" id="channel-info-thumbnail-prompt">
<img class="interactive" src="<%= channel.thumbnail %> " id="channel-info-thumbnail"> <img class="interactive" src="<%= encodeURI(unescape(channel.thumbnail)) %> " id="channel-info-thumbnail">
</div> </div>
</span> </span>
<span class="channel-info-span" id="channel-info-description-span"> <span class="channel-info-span" id="channel-info-description-span">
<p class="channel-info-label">Description:</p> <p class="channel-info-label">Description:</p>
<p class="interactive" id="channel-info-description"><%= channel.description %></p> <p class="interactive" id="channel-info-description"><%= unescape(channel.description) %></p>
<span> <span>
</div> </div>
</div> </div>

View file

@ -20,10 +20,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(channel.permissions.toObject()).forEach((key)=>{ %> <% Object.keys(channel.permissions.toObject()).forEach((key)=>{ %>
<% if(key != "channelOverrides"){ %> <% if(key != "channelOverrides"){ %>
<span class="admin-list-field-container"> <span class="admin-list-field-container">
<label class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%- key %>"><%- key %>: </label> <%# These strings are generated internally server-side. There really isn't much of a reason to sanatize them.%>
<select data-key="<%- key %>" name="admin-perm-list-rank-select-<%- key %>" class="channel-perm-select admin-list-select admin-perm-list-rank-select"> <label class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%= key %>"><%= key %>: </label>
<select data-key="<%= key %>" name="admin-perm-list-rank-select-<%= key %>" class="channel-perm-select admin-list-select admin-perm-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %> <%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(channel.permissions.toObject()[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option> <option <%if(channel.permissions.toObject()[key] == rank){%> selected <%}%> value="<%= rank %>"><%= rank %></option>
<% }); %> <% }); %>
</select> </select>
</span> </span>

View file

@ -19,13 +19,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<form action="javascript:" class="admin-list-field"> <form action="javascript:" class="admin-list-field">
<% Object.keys(channel.settings).forEach((key) => { %> <% Object.keys(channel.settings).forEach((key) => { %>
<span class="admin-list-field-container"> <span class="admin-list-field-container">
<label class="admin-list-label"><%- key %>:</label> <label class="admin-list-label"><%= key %>:</label>
<% switch(typeof channel.settings[key]){ <% switch(typeof channel.settings[key]){
case "string": %> case "string": %>
<input data-key="<%- key %>" class="channel-preference-list-item" value="<%- channel.settings[key] %>"> <input data-key="<%= key %>" class="channel-preference-list-item" value="<%= channel.settings[key] %>">
<% break; <% break;
default: %> default: %>
<input data-key="<%- key %>" class="channel-preference-list-item" type="checkbox" <% if(channel.settings[key]){ %> checked <% } %>> <input data-key="<%= key %>" class="channel-preference-list-item" type="checkbox" <% if(channel.settings[key]){ %> checked <% } %>>
<% break; <% break;
} %> } %>
</span> </span>

View file

@ -18,16 +18,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% if(profile == null){ %> <% if(profile == null){ %>
<p>Profile not found!</p> <p>Profile not found!</p>
<% }else{ %> <% }else{ %>
<a class="panel profile-link" target="_blank" href="/profile/<%- profile.user %>">View Full Profile<i class="bi-box-arrow-in-up-right"></i></a> <% const splitBio = profile.bio.split('\n'); %>
<h2 class="panel profile-name"><%- profile.user %></h2> <a class="panel profile-link" target="_blank" href="/profile/<%= encodeURI(unescape(profile.user)) %>">View Full Profile<i class="bi-box-arrow-in-up-right"></i></a>
<%- include('../profile/status', {profile, presence, auxClass:"panel"}); %> <h2 class="panel profile-name"><%= unescape(profile.user) %></h2>
<img class="panel profile-img" src="<%- profile.img %>"> <%- include('../profile/status', {profile, presence, auxClass:"panel", unescape}); %>
<p class="panel profile-info">Toke Count: <%- profile.tokeCount %></p> <img class="panel profile-img" src="<%= encodeURI(unescape(profile.img)) %>">
<div class="dynamic-container panel profile-box">
<p class="panel profile-info">Toke Count: <%= profile.tokeCount %></p>
<% if(profile.pronouns != '' && profile.pronouns != null){ %> <% if(profile.pronouns != '' && profile.pronouns != null){ %>
<p class="panel profile-info">Pronouns: <%- profile.pronouns %></p> <p class="panel profile-info">Pronouns: <%= unescape(profile.pronouns) %></p>
<% } %> <% } %>
<p class="panel profile-info">Signature: <%- profile.signature %></p> <p class="panel profile-info">Signature: <%= unescape(profile.signature) %></p>
<p class="panel profile-bio-label">Bio:</p> <p class="panel profile-bio">
<p class="panel profile-bio"><%- profile.bio %></p> <% for(const line of splitBio){ %>
<%= unescape(line) %><br>
<% } %>
</p>
</div>
<% } %> <% } %>
</div> </div>

View file

@ -15,11 +15,23 @@ 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/>. %> along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<span class="profile-bio-span"> <span class="profile-bio-span">
<h4 id="profile-bio-label" class="profile-item-label">Bio:</h4> <h4 id="profile-bio-label" class="profile-item-label">Bio:</h4>
<%
//Split bio by newline
const splitBio = profile.bio.split('\n');
%>
<% if(selfProfile){ %> <% if(selfProfile){ %>
<%# Make sure to convert newlines to br so they display proepr %> <%# Make sure to convert newlines to br so they display proepr %>
<p class="profile-item interactive" id="profile-bio-content"><%- profile.bio.replaceAll('\n','<br>') %></p> <p class="profile-item interactive" id="profile-bio-content">
<% for(const line of splitBio){ %>
<%= unescape(line) %><br>
<% } %>
</p>
<textarea class="profile-item-prompt" id="profile-bio-prompt"></textarea> <textarea class="profile-item-prompt" id="profile-bio-prompt"></textarea>
<% }else{ %> <% }else{ %>
<p class="profile-item" id="profile-bio-content"><%- profile.bio.replaceAll('\n','<br>') %></p> <p class="profile-item" id="profile-bio-content">
<% for(const line of splitBio){ %>
<%= unescape(line) %><br>
<% } %>
</p>
<% } %> <% } %>
</span> </span>

View file

@ -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 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/>. %> along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<span class="profile-item"> <span class="profile-item">
<p class="profile-item" id="profile-creation-date" title="<%- profile.date.toUTCString() %>">Joined: <%- profile.date.toLocaleDateString(); %></p> <p class="profile-item" id="profile-creation-date" title="<%= profile.date.toUTCString() %>">Joined: <%= profile.date.toLocaleDateString(); %></p>
</span> </span>

View file

@ -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 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/>. %> along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<div class="profile-item" id="profile-img"> <div class="profile-item" id="profile-img">
<img class="profile-item" id="profile-img-content" src="<%- profile.img %>"> <img class="profile-item" id="profile-img-content" src="<%= encodeURI(unescape(profile.img)) %>">
<% if(selfProfile){ %> <% if(selfProfile){ %>
<input class="profile-item-prompt" id="profile-img-prompt"> <input class="profile-item-prompt" id="profile-img-prompt">
<% } %> <% } %>

View file

@ -24,10 +24,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% }else if(profile.pronouns != null && profile.pronouns != ""){ %> <% }else if(profile.pronouns != null && profile.pronouns != ""){ %>
<span class="profile-item profile-item-oneliner"> <span class="profile-item profile-item-oneliner">
<% if(selfProfile){ %> <% if(selfProfile){ %>
<p class="profile-item profile-item-oneliner" id="profile-pronouns">Pronouns: <span class="profile-content interactive" id="profile-pronouns-content"><%- profile.pronouns %></span></p> <p class="profile-item profile-item-oneliner" id="profile-pronouns">Pronouns: <span class="profile-content interactive" id="profile-pronouns-content"><%= unescape(profile.pronouns) %></span></p>
<input class="profile-item-prompt" id="profile-pronouns-prompt"> <input class="profile-item-prompt" id="profile-pronouns-prompt">
<% }else{ %> <% }else{ %>
<p class="profile-item profile-item-oneliner" id="profile-pronouns">Pronouns: <span class="profile-content" id="profile-pronouns-content"><%- profile.pronouns %></span></p> <p class="profile-item profile-item-oneliner" id="profile-pronouns">Pronouns: <span class="profile-content" id="profile-pronouns-content"><%= unescape(profile.pronouns) %></span></p>
<% } %> <% } %>
</span> </span>
<% } %> <% } %>

View file

@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<h3 class="account-settings" id="account-settings-label">Account Settings</h3> <h3 class="account-settings" id="account-settings-label">Account Settings</h3>
<% if(profile.email){ %> <% if(profile.email){ %>
<h4 class="account-settings" id="account-email-label">Email Address:</h3> <h4 class="account-settings" id="account-email-label">Email Address:</h3>
<h4 class="account-settings" id="account-email-address"><%= profile.email %></h4> <h4 class="account-settings" id="account-email-address"><%= unescape(profile.email) %></h4>
<% } %> <% } %>
<span class="account-settings" id="account-settings-buttons"> <span class="account-settings" id="account-settings-buttons">
<button href="javascript:" class="account-settings positive-button" id="account-settings-update-email-button">Update Email</button> <button href="javascript:" class="account-settings positive-button" id="account-settings-update-email-button">Update Email</button>

View file

@ -15,9 +15,9 @@ 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/>. %> along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<span class="profile-item"> <span class="profile-item">
<% if(selfProfile){ %> <% if(selfProfile){ %>
<p class="profile-item profile-item-oneliner" id="profile-signature">Signature: <span class="profile-content interactive" id="profile-signature-content"><%- profile.signature %></span></p> <p class="profile-item profile-item-oneliner" id="profile-signature">Signature: <span class="profile-content interactive" id="profile-signature-content"><%= unescape(profile.signature) %></span></p>
<input class="profile-item-prompt" id="profile-signature-prompt"> <input class="profile-item-prompt" id="profile-signature-prompt">
<% }else{ %> <% }else{ %>
<p class="profile-item profile-item-oneliner" id="profile-signature">Signature: <span class="profile-content" id="profile-signature-content"><%- profile.signature %></span></p> <p class="profile-item profile-item-oneliner" id="profile-signature">Signature: <span class="profile-content" id="profile-signature-content"><%= unescape(profile.signature) %></span></p>
<% } %> <% } %>
</span> </span>

View file

@ -14,9 +14,9 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. %> along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% if(profile.user == "Tokebot"){ %> <% if(profile.user == "Tokebot"){ %>
<p id="profile-status" class="<%- auxClass %> positive profile-status"><span class="bi-record-fill"></span>Perma-Couched</p> <p id="profile-status" class="<%= auxClass %> positive profile-status"><span class="bi-record-fill"></span>Perma-Couched</p>
<% }else{ %> <% }else{ %>
<% const statusClass = (presence.status == "Streaming") ? "positive" : ((presence.status == "Offline") ? "inactive" : "positive-low");%> <% const statusClass = (presence.status == "Streaming") ? "positive" : ((presence.status == "Offline") ? "inactive" : "positive-low");%>
<% const curChan = (presence.activeConnections == null || presence.activeConnections.length <= 0) ? '' : (presence.activeConnections.length == 1 ? ` - /c/${presence.activeConnections[0].channel.name}` : " - Multiple Channels"); %> <% const curChan = (presence.activeConnections == null || presence.activeConnections.length <= 0) ? '' : (presence.activeConnections.length == 1 ? ` - /c/${unescape(presence.activeConnections[0].channel.name)}` : " - Multiple Channels"); %>
<p id="profile-status" class="<%- auxClass %> <%- statusClass %> profile-status"><span class="bi-record-fill"></span><%- presence.status %><%-curChan%></p> <p id="profile-status" class="<%= auxClass %> <%= statusClass %> profile-status"><span class="bi-record-fill"></span><%= presence.status %><%= unescape(curChan)%></p>
<% } %> <% } %>

View file

@ -14,16 +14,16 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. %> along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<span class="profile-item profile-toke-count"> <span class="profile-item profile-toke-count">
<p class="profile-item profile-toke-count interactive">Toke Count: (<%- profile.tokeCount %> Total) </p> <p class="profile-item profile-toke-count interactive">Toke Count: (<%= profile.tokeCount %> Total) </p>
<i class="-item bi-caret-left-fill profile-toke-count" id="toggle-toke-list"></i> <i class="-item bi-caret-left-fill profile-toke-count" id="toggle-toke-list"></i>
</span> </span>
<div class="profile-item nested-dynamic-container" id="profile-tokes"> <div class="profile-item nested-dynamic-container" id="profile-tokes">
<% profile.tokes.forEach((count, toke) => { %> <% profile.tokes.forEach((count, toke) => { %>
<% if(toke != "Legacy Tokes"){ %> <% if(toke != "Legacy Tokes"){ %>
<p class="profile-item profile-toke" id='profile-tokes<%-toke%>'>!<%- toke %>: <%- count %></p> <p class="profile-item profile-toke" id='profile-tokes<%= unescape(toke)%>'>!<%= unescape(toke) %>: <%= count %></p>
<% } %> <% } %>
<% }); %> <% }); %>
<% if(profile.tokes.get("Legacy Tokes") != null){ %> <% if(profile.tokes.get("Legacy Tokes") != null){ %>
<p class="profile-item profile-toke" id='profile-tokes-legacy-tokes'><br>Legacy Tokes: <%- profile.tokes.get("Legacy Tokes") %></p> <p class="profile-item profile-toke" id='profile-tokes-legacy-tokes'><br>Legacy Tokes: <%= profile.tokes.get("Legacy Tokes") %></p>
<% } %> <% } %>
</div> </div>

View file

@ -17,14 +17,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<h3 class="tooltip">Known Alts:</h3> <h3 class="tooltip">Known Alts:</h3>
<% for(let alt in alts){%> <% for(let alt in alts){%>
<div class="seperator"></div> <div class="seperator"></div>
<p class="tooltip">User: <%- alts[alt].user %></p> <p class="tooltip">User: <%= unescape(alts[alt].user) %></p>
<p class="tooltip">ID: <%- alts[alt].id %></p> <p class="tooltip">ID: <%= alts[alt].id %></p>
<% if(alts[alt].pronouns != '' && alts[alt].pronouns != null){ %> <% if(alts[alt].pronouns != '' && alts[alt].pronouns != null){ %>
<p class="tooltip">Pronouns: <%- alts[alt].pronouns %></p> <p class="tooltip">Pronouns: <%= unescape(alts[alt].pronouns) %></p>
<% } %> <% } %>
<p class="tooltip">Signature: <%- alts[alt].signature %></p> <p class="tooltip">Signature: <%= unescape(alts[alt].signature) %></p>
<p class="tooltip">Toke Count: <%- alts[alt].tokeCount %></p> <p class="tooltip">Toke Count: <%= alts[alt].tokeCount %></p>
<p class="tooltip">Joined: <%- alts[alt].date.toLocaleString() %></p> <p class="tooltip">Joined: <%= unescape(alts[alt].date.toLocaleString()) %></p>
<% } %> <% } %>
<% }else{ %> <% }else{ %>
<p class="tooltip">No alts detected...</p> <p class="tooltip">No alts detected...</p>

View file

@ -17,11 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% if(profile == null){ %> <% if(profile == null){ %>
<p>Profile not found!</p> <p>Profile not found!</p>
<% }else{ %> <% }else{ %>
<h2 class="tooltip profile-name"><%- profile.user %></h2> <h2 class="tooltip profile-name"><%= unescape(profile.user) %></h2>
<img class="tooltip profile-img" src="<%- profile.img %>"> <img class="tooltip profile-img" src="<%= encodeURI(unescape(profile.img)) %>">
<p class="tooltip">Toke Count: <%- profile.tokeCount %></p> <p class="tooltip">Toke Count: <%= profile.tokeCount %></p>
<% if(profile.pronouns != '' && profile.pronouns != null){ %> <% if(profile.pronouns != '' && profile.pronouns != null){ %>
<p class="tooltip">Pronouns: <%- profile.pronouns %></p> <p class="tooltip">Pronouns: <%= unescape(profile.pronouns) %></p>
<% } %> <% } %>
<p class="tooltip">Signature: <%- profile.signature %></p> <p class="tooltip">Signature: <%= unescape(profile.signature) %></p>
<% } %> <% } %>

View file

@ -32,21 +32,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<div id="account"> <div id="account">
<div class="profile dynamic-container" id="profile-div"> <div class="profile dynamic-container" id="profile-div">
<span id="profile-info" class="profile"> <span id="profile-info" class="profile">
<h1 class="profile-item" id="profile-username"><%- profile.user %></h1> <h1 class="profile-item" id="profile-username"><%= unescape(profile.user) %></h1>
<%- include('partial/profile/status', {profile, presence, auxClass: ""}); %> <%- include('partial/profile/status', {profile, presence, auxClass: "", unescape}); %>
<%- include('partial/profile/image', {profile, selfProfile}); %> <%- include('partial/profile/image', {profile, selfProfile, unescape}); %>
<%- include('partial/profile/pronouns', {profile, selfProfile}); %> <%- include('partial/profile/pronouns', {profile, selfProfile, unescape}); %>
<%- include('partial/profile/signature', {profile, selfProfile}); %> <%- include('partial/profile/signature', {profile, selfProfile, unescape}); %>
<%- include('partial/profile/tokeCount', {profile, selfProfile}); %> <%- include('partial/profile/tokeCount', {profile, selfProfile, unescape}); %>
<%- include('partial/profile/date', {profile, selfProfile}); %> <%- include('partial/profile/date', {profile, selfProfile}); %>
</span> </span>
<span id="profile-info-aux" class="profile"> <span id="profile-info-aux" class="profile">
<%- include('partial/profile/bio', {profile, selfProfile}); %> <%- include('partial/profile/bio', {profile, selfProfile, unescape}); %>
<%- include('partial/profile/badges', {profile, selfProfile}); %> <%- include('partial/profile/badges', {profile, selfProfile}); %>
</span> </span>
</div> </div>
<% if(selfProfile){ %> <% if(selfProfile){ %>
<%- include('partial/profile/settings', {profile, selfProfile}); %> <%- include('partial/profile/settings', {profile, selfProfile, unescape}); %>
<% } %> <% } %>
</div> </div>
<% }else if(user){ %> <% }else if(user){ %>

View file

@ -48,6 +48,12 @@ p.panel{
} }
.panel.profile-bio{ .panel.profile-bio{
margin-top: 0; margin-top: 1.5em;
text-wrap: wrap; text-wrap: wrap;
text-align: center;
}
.panel.profile-box{
margin: 2em 5%;
padding: 0.2em 0;
} }