From af7f4219a54082ef81705fb97ff640aeae3743ec Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Wed, 11 Dec 2024 07:16:42 -0500 Subject: [PATCH] Started work on site-wide toke command management & errorHandler --- .../api/account/registerController.js | 3 +- .../api/admin/tokeCommandController.js | 64 +++++++++++++++++++ src/routers/api/accountRouter.js | 2 +- src/routers/api/adminRouter.js | 6 +- src/routers/api/channelRouter.js | 4 +- src/schemas/permissionSchema.js | 6 ++ src/utils/loggerUtils.js | 7 +- src/validators/accountValidator.js | 4 +- src/validators/channelValidator.js | 4 +- src/validators/permissionsValidator.js | 6 ++ src/validators/tokebotValidator.js | 22 +++++++ www/js/adminPanel.js | 29 +++++++++ 12 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 src/controllers/api/admin/tokeCommandController.js create mode 100644 src/validators/tokebotValidator.js diff --git a/src/controllers/api/account/registerController.js b/src/controllers/api/account/registerController.js index 4ab51c4..bcd5411 100644 --- a/src/controllers/api/account/registerController.js +++ b/src/controllers/api/account/registerController.js @@ -35,8 +35,7 @@ module.exports.post = async function(req, res){ //if we found any related nuked bans if(nukedBans != null){ //Shit our pants! - res.status(401); - return res.send({errors:[{msg:"Cannot re-create banned account!",type:"unauthorized"}]}); + return errorHandler(res, 'Cannon re-create banned account!', 'unauthorized'); } await userModel.register(user) diff --git a/src/controllers/api/admin/tokeCommandController.js b/src/controllers/api/admin/tokeCommandController.js new file mode 100644 index 0000000..8231fc6 --- /dev/null +++ b/src/controllers/api/admin/tokeCommandController.js @@ -0,0 +1,64 @@ +/*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 {validationResult, matchedData} = require('express-validator'); + +//local imports +const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils.js'); +const tokeCommandModel = require('../../../schemas/tokebot/tokeCommandSchema.js'); + +module.exports.get = async function(req, res){ + try{ + const tokeList = await tokeCommandModel.getCommandStrings(); + + res.status(200); + return res.send(tokeList); + }catch(err){ + return exceptionHandler(res, err); + } +} + +module.exports.post = async function(req, res){ + try{ + //get validation error results + const validResult = validationResult(req); + + //if they're empty + if(validResult.isEmpty()){ + const {command} = matchedData(req); + const foundToke = await tokeCommandModel.findOne({command}); + + if(foundToke != null){ + return errorHandler(res, `Toke command '!${command}' already exists!`); + } + + //Add the toke + const tokeDB = await tokeCommandModel.create({command}); + + //Return the updated command list + res.status(200); + return res.send(await tokeCommandModel.getCommandStrings()); + }else{ + //otherwise scream + res.status(400); + return res.send({errors: validResult.array()}) + } + + }catch(err){ + return exceptionHandler(res, err); + } +} \ No newline at end of file diff --git a/src/routers/api/accountRouter.js b/src/routers/api/accountRouter.js index d22e2d7..df16f34 100644 --- a/src/routers/api/accountRouter.js +++ b/src/routers/api/accountRouter.js @@ -18,7 +18,7 @@ along with this program. If not, see .*/ const { Router } = require('express'); //local imports -const {accountValidator} = require("../../validators/accountValidator"); +const accountValidator = require("../../validators/accountValidator"); const loginController = require("../../controllers/api/account/loginController"); const logoutController = require("../../controllers/api/account/logoutController"); const registerController = require("../../controllers/api/account/registerController"); diff --git a/src/routers/api/adminRouter.js b/src/routers/api/adminRouter.js index 1a0e254..d065d1a 100644 --- a/src/routers/api/adminRouter.js +++ b/src/routers/api/adminRouter.js @@ -20,14 +20,16 @@ const { Router } = require('express'); //local imports -const {accountValidator} = require("../../validators/accountValidator"); +const accountValidator = require("../../validators/accountValidator"); const {permissionsValidator, channelPermissionValidator} = require("../../validators/permissionsValidator"); +const tokebotValidator = require("../../validators/tokebotValidator"); const permissionSchema = require("../../schemas/permissionSchema"); const listUsersController = require("../../controllers/api/admin/listUsersController"); const listChannelsController = require("../../controllers/api/admin/listChannelsController"); const changeRankController = require("../../controllers/api/admin/changeRankController"); const permissionsController = require("../../controllers/api/admin/permissionsController"); const banController = require("../../controllers/api/admin/banController"); +const tokeCommandController = require('../../controllers/api/admin/tokeCommandController'); //globals const router = Router(); @@ -42,5 +44,7 @@ router.get('/ban', permissionSchema.reqPermCheck("adminPanel"), banController.g //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.delete('/ban', permissionSchema.reqPermCheck("banUser"), accountValidator.user(), banController.delete); +router.get('/tokeCommands', permissionSchema.reqPermCheck("adminPanel"), tokeCommandController.get); +router.post('/tokeCommands', permissionSchema.reqPermCheck("editTokeCommands"), tokebotValidator.command(), tokeCommandController.post); module.exports = router; diff --git a/src/routers/api/channelRouter.js b/src/routers/api/channelRouter.js index 88f85b9..2722938 100644 --- a/src/routers/api/channelRouter.js +++ b/src/routers/api/channelRouter.js @@ -21,8 +21,8 @@ const { Router } = require('express'); //local imports const permissionSchema = require("../../schemas/permissionSchema"); const channelModel = require("../../schemas/channel/channelSchema"); -const {channelValidator} = require("../../validators/channelValidator"); -const {accountValidator} = require("../../validators/accountValidator"); +const channelValidator = require("../../validators/channelValidator"); +const accountValidator = require("../../validators/accountValidator"); const {channelPermissionValidator} = require("../../validators/permissionsValidator"); const registerController = require("../../controllers/api/channel/registerController"); const listController = require("../../controllers/api/channel/listController"); diff --git a/src/schemas/permissionSchema.js b/src/schemas/permissionSchema.js index 8f361bd..a8354e9 100644 --- a/src/schemas/permissionSchema.js +++ b/src/schemas/permissionSchema.js @@ -51,6 +51,12 @@ const permissionSchema = new mongoose.Schema({ default: "admin", required: true }, + editTokeCommands: { + type: mongoose.SchemaTypes.String, + enum: rankEnum, + default: "admin", + required: true + }, banUser: { type: mongoose.SchemaTypes.String, enum: rankEnum, diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js index fc38e50..049f716 100644 --- a/src/utils/loggerUtils.js +++ b/src/utils/loggerUtils.js @@ -15,10 +15,15 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ //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"){ + res.status(400); + return res.send({errors: [{type, msg, date: new Date()}]}); +} + module.exports.exceptionHandler = function(res, err){ //if not yell at the browser for fucking up, and tell it what it did wrong. res.status(400); - return res.send({errors: [{type: "Caught Exception", msg: err.message, date: new Date()}]}); + module.exports.errorHandler(res, err.message, "Caught Exception"); } module.exports.socketExceptionHandler = function(socket, err){ diff --git a/src/validators/accountValidator.js b/src/validators/accountValidator.js index 16f562d..d875abd 100644 --- a/src/validators/accountValidator.js +++ b/src/validators/accountValidator.js @@ -20,13 +20,13 @@ const { check, body, checkSchema, checkExact} = require('express-validator'); //local imports const {isRank} = require('./permissionsValidator'); -module.exports.accountValidator = { +module.exports = { user: (field = 'user') => body(field).escape().trim().isLength({min: 1, max: 22}), //Password security requirements may change over time, therefore we should only validate against strongPassword() when creating new accounts //that way we don't break old ones upon change pass: (field = 'pass') => body(field).notEmpty().escape().trim(), - securePass: (field) => module.exports.accountValidator.pass(field).isStrongPassword({minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1}), + securePass: (field) => module.exports.pass(field).isStrongPassword({minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1}), email: (field = 'email') => body(field).optional().isEmail().normalizeEmail(), diff --git a/src/validators/channelValidator.js b/src/validators/channelValidator.js index d4baafc..3309c30 100644 --- a/src/validators/channelValidator.js +++ b/src/validators/channelValidator.js @@ -18,9 +18,9 @@ along with this program. If not, see .*/ const { check, body, checkSchema, checkExact} = require('express-validator'); //local imports -const {accountValidator} = require('./accountValidator'); +const accountValidator = require('./accountValidator'); -module.exports.channelValidator = { +module.exports = { name: (field = 'name') => check(field).escape().trim().isLength({min: 1, max: 50}), description: (field = 'description') => body(field).escape().trim().isLength({min: 1, max: 1000}), diff --git a/src/validators/permissionsValidator.js b/src/validators/permissionsValidator.js index a244b5f..1ff4662 100644 --- a/src/validators/permissionsValidator.js +++ b/src/validators/permissionsValidator.js @@ -52,6 +52,12 @@ module.exports.permissionsValidator = { options: module.exports.isRank }, }, + 'permissionsMap.editTokeCommands': { + optional: true, + custom: { + options: module.exports.isRank + } + }, 'permissionsMap.banUser': { optional: true, custom: { diff --git a/src/validators/tokebotValidator.js b/src/validators/tokebotValidator.js new file mode 100644 index 0000000..67744e5 --- /dev/null +++ b/src/validators/tokebotValidator.js @@ -0,0 +1,22 @@ +/*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 { check } = require('express-validator'); + +module.exports = { + command: (field = 'command') => check(field).escape().trim().isAlphanumeric().isLength({min: 1, max: 30}), +} \ No newline at end of file diff --git a/www/js/adminPanel.js b/www/js/adminPanel.js index d48e82c..8637d90 100644 --- a/www/js/adminPanel.js +++ b/www/js/adminPanel.js @@ -169,6 +169,35 @@ class canopyAdminUtils{ utils.ux.displayResponseError(await response.json()); } } + + async getTokeCommands(){ + var response = await fetch(`/api/admin/tokeCommands`,{ + method: "GET" + }); + + if(response.status == 200){ + return await response.json(); + }else{ + utils.ux.displayResponseError(await response.json()); + } + } + + async addTokeCommand(command){ + var response = await fetch(`/api/admin/tokeCommands`,{ + method: "POST", + headers: { + "Content-Type": "application/json" + }, + //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({command}) + }); + + if(response.status == 200){ + return await response.json(); + }else{ + utils.ux.displayResponseError(await response.json()); + } + } } class adminUserList{