Finished up with IP-Ban functionality on the back-end. Just need to finish up with UI.

This commit is contained in:
rainbow napkin 2025-01-01 17:36:43 -05:00
parent 756c42ceaa
commit 977e8e1e2e
16 changed files with 284 additions and 67 deletions

View file

@ -1,5 +1,6 @@
{ {
"instanceName": "Canopy", "instanceName": "Canopy",
"verbose": false,
"port": 8080, "port": 8080,
"protocol": "http", "protocol": "http",
"domain": "localhost", "domain": "localhost",

View file

@ -18,6 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
const channelModel = require('../../schemas/channel/channelSchema'); const channelModel = require('../../schemas/channel/channelSchema');
const emoteModel = require('../../schemas/emoteSchema'); const emoteModel = require('../../schemas/emoteSchema');
const {userModel} = require('../../schemas/user/userSchema'); const {userModel} = require('../../schemas/user/userSchema');
const userBanModel = require('../../schemas/user/userBanSchema');
const loggerUtils = require('../../utils/loggerUtils'); const loggerUtils = require('../../utils/loggerUtils');
const csrfUtils = require('../../utils/csrfUtils'); const csrfUtils = require('../../utils/csrfUtils');
const activeChannel = require('./activeChannel'); const activeChannel = require('./activeChannel');
@ -39,23 +40,29 @@ module.exports = class{
} }
async handleConnection(socket){ async handleConnection(socket){
try{
//ensure unbanned ip and valid CSRF token
if(!(await this.validateSocket(socket))){
socket.disconnect();
return;
}
//Prevent logged out connections and authenticate socket //Prevent logged out connections and authenticate socket
if(socket.request.session.user != null){ if(socket.request.session.user != null){
try{
//Authenticate socket //Authenticate socket
const userDB = await this.authSocket(socket); const userDB = await this.authSocket(socket);
//Get the active channel based on the socket //Get the active channel based on the socket
var {activeChan, chanDB} = await this.getActiveChan(socket); var {activeChan, chanDB} = await this.getActiveChan(socket);
//Check for ban //Check for chan ban
const ban = await chanDB.checkBanByUserDoc(userDB); const ban = await chanDB.checkBanByUserDoc(userDB);
if(ban != null){ if(ban != null){
//Toss out banned user's //Toss out banned user's
if(ban.expirationDays < 0){ if(ban.expirationDays < 0){
socket.emit("kick", {type: "Banned", reason: "You have been permanently banned from this channel!"}); socket.emit("kick", {type: "kicked", reason: "You have been permanently banned from this channel!"});
}else{ }else{
socket.emit("kick", {type: "Banned", reason: `You have been temporarily banned from this channel, and will be unbanned in ${ban.getDaysUntilExpiration()} day(s)!`}); socket.emit("kick", {type: "kicked", reason: `You have been temporarily banned from this channel, and will be unbanned in ${ban.getDaysUntilExpiration()} day(s)!`});
} }
socket.disconnect(); socket.disconnect();
return; return;
@ -68,24 +75,41 @@ module.exports = class{
//Connect the socket to it's given channel //Connect the socket to it's given channel
//Lil' hacky to pass chanDB like that, but why double up on DB calls? //Lil' hacky to pass chanDB like that, but why double up on DB calls?
activeChan.handleConnection(userDB, chanDB, socket); activeChan.handleConnection(userDB, chanDB, socket);
}else{
//Toss out anon's
socket.emit("kick", {type: "disconnected", reason: "You must log-in to join this channel!"});
socket.disconnect();
return;
}
}catch(err){ }catch(err){
//Flip a table if something fucks up //Flip a table if something fucks up
return loggerUtils.socketCriticalExceptionHandler(socket, err); return loggerUtils.socketCriticalExceptionHandler(socket, err);
} }
}else{
//Toss out anon's
socket.emit("kick", {type: "Disconnected", reason: "You must log-in to join this channel!"});
socket.disconnect();
return;
} }
async validateSocket(socket){
//Look for ban by IP
const ipBanDB = await userBanModel.checkBanByIP(socket.handshake.address);
//If this ip is randy bobandy
if(ipBanDB != null){
//tell it to fuck off
socket.emit("kick", {type: "kicked", reason: "The IP address you are trying to connect from has been banned!"});
return false;
}
//Check for Cross-Site Request Forgery
if(!csrfUtils.isRequestValid(socket.request)){
socket.emit("kick", {type: "disconnected", reason: "Invalid CSRF Token!"});
return false;
}
return true;
} }
async authSocket(socket){ async authSocket(socket){
//Check for Cross-Site Request Forgery
if(!csrfUtils.isRequestValid(socket.request)){
throw new Error("Invalid CSRF Token!");
}
//Find the user in the Database since the session won't store enough data to fulfill our needs :P //Find the user in the Database since the session won't store enough data to fulfill our needs :P
const userDB = await userModel.findOne({user: socket.request.session.user.user}); const userDB = await userModel.findOne({user: socket.request.session.user.user});

View file

@ -71,7 +71,6 @@ module.exports.post = async function(req, res){
return res.send({errors: validResult.array()}); return res.send({errors: validResult.array()});
} }
}catch(err){ }catch(err){
console.log(err)
return exceptionHandler(res, err); return exceptionHandler(res, err);
} }
} }

View file

@ -52,6 +52,22 @@ module.exports.post = async function(req, res){
return errorHandler(res, 'Cannot re-create banned account!', 'unauthorized'); return errorHandler(res, 'Cannot re-create banned account!', 'unauthorized');
} }
//Look for ban by IP
const ipBanDB = await userBanModel.checkBanByIP(req.ip);
//If this ip is randy bobandy
if(ipBanDB != null){
//Make the code and message look pretty (kinda) at the same time
const banMsg = [
'The IP address you are trying to register an account from has been banned.',
'If you beleive this to be an error feel free to reach out to your server administrator.',
'Otherwise, fuck off :)'
];
//tell it to fuck off
return errorHandler(res, banMsg.join('<br>'), 'unauthorized');
}
await userModel.register(user, req.ip); await userModel.register(user, req.ip);
return res.sendStatus(200); return res.sendStatus(200);
}else{ }else{
@ -59,7 +75,6 @@ module.exports.post = async function(req, res){
return res.send({errors: validResult.array()}); return res.send({errors: validResult.array()});
} }
}catch(err){ }catch(err){
console.log(err);
return exceptionHandler(res, err); return exceptionHandler(res, err);
} }
} }

View file

@ -40,7 +40,7 @@ module.exports.post = async function(req, res){
try{ try{
const validResult = validationResult(req); const validResult = validationResult(req);
if(validResult.isEmpty()){ if(validResult.isEmpty()){
const {user, permanent, expirationDays} = matchedData(req); const {user, permanent, ipBan, expirationDays} = matchedData(req);
const userDB = await userModel.findOne({user}); const userDB = await userModel.findOne({user});
if(userDB == null){ if(userDB == null){
@ -54,7 +54,7 @@ module.exports.post = async function(req, res){
return errorHandler(res, 'You cannot ban peer/outranking users', 'Unauthorized', 401); return errorHandler(res, 'You cannot ban peer/outranking users', 'Unauthorized', 401);
} }
await banModel.banByUserDoc(userDB, permanent, expirationDays); await banModel.banByUserDoc(userDB, permanent, expirationDays, ipBan);
res.status(200); res.status(200);
return res.send(await banModel.getBans()); return res.send(await banModel.getBans());

View file

@ -50,7 +50,7 @@ router.post('/changeRank', permissionSchema.reqPermCheck("changeRank"), account
//Ban //Ban
router.get('/ban', permissionSchema.reqPermCheck("adminPanel"), banController.get); router.get('/ban', permissionSchema.reqPermCheck("adminPanel"), banController.get);
//Sometimes they're so simple you don't need to put your validators in their own special place :P //Sometimes they're so simple you don't need to put your validators in their own special place :P
router.post('/ban', permissionSchema.reqPermCheck("banUser"), accountValidator.user(), body("permanent").isBoolean(), body("expirationDays").isInt(), banController.post); router.post('/ban', permissionSchema.reqPermCheck("banUser"), accountValidator.user(), body("permanent").isBoolean(), body("ipBan").isBoolean(), body("expirationDays").isInt(), banController.post);
router.delete('/ban', permissionSchema.reqPermCheck("banUser"), accountValidator.user(), banController.delete); router.delete('/ban', permissionSchema.reqPermCheck("banUser"), accountValidator.user(), banController.delete);
//TokeCommands //TokeCommands
router.get('/tokeCommands', permissionSchema.reqPermCheck("adminPanel"), tokeCommandController.get); router.get('/tokeCommands', permissionSchema.reqPermCheck("adminPanel"), tokeCommandController.get);

View file

@ -328,18 +328,10 @@ channelSchema.methods.rankCrawl = async function(userDB,cb){
//TODO: replace this with rank check function shared with setRank //TODO: replace this with rank check function shared with setRank
this.rankList.forEach(async (rankObj, rankIndex) => { this.rankList.forEach(async (rankObj, rankIndex) => {
//check against user ID to speed things up //check against user ID to speed things up
if(rankObj.user.user == null){ if(rankObj.user != null && rankObj.user._id.toString() == userDB._id.toString()){
if(rankObj.user.toString() == userDB._id.toString()){
//If we found a match, call back //If we found a match, call back
cb(rankObj, rankIndex); cb(rankObj, rankIndex);
} }
}else{
//in case someone populated the users
if(rankObj.user.user == userDB.user){
//If we found a match, call back
cb(rankObj, rankIndex);
}
}
}); });
} }
@ -379,14 +371,22 @@ channelSchema.methods.setRank = async function(userDB,rank){
channelSchema.methods.getRankList = async function(){ channelSchema.methods.getRankList = async function(){
//Create an empty array to hold the user list //Create an empty array to hold the user list
const rankList = new Map() const rankList = new Map()
//Create temp rank list to replace the current one in the advant we have busted users
let tempRankList = [];
//Flag that lets us know we gotta save
let reqSave = false;
//Populate the user objects in our ranklist based off of their DB ID's //Populate the user objects in our ranklist based off of their DB ID's
await this.populate('rankList.user'); await this.populate('rankList.user');
//For each rank object in the rank list //For each rank object in the rank list
this.rankList.forEach(async (rankObj, rankObjIndex) => { for(rankObjIndex in this.rankList){
const rankObj = this.rankList[rankObjIndex];
//If the use still exists //If the use still exists
if(rankObj.user != null){ if(rankObj.user != null){
//Push current rank object to the temp rank list in the advant that it doesn't get saved
tempRankList.push(rankObj);
//Create a new user object from rank object data //Create a new user object from rank object data
const userObj = { const userObj = {
id: rankObj.user.id, id: rankObj.user.id,
@ -397,12 +397,20 @@ channelSchema.methods.getRankList = async function(){
//Add our user object to the list //Add our user object to the list
rankList.set(rankObj.user.user, userObj); rankList.set(rankObj.user.user, userObj);
//Otherwise if it's an invalid rank for a deleted user
}else{ }else{
//Otherwise clean deleted users out of list //Ignore the rank object and throw the save flag to save the temporary rank list
this.rankList.splice(rankObjIndex,1); reqSave = true;
}
}
//if we need to save the temp rank list
if(reqSave){
//set rank list
this.rankList = tempRankList;
//save
await this.save(); await this.save();
} }
});
//return userList //return userList
return rankList; return rankList;

View file

@ -75,7 +75,6 @@ emoteSchema.statics.loadDefaults = async function(){
}catch(err){ }catch(err){
if(emote != null){ if(emote != null){
console.log(err);
console.log(`Error loading emote [${emote.name}]:`); console.log(`Error loading emote [${emote.name}]:`);
}else{ }else{
console.log("Error, null emote:"); console.log("Error, null emote:");

View file

@ -60,7 +60,6 @@ flairSchema.statics.loadDefaults = async function(){
}catch(err){ }catch(err){
if(flair != null){ if(flair != null){
console.log(err);
console.log(`Error loading flair '${flair.name}':`); console.log(`Error loading flair '${flair.name}':`);
}else{ }else{
console.log("Error, null flair:"); console.log("Error, null flair:");

View file

@ -93,7 +93,6 @@ tokeCommandSchema.statics.loadDefaults = async function(){
}catch(err){ }catch(err){
if(toke != null){ if(toke != null){
console.log(err);
console.log(`Error loading toke command: '!${toke}'`); console.log(`Error loading toke command: '!${toke}'`);
}else{ }else{
console.log("Error, null toke!"); console.log("Error, null toke!");

View file

@ -18,6 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
const {mongoose} = require('mongoose'); const {mongoose} = require('mongoose');
//Local Imports //Local Imports
const hashUtil = require('../../utils/hashUtils');
const {userModel} = require('./userSchema'); const {userModel} = require('./userSchema');
const userBanSchema = new mongoose.Schema({ const userBanSchema = new mongoose.Schema({
@ -25,16 +26,20 @@ const userBanSchema = new mongoose.Schema({
type: mongoose.SchemaTypes.ObjectID, type: mongoose.SchemaTypes.ObjectID,
ref: "user" ref: "user"
}, },
//To be used in future when ip-hashing/better session tracking is implemented
ips: { ips: {
plaintext: {
type: [mongoose.SchemaTypes.String], type: [mongoose.SchemaTypes.String],
required: false required: false
}, },
//To be used in future when alt-detection has been implemented hashed: {
alts: { type: [mongoose.SchemaTypes.String],
required: false
}
},
alts: [{
type: mongoose.SchemaTypes.ObjectID, type: mongoose.SchemaTypes.ObjectID,
ref: "user" ref: "user"
}, }],
deletedNames: { deletedNames: {
type: [mongoose.SchemaTypes.String], type: [mongoose.SchemaTypes.String],
required: false required: false
@ -58,15 +63,92 @@ const userBanSchema = new mongoose.Schema({
} }
}); });
userBanSchema.statics.checkBanByIP = async function(ip){
//Get hash of ip
const ipHash = hashUtil.hashIP(ip);
//Get all bans
const banDB = await this.find({});
//Create null variable to hold any found ban
let foundBan = null;
//For every ban
for(ban of banDB){
//Create empty list to hold unmatched hashes in the advent that we match one
let tempHashes = [];
//Create flag to throw to save tempHashes in the advent that we have matches we dont want to save as hashes
let saveBan = false;
//For every plaintext IP in the ban
for(ipIndex in ban.ips.plaintext){
//Get the current ip
const curIP = ban.ips.plaintext[ipIndex];
//Check the current IP against the given ip
if(ip == curIP){
//If it matches we found the ban
foundBan = ban;
}
}
//For every hashed IP in the ban
for(ipIndex in ban.ips.hashed){
//Get the current ip hash
const curHash = ban.ips.hashed[ipIndex];
//Check the current hash against the given hash
if(ipHash == curHash){
//If it matches we found the ban
foundBan = ban;
//Push the match to plaintext IPs so we know who the fucker is
ban.ips.plaintext.push(ip);
//Throw the save ban flag to save the ban
saveBan = true;
//Otherwise
}else{
//Keep the hash since it hasn't been matched yet
tempHashes.push(curHash);
}
}
//If we matched a hashed ip and we need to save it as plaintext
if(saveBan){
//Keep unmatched hashes
ban.ips.hashed = tempHashes;
//Save the current ban
await ban.save();
}
}
return foundBan;
}
userBanSchema.statics.checkBanByUserDoc = async function(userDB){ userBanSchema.statics.checkBanByUserDoc = async function(userDB){
const banDB = await this.find({}); const banDB = await this.find({});
var foundBan = null; var foundBan = null;
banDB.forEach((ban) => { banDB.forEach((ban) => {
if(ban.user != null){ if(ban.user != null){
//if we found a match
if(ban.user.toString() == userDB._id.toString()){ if(ban.user.toString() == userDB._id.toString()){
//Set found ban
foundBan = ban; foundBan = ban;
} }
//For each banned alt
for(altIndex in ban.alts){
//get current alt
const alt = ban.alts[altIndex];
//if the alt matches our user
if(alt._id.toString() == userDB._id.toString()){
//Set found ban
foundBan = ban;
}
}
} }
}); });
@ -99,7 +181,7 @@ userBanSchema.statics.checkProcessedBans = async function(user){
return foundBan; return foundBan;
} }
userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expirationDays){ userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expirationDays, ipBan = false){
//Prevent missing users //Prevent missing users
if(userDB == null){ if(userDB == null){
throw new Error("User not found") throw new Error("User not found")
@ -110,6 +192,7 @@ userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expiratio
throw new Error("User already banned"); throw new Error("User already banned");
} }
//Verify time to expire/delete depending on action
if(expirationDays < 0){ if(expirationDays < 0){
throw new Error("Expiration Days must be a positive integer!"); throw new Error("Expiration Days must be a positive integer!");
}else if(expirationDays < 30 && permanent){ }else if(expirationDays < 30 && permanent){
@ -118,20 +201,65 @@ userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expiratio
throw new Error("Expiration/Deletion date cannot be longer than half a year out from the original ban date."); throw new Error("Expiration/Deletion date cannot be longer than half a year out from the original ban date.");
} }
//Log the user out await banSessions(userDB);
if(permanent){
await userDB.killAllSessions(`Your account has been permanently banned, and will be nuked from the database in ${expirationDays} day(s).`);
}else{
await userDB.killAllSessions(`Your account has been temporarily banned, and will be reinstated in: ${expirationDays} day(s).`);
}
//Add the ban to the database //Add the ban to the database
return await this.create({user: userDB._id, permanent, expirationDays}); const banDB = await this.create({user: userDB._id, permanent, expirationDays});
//If we're banning the users IP
if(ipBan){
//Scrape IP's from current user into the ban record
await scrapeUserIPs(userDB);
//Populate the users alts
await userDB.populate('alts');
//For each of the users alts
for(altIndex in userDB.alts){
//Add the current alt to the ban record
banDB.alts.push(userDB.alts[altIndex]._id);
//Scrape out the IPs from the current alt into the ban record
await scrapeUserIPs(userDB.alts[altIndex]);
//Kill all of alts sessions
await banSessions(userDB.alts[altIndex]);
} }
userBanSchema.statics.ban = async function(user, permanent, expirationDays){ //Save commited IP information to the ban record
await banDB.save();
async function scrapeUserIPs(curRecord){
//For each hashed ip on record for this user
for(hashIndex in curRecord.recentIPs){
//Look for any occurance of the current hash
const foundHash = banDB.ips.hashed.indexOf(curRecord.recentIPs[hashIndex].ipHash);
//If its not listed in the ban record
if(foundHash == -1){
//Add it to the list of hashed IPs for this ban
banDB.ips.hashed.push(curRecord.recentIPs[hashIndex].ipHash);
}
}
}
}
//return the ban record
return banDB;
async function banSessions(user){
//Log the user out
if(permanent){
await user.killAllSessions(`Your account has been permanently banned, and will be nuked from the database in ${expirationDays} day(s).`);
}else{
await user.killAllSessions(`Your account has been temporarily banned, and will be reinstated in: ${expirationDays} day(s).`);
}
}
}
userBanSchema.statics.ban = async function(user, permanent, expirationDays, ipBan){
const userDB = await userModel.findOne({user: user.user}); const userDB = await userModel.findOne({user: user.user});
return this.banByUserDoc(userDB, permanent, expirationDays); return this.banByUserDoc(userDB, permanent, expirationDays, ipBan);
} }
userBanSchema.statics.unbanByUserDoc = async function(userDB){ userBanSchema.statics.unbanByUserDoc = async function(userDB){
@ -217,6 +345,7 @@ userBanSchema.statics.getBans = async function(){
userBanSchema.statics.processExpiredBans = async function(){ userBanSchema.statics.processExpiredBans = async function(){
const banDB = await this.find({}); const banDB = await this.find({});
//Firem all off all at once seperately without waiting for one another
banDB.forEach(async (ban) => { banDB.forEach(async (ban) => {
//This ban was already processed, and it's user has been deleted. There is no more to be done... //This ban was already processed, and it's user has been deleted. There is no more to be done...
if(ban.user == null){ if(ban.user == null){
@ -227,14 +356,27 @@ userBanSchema.statics.processExpiredBans = async function(){
if(ban.getDaysUntilExpiration() <= 0){ if(ban.getDaysUntilExpiration() <= 0){
//If the ban is permanent //If the ban is permanent
if(ban.permanent){ if(ban.permanent){
//Populate the user field //Populate the user and alt fields
await ban.populate('user'); await ban.populate('user');
await ban.populate('alts');
//Add the name to our deleted names list //Add the name to our deleted names list
ban.deletedNames.push(ban.user.user); ban.deletedNames.push(ban.user.user);
//Hey hey hey, goodbye! //Hey hey hey, goodbye!
await userModel.deleteOne({_id: ban.user._id}); await userModel.deleteOne({_id: ban.user._id});
//Empty out the reference //Empty out the reference
ban.user = null; ban.user = null;
//For every alt
for(alt of ban.alts){
//Add the alts name to the deleted names list
ban.deletedNames.push(alt.user);
//Motherfuckin' Kablewie!
await userModel.deleteOne({_id: alt._id});
}
//Clear out the alts array
ban.alts = [];
//Save the ban //Save the ban
await ban.save(); await ban.save();
}else{ }else{
@ -242,7 +384,7 @@ userBanSchema.statics.processExpiredBans = async function(){
await this.deleteOne({_id: ban._id}); await this.deleteOne({_id: ban._id});
} }
} }
}) });
} }
//methods //methods

View file

@ -14,6 +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/>.*/
//Config
const config = require('../../config.json');
//At some point this will be a bit more advanced, right now it's just a placeholder :P //At some point this will be a bit more advanced, right now it's just a placeholder :P
module.exports.errorHandler = function(res, msg, type = "Generic", status = 400){ module.exports.errorHandler = function(res, msg, type = "Generic", status = 400){
//Some controllers do things after sending headers, for those, we should remain silent //Some controllers do things after sending headers, for those, we should remain silent
@ -24,11 +27,23 @@ module.exports.errorHandler = function(res, msg, type = "Generic", status = 400)
} }
module.exports.exceptionHandler = function(res, err){ module.exports.exceptionHandler = function(res, err){
//If we're being verbose
if(config.verbose){
//Log the error
console.log(err)
}
//if not yell at the browser for fucking up, and tell it what it did wrong. //if not yell at the browser for fucking up, and tell it what it did wrong.
module.exports.errorHandler(res, err.message, "Caught Exception"); module.exports.errorHandler(res, err.message, "Caught Exception");
} }
module.exports.socketExceptionHandler = function(socket, err){ module.exports.socketExceptionHandler = function(socket, err){
//If we're being verbose
if(config.verbose){
//Log the error
console.log(err)
}
//if not yell at the browser for fucking up, and tell it what it did wrong. //if not yell at the browser for fucking up, and tell it what it did wrong.
return socket.emit("error", {errors: [{type: "Caught Exception", msg: err.message, date: new Date()}]}); return socket.emit("error", {errors: [{type: "Caught Exception", msg: err.message, date: new Date()}]});
} }

View file

@ -32,6 +32,15 @@ module.exports.authenticateSession = async function(user, pass, req){
//Grab previous attempts //Grab previous attempts
const attempt = failedAttempts.get(user); const attempt = failedAttempts.get(user);
//Look for ban by IP
const ipBanDB = await userBanModel.checkBanByIP(req.ip);
//If this ip is randy bobandy
if(ipBanDB != null){
//tell it to fuck off
throw new Error(`The IP address you are trying to login from has been banned.`);
}
//If we have failed attempts //If we have failed attempts
if(attempt != null){ if(attempt != null){
//If we have more failed attempts than allowed //If we have more failed attempts than allowed
@ -53,13 +62,15 @@ module.exports.authenticateSession = async function(user, pass, req){
//Authenticate the session //Authenticate the session
const userDB = await userModel.authenticate(user, pass); const userDB = await userModel.authenticate(user, pass);
const banDB = await userBanModel.checkBanByUserDoc(userDB);
//Check for user ban
const userBanDB = await userBanModel.checkBanByUserDoc(userDB);
//If the user is banned //If the user is banned
if(banDB){ if(userBanDB){
//Make the number a little prettier despite the lack of precision since we're not doing calculations here :P //Make the number a little prettier despite the lack of precision since we're not doing calculations here :P
const expiration = banDB.getDaysUntilExpiration() < 1 ? 0 : banDB.getDaysUntilExpiration(); const expiration = userBanDB.getDaysUntilExpiration() < 1 ? 0 : userBanDB.getDaysUntilExpiration();
if(banDB.permanent){ if(userBanDB.permanent){
throw new Error(`Your account has been permanently banned, and will be nuked from the database in: ${expiration} day(s)`); throw new Error(`Your account has been permanently banned, and will be nuked from the database in: ${expiration} day(s)`);
}else{ }else{
throw new Error(`Your account has been temporarily banned, and will be reinstated in: ${expiration} day(s)`); throw new Error(`Your account has been temporarily banned, and will be reinstated in: ${expiration} day(s)`);

View file

@ -150,6 +150,10 @@ p.navbar-item, input.navbar-item{
margin-top: 0; margin-top: 0;
} }
.popup-plaintext-content{
text-align: center;
}
/* tooltip */ /* tooltip */
div.tooltip{ div.tooltip{
position: fixed; position: fixed;

View file

@ -139,7 +139,7 @@ class canopyAdminUtils{
} }
} }
async banUser(user, permanent, expirationDays){ async banUser(user, permanent, ipBan, expirationDays){
var response = await fetch(`/api/admin/ban`,{ var response = await fetch(`/api/admin/ban`,{
method: "POST", method: "POST",
headers: { headers: {
@ -147,7 +147,7 @@ class canopyAdminUtils{
"x-csrf-token": utils.ajax.getCSRFToken() "x-csrf-token": utils.ajax.getCSRFToken()
}, },
//Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible... //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
body: JSON.stringify({user, permanent, expirationDays}) body: JSON.stringify({user, permanent, ipBan, expirationDays})
}); });
if(response.status == 200){ if(response.status == 200){

View file

@ -234,6 +234,7 @@ class canopyUXUtils{
this.contentDiv.innerHTML = await utils.ajax.getPopup(this.content); this.contentDiv.innerHTML = await utils.ajax.getPopup(this.content);
}else{ }else{
this.contentDiv.innerHTML = this.content; this.contentDiv.innerHTML = this.content;
this.contentDiv.classList.add('popup-plaintext-content');
} }
//display popup nodes //display popup nodes