diff --git a/controllers/forms.js b/controllers/forms.js index 14fd6d8b..c9ae076d 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -10,7 +10,6 @@ const express = require('express') , processIp = require(__dirname+'/../helpers/processip.js') , calcPerms = require(__dirname+'/../helpers/checks/calcpermsmiddleware.js') , hasPerms = require(__dirname+'/../helpers/checks/haspermsmiddleware.js') - , paramConverter = require(__dirname+'/../helpers/paramconverter.js') , numFiles = require(__dirname+'/../helpers/numfiles.js') , imageHashes = require(__dirname+'/../helpers/imagehash.js') , banCheck = require(__dirname+'/../helpers/checks/bancheck.js') @@ -23,81 +22,61 @@ const express = require('express') , blockBypassCheck = require(__dirname+'/../helpers/checks/blockbypass.js') , fileMiddlewares = require(__dirname+'/../helpers/filemiddlewares.js') //controllers - , deleteBoardController = require(__dirname+'/forms/deleteboard.js') - , editBansController = require(__dirname+'/forms/editbans.js') - , appealController = require(__dirname+'/forms/appeal.js') - , globalActionController = require(__dirname+'/forms/globalactions.js') - , actionController = require(__dirname+'/forms/actions.js') - , addCustomPageController = require(__dirname+'/forms/addcustompage.js') - , deleteCustomPageController = require(__dirname+'/forms/deletecustompage.js') - , addNewsController = require(__dirname+'/forms/addnews.js') - , editNewsController = require(__dirname+'/forms/editnews.js') - , deleteNewsController = require(__dirname+'/forms/deletenews.js') - , uploadBannersController = require(__dirname+'/forms/uploadbanners.js') - , deleteBannersController = require(__dirname+'/forms/deletebanners.js') - , addFlagsController = require(__dirname+'/forms/addflags.js') - , deleteFlagsController = require(__dirname+'/forms/deleteflags.js') - , boardSettingsController = require(__dirname+'/forms/boardsettings.js') - , transferController = require(__dirname+'/forms/transfer.js') - , resignController = require(__dirname+'/forms/resign.js') - , deleteAccountController = require(__dirname+'/forms/deleteaccount.js') - , loginController = require(__dirname+'/forms/login.js') - , registerController = require(__dirname+'/forms/register.js') - , changePasswordController = require(__dirname+'/forms/changepassword.js') - , editAccountsController = require(__dirname+'/forms/editaccounts.js') - , globalSettingsController = require(__dirname+'/forms/globalsettings.js') - , createBoardController = require(__dirname+'/forms/create.js') - , makePostController = require(__dirname+'/forms/makepost.js') - , editPostController = require(__dirname+'/forms/editpost.js') - , newCaptcha = require(__dirname+'/../models/forms/newcaptcha.js') - , blockBypass = require(__dirname+'/../models/forms/blockbypass.js') - , logout = require(__dirname+'/../models/forms/logout.js'); + , { deleteBoardController, editBansController, appealController, globalActionController, + actionController, addCustomPageController, deleteCustomPageController, addNewsController, + editNewsController, deleteNewsController, uploadBannersController, deleteBannersController, + addFlagsController, deleteFlagsController, boardSettingsController, transferController, + resignController, deleteAccountController, loginController, registerController, changePasswordController, + editAccountsController, globalSettingsController, createBoardController, makePostController, + editPostController, newCaptcha, blockBypass, logout } = require(__dirname+'/forms/index.js'); + //make new post router.post('/board/:board/post', geoAndTor, fileMiddlewares.postsEarly, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, fileMiddlewares.posts, - paramConverter, verifyCaptcha, numFiles, blockBypassCheck, dnsblCheck, imageHashes, makePostController); + makePostController.paramConverter, verifyCaptcha, numFiles, blockBypassCheck, dnsblCheck, imageHashes, makePostController.controller); router.post('/board/:board/modpost', geoAndTor, fileMiddlewares.postsEarly, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), fileMiddlewares.posts, - paramConverter, csrf, numFiles, blockBypassCheck, dnsblCheck, makePostController); //mod post has token instead of captcha + makePostController.paramConverter, csrf, numFiles, blockBypassCheck, dnsblCheck, makePostController.controller); //mod post has token instead of captcha //post actions -router.post('/board/:board/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, paramConverter, verifyCaptcha, actionController); //public, with captcha -router.post('/board/:board/modactions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), paramConverter, actionController); //board manage page -router.post('/global/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(1), paramConverter, globalActionController); //global manage page +router.post('/board/:board/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, actionController.paramConverter, verifyCaptcha, actionController.controller); //public, with captcha +router.post('/board/:board/modactions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), actionController.paramConverter, actionController.controller); //board manage page +router.post('/global/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(1), globalActionController.paramConverter, globalActionController.controller); //global manage page //appeal ban -router.post('/appeal', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, paramConverter, verifyCaptcha, appealController); +router.post('/appeal', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, appealController.paramConverter, verifyCaptcha, appealController.controller); //edit post -router.post('/editpost', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, paramConverter, Boards.bodyExists, calcPerms, hasPerms(3), editPostController); +router.post('/editpost', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, editPostController.paramConverter, Boards.bodyExists, calcPerms, hasPerms(3), editPostController.controller); //board management forms -router.post('/board/:board/transfer', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, transferController); -router.post('/board/:board/settings', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, boardSettingsController); -router.post('/board/:board/addbanners', useSession, sessionRefresh, fileMiddlewares.banner, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, numFiles, uploadBannersController); //add banners -router.post('/board/:board/deletebanners', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, deleteBannersController); //delete banners -router.post('/board/:board/addflags', useSession, sessionRefresh, fileMiddlewares.flag, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, numFiles, addFlagsController); //add flags -router.post('/board/:board/deleteflags', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, deleteFlagsController); //delete flags -router.post('/board/:board/addcustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, addCustomPageController); //add banners -router.post('/board/:board/deletecustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), paramConverter, deleteCustomPageController); //delete banners -router.post('/board/:board/editbans', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(3), paramConverter, editBansController); //edit bans -router.post('/board/:board/deleteboard', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(config.get.deleteBoardPermLevel), deleteBoardController); //delete board +router.post('/board/:board/transfer', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), transferController.paramConverter, transferController.controller); +router.post('/board/:board/settings', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), boardSettingsController.paramConverter, boardSettingsController.controller); +router.post('/board/:board/addbanners', useSession, sessionRefresh, fileMiddlewares.banner, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), numFiles, uploadBannersController.controller); //add banners +router.post('/board/:board/deletebanners', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteBannersController.paramConverter, deleteBannersController.controller); //delete banners +router.post('/board/:board/addflags', useSession, sessionRefresh, fileMiddlewares.flag, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), numFiles, addFlagsController.controller); //add flags +router.post('/board/:board/deleteflags', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteFlagsController.paramConverter, deleteFlagsController.controller); //delete flags +router.post('/board/:board/addcustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), addCustomPageController.paramConverter, addCustomPageController.controller); //add banners +router.post('/board/:board/deletecustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteCustomPageController.paramConverter, deleteCustomPageController.controller); //delete banners +router.post('/board/:board/editbans', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(3), editBansController.paramConverter, editBansController.controller); //edit bans +router.post('/board/:board/deleteboard', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(config.get.deleteBoardPermLevel), deleteBoardController.controller); //delete board //global management forms -router.post('/global/editbans', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(1), paramConverter, editBansController); //remove bans -router.post('/global/deleteboard', useSession, sessionRefresh, csrf, paramConverter, calcPerms, isLoggedIn, hasPerms(Math.min(config.get.deleteBoardPermLevel, 1)), deleteBoardController); //delete board from global management panel -router.post('/global/addnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), paramConverter, addNewsController); //add new newspost -router.post('/global/editnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), paramConverter, editNewsController); //add new newspost -router.post('/global/deletenews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), paramConverter, deleteNewsController); //delete news -router.post('/global/editaccounts', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), paramConverter, editAccountsController); //account editing -router.post('/global/settings', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), paramConverter, globalSettingsController); //global settings +router.post('/global/editbans', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(1), editBansController.paramConverter, editBansController.controller); //remove bans +router.post('/global/deleteboard', useSession, sessionRefresh, csrf, deleteBoardController.paramConverter, calcPerms, isLoggedIn, hasPerms(Math.min(config.get.deleteBoardPermLevel, 1)), deleteBoardController.controller); //delete board from global management panel +router.post('/global/addnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), addNewsController.paramConverter, addNewsController.controller); //add new newspost +router.post('/global/editnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), editNewsController.paramConverter, editNewsController.controller); //add new newspost +router.post('/global/deletenews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), deleteNewsController.paramConverter, deleteNewsController.controller); //delete news +router.post('/global/editaccounts', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), editAccountsController.paramConverter, editAccountsController.controller); //account editing +router.post('/global/settings', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), globalSettingsController.paramConverter, globalSettingsController.controller); //global settings //create board -router.post('/create', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, isLoggedIn, verifyCaptcha, calcPerms, paramConverter, createBoardController); +router.post('/create', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, isLoggedIn, verifyCaptcha, calcPerms, createBoardController.paramConverter, createBoardController.controller); + //accounts -router.post('/login', useSession, paramConverter, loginController); +router.post('/login', useSession, loginController.paramConverter, loginController.controller); router.post('/logout', useSession, logout); -router.post('/register', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, verifyCaptcha, calcPerms, paramConverter, registerController); -router.post('/changepassword', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, verifyCaptcha, paramConverter, changePasswordController); -router.post('/resign', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, paramConverter, resignController); -router.post('/deleteaccount', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, paramConverter, deleteAccountController); +router.post('/register', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, verifyCaptcha, calcPerms, registerController.paramConverter, registerController.controller); +router.post('/changepassword', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, verifyCaptcha, changePasswordController.paramConverter, changePasswordController.controller); +router.post('/resign', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, resignController.paramConverter, resignController.controller); +router.post('/deleteaccount', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, deleteAccountController.controller); //removes captcha cookie, for refreshing for noscript users router.post('/newcaptcha', newCaptcha); diff --git a/controllers/forms/actions.js b/controllers/forms/actions.js index ebedb3a4..8b3b8bf9 100644 --- a/controllers/forms/actions.js +++ b/controllers/forms/actions.js @@ -4,142 +4,163 @@ const { Posts } = require(__dirname+'/../../db/') , config = require(__dirname+'/../../config.js') , actionHandler = require(__dirname+'/../../models/forms/actionhandler.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js'); - -module.exports = async (req, res, next) => { - - const { globalLimits } = config.get; - const errors = []; - - //make sure they checked 1-10 posts - if (!req.body.checkedposts || req.body.checkedposts.length === 0) { - errors.push('Must select at least one post'); - } else if (res.locals.permLevel >= 4 && globalLimits.multiInputs.posts.anon - && req.body.checkedposts.length > globalLimits.multiInputs.posts.anon) { - errors.push(`Must not select >${globalLimits.multiInputs.posts.anon} posts per request`); - } else if (globalLimits.multiInputs.posts.staff - && req.body.checkedposts.length > globalLimits.multiInputs.posts.staff) { - errors.push(`Must not select >${globalLimits.multiInputs.posts.staff} posts per request`); - } - - //checked reports - if (req.body.checkedreports) { - if (!req.body.report_ban) { - errors.push('Must select a report action if checked reports'); - } - if (!req.body.checkedposts) { - errors.push('Must check parent post if checking reports for report action'); - } else if (req.body.checkedreports.length > req.body.checkedposts.length*5) { - //5 reports max per post - errors.push('Invalid number of reports checked'); + , actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); + +module.exports = { + + options: { + timeFields: [], + trimFields: [], + allowedArrays: [], + processThreadIdParam: [], + processDateParam: [], + processMessageLength: [], + numberFields: [], + numberArrays: [], + objectIdFields: [], + objectIdArrays: [] + }, + + paramConverter: paramConverter(module.exports.options), + + controller: async (req, res, next) => { + + const { globalLimits } = config.get; + const errors = []; + + //make sure they checked 1-10 posts + if (!req.body.checkedposts || req.body.checkedposts.length === 0) { + errors.push('Must select at least one post'); + } else if (res.locals.permLevel >= 4 && globalLimits.multiInputs.posts.anon + && req.body.checkedposts.length > globalLimits.multiInputs.posts.anon) { + errors.push(`Must not select >${globalLimits.multiInputs.posts.anon} posts per request`); + } else if (globalLimits.multiInputs.posts.staff + && req.body.checkedposts.length > globalLimits.multiInputs.posts.staff) { + errors.push(`Must not select >${globalLimits.multiInputs.posts.staff} posts per request`); } - } else if (!req.body.checkedreports && req.body.report_ban) { - errors.push('Must select post and reports to ban reporter'); - } - res.locals.actions = actionChecker(req); + //checked reports + if (req.body.checkedreports) { + if (!req.body.report_ban) { + errors.push('Must select a report action if checked reports'); + } + if (!req.body.checkedposts) { + errors.push('Must check parent post if checking reports for report action'); + } else if (req.body.checkedreports.length > req.body.checkedposts.length*5) { + //5 reports max per post + errors.push('Invalid number of reports checked'); + } + } else if (!req.body.checkedreports && req.body.report_ban) { + errors.push('Must select post and reports to ban reporter'); + } - //make sure they selected at least 1 action - if (res.locals.actions.validActions.length === 0) { - errors.push('No actions selected'); - } + res.locals.actions = actionChecker(req); - //check if they have permission to perform the actions - if (res.locals.permLevel > res.locals.actions.authRequired) { - errors.push('No permission'); - } - if (res.locals.permLevel >= 4) { - if (req.body.delete && !res.locals.board.settings.userPostDelete) { - errors.push('Post deletion is disabled on this board'); + //make sure they selected at least 1 action + if (res.locals.actions.validActions.length === 0) { + errors.push('No actions selected'); } - if (req.body.spoiler && !res.locals.board.settings.userPostSpoiler) { - errors.push('File spoilers are disabled on this board'); + + //check if they have permission to perform the actions + if (res.locals.permLevel > res.locals.actions.authRequired) { + errors.push('No permission'); } - if (req.body.unlink_file && !res.locals.board.settings.userPostUnlink) { - errors.push('File unlinking is disabled on this board'); + if (res.locals.permLevel >= 4) { + if (req.body.delete && !res.locals.board.settings.userPostDelete) { + errors.push('Post deletion is disabled on this board'); + } + if (req.body.spoiler && !res.locals.board.settings.userPostSpoiler) { + errors.push('File spoilers are disabled on this board'); + } + if (req.body.unlink_file && !res.locals.board.settings.userPostUnlink) { + errors.push('File unlinking is disabled on this board'); + } } - } - //check that actions are valid - if (req.body.edit && req.body.checkedposts && req.body.checkedposts.length > 1) { - errors.push('Must select only 1 post for edit action'); - } - if (req.body.postpassword && req.body.postpassword.length > globalLimits.fieldLength.postpassword) { - errors.push(`Password must be ${globalLimits.fieldLength.postpassword} characters or less`); - } - if (req.body.report_reason && req.body.report_reason.length > globalLimits.fieldLength.report_reason) { - errors.push(`Report must be ${globalLimits.fieldLength.report_reason} characters or less`); - } - if (req.body.ban_reason && req.body.ban_reason.length > globalLimits.fieldLength.ban_reason) { - errors.push(`Ban reason must be ${globalLimits.fieldLength.ban_reason} characters or less`); - } - if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { - errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); - } - if ((req.body.report || req.body.global_report) && (!req.body.report_reason || req.body.report_reason.length === 0)) { - errors.push('Reports must have a reason'); - } - if (req.body.move) { - if (!req.body.move_to_thread) { - errors.push('Must input destinaton thread number to move posts'); - } else if (req.body.move_to_thread) { - const destinationThread = await Posts.threadExists(req.params.board, req.body.move_to_thread); - if (!destinationThread) { - errors.push('Destination thread for move does not exist'); - } else { - res.locals.destinationThread = destinationThread; + //check that actions are valid + if (req.body.edit && req.body.checkedposts && req.body.checkedposts.length > 1) { + errors.push('Must select only 1 post for edit action'); + } + if (req.body.postpassword && req.body.postpassword.length > globalLimits.fieldLength.postpassword) { + errors.push(`Password must be ${globalLimits.fieldLength.postpassword} characters or less`); + } + if (req.body.report_reason && req.body.report_reason.length > globalLimits.fieldLength.report_reason) { + errors.push(`Report must be ${globalLimits.fieldLength.report_reason} characters or less`); + } + if (req.body.ban_reason && req.body.ban_reason.length > globalLimits.fieldLength.ban_reason) { + errors.push(`Ban reason must be ${globalLimits.fieldLength.ban_reason} characters or less`); + } + if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { + errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); + } + if ((req.body.report || req.body.global_report) && (!req.body.report_reason || req.body.report_reason.length === 0)) { + errors.push('Reports must have a reason'); + } + if (req.body.move) { + if (!req.body.move_to_thread) { + errors.push('Must input destinaton thread number to move posts'); + } else if (req.body.move_to_thread) { + const destinationThread = await Posts.threadExists(req.params.board, req.body.move_to_thread); + if (!destinationThread) { + errors.push('Destination thread for move does not exist'); + } else { + res.locals.destinationThread = destinationThread; + } } } - } - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/` - }) - } - - try { - res.locals.posts = await Posts.getPosts(req.params.board, req.body.checkedposts, true); - } catch (err) { - return next(err); - } + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}/` + }) + } - if (!res.locals.posts || res.locals.posts.length === 0) { - return dynamicResponse(req, res, 404, 'message', { - 'title': 'Not found', - 'error': 'Selected posts not found', - 'redirect': `/${req.params.board}/` - }) - } + try { + res.locals.posts = await Posts.getPosts(req.params.board, req.body.checkedposts, true); + } catch (err) { + return next(err); + } - if (req.body.edit) { - //edit post, only allowing one - return res.render('editpost', { - 'post': res.locals.posts[0], - 'csrf': req.csrfToken(), - 'referer': (req.headers.referer || `/${res.locals.posts[0].board}/manage/thread/${res.locals.posts[0].thread || res.locals.posts[0].postId}.html`) + `#${res.locals.posts[0].postId}`, - }); - } else if (req.body.move) { - res.locals.posts = res.locals.posts.filter(p => { - //filter to remove any posts already in the thread (or the OP) of move destination - return p.postId !== req.body.move_to_thread && p.thread !== req.body.move_to_thread; - }); - if (res.locals.posts.length === 0) { - return dynamicResponse(req, res, 429, 'message', { - 'title': 'Conflict', - 'error': 'Destination thread cannot match source thread for move action', + if (!res.locals.posts || res.locals.posts.length === 0) { + return dynamicResponse(req, res, 404, 'message', { + 'title': 'Not found', + 'error': 'Selected posts not found', 'redirect': `/${req.params.board}/` + }) + } + + if (req.body.edit) { + //edit post, only allowing one + return res.render('editpost', { + 'post': res.locals.posts[0], + 'csrf': req.csrfToken(), + 'referer': (req.headers.referer || `/${res.locals.posts[0].board}/manage/thread/${res.locals.posts[0].thread || res.locals.posts[0].postId}.html`) + `#${res.locals.posts[0].postId}`, + }); + } else if (req.body.move) { + res.locals.posts = res.locals.posts.filter(p => { + //filter to remove any posts already in the thread (or the OP) of move destination + return p.postId !== req.body.move_to_thread && p.thread !== req.body.move_to_thread; }); + if (res.locals.posts.length === 0) { + return dynamicResponse(req, res, 429, 'message', { + 'title': 'Conflict', + 'error': 'Destination thread cannot match source thread for move action', + 'redirect': `/${req.params.board}/` + }); + } + } + + try { + await actionHandler(req, res, next); + } catch (err) { + return next(err); } - } - try { - await actionHandler(req, res, next); - } catch (err) { - return next(err); } } - diff --git a/controllers/forms/addban.js b/controllers/forms/addban.js deleted file mode 100644 index 0a293f67..00000000 --- a/controllers/forms/addban.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -const config = require(__dirname+'/../../config.js') - , addBan = require(__dirname+'/../../models/forms/addban.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , { isIP } = require('net'); - -module.exports = async (req, res, next) => { - - const { globalLimits, ipHashPermLevel } = config.get; - const errors = []; - - if (!req.body.ip || req.body.ip.length === 0) { - errors.push('Missing IP/hash input'); - } else if (req.body.ip.length > 50) { - errors.push('IP/hash input must be less than 50 characters'); - } else if (res.locals.permLevel > ipHashPermLevel && (isIP(req.body.ip) || req.body.ip.length !== 10)) { - errors.push('Invalid hash input'); - } - if (req.body.ban_reason && req.body.ban_reason.length > globalLimits.fieldLength.ban_reason) { - errors.push(`Ban reason must be ${globalLimits.fieldLength.ban_reason} characters or less`); - } - if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { - errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); - } - - let redirect = req.headers.referer; - if (!redirect) { - if (!req.params.board) { - redirect = '/globalmanage/bans.html'; - } else { - redirect = `/${req.params.board}/manage/bans.html`; - } - } - - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - redirect, - }); - } - - try { - await addBan(req, res, redirect); - } catch (err) { - return next(err); - } - -} - diff --git a/controllers/forms/addcustompage.js b/controllers/forms/addcustompage.js index 110962a1..ca007e10 100644 --- a/controllers/forms/addcustompage.js +++ b/controllers/forms/addcustompage.js @@ -3,54 +3,65 @@ const addCustomPage = require(__dirname+'/../../models/forms/addcustompage.js') , { CustomPages } = require(__dirname+'/../../db/') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , config = require(__dirname+'/../../config.js'); + , config = require(__dirname+'/../../config.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { globalLimits } = config.get; - const errors = []; + paramConverter: paramConverter({ + trimFields: ['message', 'title', 'page'], + processMessageLength: true, + }), - if (!req.body.message || res.locals.messageLength === 0) { - errors.push('Missing message'); - } - if (res.locals.messageLength > globalLimits.customPages.maxLength) { - errors.push(`Message must be ${globalLimits.customPages.maxLength} characters or less`); - } - if (!req.body.title || req.body.title.length === 0) { - errors.push('Missing title'); - } - if (req.body.title.length > 50) { - errors.push('Title must be 50 characters or less'); - } - if (!req.body.page - || req.body.page.length === 0) { - errors.push('Missing .html name'); - } - if (/[a-z0-9_-]+/.test(req.body.page) !== true) { - errors.push('.html name must contain a-z 0-9 _ - only'); - } - if (req.body.title.length > 50) { - errors.push('.html name must be 50 characters or less'); - } - if ((await CustomPages.boardCount(req.params.board)) > globalLimits.customPages.max) { - errors.push(`Can only create ${globalLimits.customPages.max} pages per board`); - } - if ((await CustomPages.findOne(req.params.board, req.body.page))) { - errors.push('.html name must be unique'); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.booard}/manage/custompages.html` - }); - } + const { globalLimits } = config.get; + const errors = []; - try { - await addCustomPage(req, res, next); - } catch (err) { - return next(err); - } + if (!req.body.message || res.locals.messageLength === 0) { + errors.push('Missing message'); + } + if (res.locals.messageLength > globalLimits.customPages.maxLength) { + errors.push(`Message must be ${globalLimits.customPages.maxLength} characters or less`); + } + if (!req.body.title || req.body.title.length === 0) { + errors.push('Missing title'); + } + if (req.body.title.length > 50) { + errors.push('Title must be 50 characters or less'); + } + if (!req.body.page + || req.body.page.length === 0) { + errors.push('Missing .html name'); + } + if (/[a-z0-9_-]+/.test(req.body.page) !== true) { + errors.push('.html name must contain a-z 0-9 _ - only'); + } + if (req.body.title.length > 50) { + errors.push('.html name must be 50 characters or less'); + } + if ((await CustomPages.boardCount(req.params.board)) > globalLimits.customPages.max) { + errors.push(`Can only create ${globalLimits.customPages.max} pages per board`); + } + if ((await CustomPages.findOne(req.params.board, req.body.page))) { + errors.push('.html name must be unique'); + } + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.booard}/manage/custompages.html` + }); + } + + try { + await addCustomPage(req, res, next); + } catch (err) { + return next(err); + } + + } } diff --git a/controllers/forms/addflags.js b/controllers/forms/addflags.js index b6c55159..e011d928 100644 --- a/controllers/forms/addflags.js +++ b/controllers/forms/addflags.js @@ -3,35 +3,43 @@ const addFlags = require(__dirname+'/../../models/forms/addflags.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js') - , config = require(__dirname+'/../../config.js'); + , config = require(__dirname+'/../../config.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { globalLimits } = config.get; - const errors = []; + //paramConverter: paramConverter({}), - if (res.locals.numFiles === 0) { - errors.push('Must provide a file'); - } else if (res.locals.numFiles > globalLimits.flagFiles.max) { - errors.push(`Exceeded max flag uploads in one request of ${globalLimits.flagFiles.max}`); - } else if (res.locals.board.flags.length+res.locals.numFiles > globalLimits.flagFiles.total) { - errors.push(`Total number of flags would exceed global limit of ${globalLimits.flagFiles.total}`); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - await deleteTempFiles(req).catch(e => console.error); - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/manage/assets.html` - }) - } + const { globalLimits } = config.get; + const errors = []; + + if (res.locals.numFiles === 0) { + errors.push('Must provide a file'); + } else if (res.locals.numFiles > globalLimits.flagFiles.max) { + errors.push(`Exceeded max flag uploads in one request of ${globalLimits.flagFiles.max}`); + } else if (res.locals.board.flags.length+res.locals.numFiles > globalLimits.flagFiles.total) { + errors.push(`Total number of flags would exceed global limit of ${globalLimits.flagFiles.total}`); + } + + if (errors.length > 0) { + await deleteTempFiles(req).catch(e => console.error); + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}/manage/assets.html` + }) + } + + try { + await addFlags(req, res, next); + } catch (err) { + await deleteTempFiles(req).catch(e => console.error); + return next(err); + } - try { - await addFlags(req, res, next); - } catch (err) { - await deleteTempFiles(req).catch(e => console.error); - return next(err); } } diff --git a/controllers/forms/addnews.js b/controllers/forms/addnews.js index 6800c87a..c6faf24e 100644 --- a/controllers/forms/addnews.js +++ b/controllers/forms/addnews.js @@ -1,37 +1,49 @@ 'use strict'; const addNews = require(__dirname+'/../../models/forms/addnews.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + trimFields: ['message', 'title'], + processMessageLength: true, + }), - if (!req.body.message || res.locals.messageLength === 0) { - errors.push('Missing message'); - } - if (res.locals.messageLength > 10000) { - errors.push('Message must be 10000 characters or less'); - } - if (!req.body.title || req.body.title.length === 0) { - errors.push('Missing title'); - } - if (req.body.title.length > 50) { - errors.push('Title must be 50 characters or less'); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/globalmanage/news.html' - }); - } + const errors = []; + + if (!req.body.message || res.locals.messageLength === 0) { + errors.push('Missing message'); + } + if (res.locals.messageLength > 10000) { + errors.push('Message must be 10000 characters or less'); + } + if (!req.body.title || req.body.title.length === 0) { + errors.push('Missing title'); + } + if (req.body.title.length > 50) { + errors.push('Title must be 50 characters or less'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/globalmanage/news.html' + }); + } + + try { + await addNews(req, res, next); + } catch (err) { + return next(err); + } - try { - await addNews(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/appeal.js b/controllers/forms/appeal.js index 4cc280c5..e67b62fd 100644 --- a/controllers/forms/appeal.js +++ b/controllers/forms/appeal.js @@ -3,53 +3,67 @@ const appealBans = require(__dirname+'/../../models/forms/appeal.js') , config = require(__dirname+'/../../config.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , { Bans } = require(__dirname+'/../../db'); + , { Bans } = require(__dirname+'/../../db') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { globalLimits } = config.get; - const errors = []; - if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) { - errors.push('Must select 1-10 bans'); - } - if (!req.body.message || res.locals.messageLength === 0) { - errors.push('Appeals must include a message'); - } - if (res.locals.messageLength > globalLimits.fieldLength.message) { - errors.push('Appeal message must be 2000 characters or less'); - } + paramConverter: paramConverter({ + trimFields: ['message'], + allowedArrays: ['checkedbans'], + processMessageLength: true, + objectIdArrays: ['checkedbans'] + }), - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/' - }); - } + controller: async (req, res, next) => { - let amount = 0; - try { - amount = await appealBans(req, res, next); - } catch (err) { - return next(err); - } + const { globalLimits } = config.get; + const errors = []; + if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) { + errors.push('Must select 1-10 bans'); + } + if (!req.body.message || res.locals.messageLength === 0) { + errors.push('Appeals must include a message'); + } + if (res.locals.messageLength > globalLimits.fieldLength.message) { + errors.push('Appeal message must be 2000 characters or less'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/' + }); + } + + let amount = 0; + try { + amount = await appealBans(req, res, next); + } catch (err) { + return next(err); + } - if (amount === 0) { - /* - this can occur if they selected invalid id, non-ip match, already appealed, or unappealable bans. prevented by databse filter, so we use - use the updatedCount return value to check if any appeals were made successfully. if not, we end up here. - */ - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'error': 'Invalid bans selected', + if (amount === 0) { + /* + this can occur if they selected invalid id, non-ip match, already appealed, or unappealable bans. prevented by databse filter, so we use + use the updatedCount return value to check if any appeals were made successfully. if not, we end up here. + */ + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'error': 'Invalid bans selected', + 'redirect': '/' + }); + } + + return dynamicResponse(req, res, 200, 'message', { + 'title': 'Success', + 'message': `Appealed ${amount} bans successfully`, 'redirect': '/' }); - } - return dynamicResponse(req, res, 200, 'message', { - 'title': 'Success', - 'message': `Appealed ${amount} bans successfully`, - 'redirect': '/' - }); + } } diff --git a/controllers/forms/boardsettings.js b/controllers/forms/boardsettings.js index 9636e1f1..3666132b 100644 --- a/controllers/forms/boardsettings.js +++ b/controllers/forms/boardsettings.js @@ -4,170 +4,192 @@ const changeBoardSettings = require(__dirname+'/../../models/forms/changeboardse , { themes, codeThemes } = require(__dirname+'/../../helpers/themes.js') , { Ratelimits } = require(__dirname+'/../../db/') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , config = require(__dirname+'/../../config.js'); + , config = require(__dirname+'/../../config.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { globalLimits, rateLimitCost } = config.get; - const errors = []; + options: { + timeFields: [], + trimFields: [], + allowedArrays: [], + processThreadIdParam: [], + processDateParam: [], + processMessageLength: [], + numberFields: [], + numberArrays: [], + objectIdFields: [], + objectIdArrays: [] + }, -//TODO: add helpers for different checks, passing name, min/max and return true with error if hit - if (req.body.description && - (req.body.description.length < 1 || - req.body.description.length > globalLimits.fieldLength.description)) { - errors.push(`Board description must be 1-${globalLimits.fieldLength.description} characters`); - } - if (req.body.announcements && (req.body.announcements.length < 1 || req.body.announcements.length > 2000)) { - errors.push('Board announcements must be 1-2000 characters'); - } - if (req.body.tags && req.body.tags.length > 2000) { - errors.push('Tags length must be 2000 characters or less'); - } - if (req.body.filters && req.body.filters.length > 2000) { - errors.push('Filters length must be 2000 characters or less'); - } - if (req.body.custom_css && globalLimits.customCss.enabled) { - if (res.locals.permLevel > 1 && globalLimits.customCss.strict && globalLimits.customCss.filters.some(filter => req.body.custom_css.includes(filter))) { - errors.push(`Custom CSS strict mode is enabled and does not allow the following: "${globalLimits.customCss.filters.join('", "')}"`); + paramConverter: paramConverter(module.exports.options), + + controller: async (req, res, next) => { + + const { globalLimits, rateLimitCost } = config.get; + const errors = []; + + //TODO: add helpers for different checks, passing name, min/max and return true with error if hit + if (req.body.description && + (req.body.description.length < 1 || + req.body.description.length > globalLimits.fieldLength.description)) { + errors.push(`Board description must be 1-${globalLimits.fieldLength.description} characters`); } - if (req.body.custom_css.length > globalLimits.customCss.max) { - errors.push(`Custom CSS must be ${globalLimits.customCss.max} characters or less`); + if (req.body.announcements && (req.body.announcements.length < 1 || req.body.announcements.length > 2000)) { + errors.push('Board announcements must be 1-2000 characters'); + } + if (req.body.tags && req.body.tags.length > 2000) { + errors.push('Tags length must be 2000 characters or less'); + } + if (req.body.filters && req.body.filters.length > 2000) { + errors.push('Filters length must be 2000 characters or less'); + } + if (req.body.custom_css && globalLimits.customCss.enabled) { + if (res.locals.permLevel > 1 && globalLimits.customCss.strict && globalLimits.customCss.filters.some(filter => req.body.custom_css.includes(filter))) { + errors.push(`Custom CSS strict mode is enabled and does not allow the following: "${globalLimits.customCss.filters.join('", "')}"`); + } + if (req.body.custom_css.length > globalLimits.customCss.max) { + errors.push(`Custom CSS must be ${globalLimits.customCss.max} characters or less`); + } + } + if (req.body.moderators && req.body.moderators.length > 500) { + errors.push('Moderators length must be 500 characters orless'); + } + if (req.body.name && + (req.body.name.length < 1 || + req.body.name.length > globalLimits.fieldLength.boardname)) { + errors.push(`Board name must be 1-${globalLimits.fieldLength.boardname} characters`); + } + if (req.body.default_name && (req.body.default_name.length < 1 || req.body.default_name.length > 50)) { + errors.push('Anon name must be 1-50 characters'); + } + if (typeof req.body.reply_limit === 'number' + && (req.body.reply_limit < globalLimits.replyLimit.min + || req.body.reply_limit > globalLimits.replyLimit.max)) { + errors.push(`Reply Limit must be ${globalLimits.replyLimit.min}-${globalLimits.replyLimit.max}`); + } + if (typeof req.body.bump_limit === 'number' + && (req.body.bump_limit < globalLimits.bumpLimit.min + || req.body.bump_limit > globalLimits.bumpLimit.max)) { + errors.push(`Bump Limit must be ${globalLimits.bumpLimit.min}-${globalLimits.bumpLimit.max}`); + } + if (typeof req.body.thread_limit === 'number' + && (req.body.thread_limit < globalLimits.threadLimit.min + || req.body.thread_limit > globalLimits.threadLimit.max)) { + errors.push(`Threads Limit must be ${globalLimits.threadLimit.min}-${globalLimits.threadLimit.max}`); + } + if (typeof req.body.max_files === 'number' && (req.body.max_files < 0 || req.body.max_files > globalLimits.postFiles.max)) { + errors.push(`Max files must be 0-${globalLimits.postFiles.max}`); } - } - if (req.body.moderators && req.body.moderators.length > 500) { - errors.push('Moderators length must be 500 characters orless'); - } - if (req.body.name && - (req.body.name.length < 1 || - req.body.name.length > globalLimits.fieldLength.boardname)) { - errors.push(`Board name must be 1-${globalLimits.fieldLength.boardname} characters`); - } - if (req.body.default_name && (req.body.default_name.length < 1 || req.body.default_name.length > 50)) { - errors.push('Anon name must be 1-50 characters'); - } - if (typeof req.body.reply_limit === 'number' - && (req.body.reply_limit < globalLimits.replyLimit.min - || req.body.reply_limit > globalLimits.replyLimit.max)) { - errors.push(`Reply Limit must be ${globalLimits.replyLimit.min}-${globalLimits.replyLimit.max}`); - } - if (typeof req.body.bump_limit === 'number' - && (req.body.bump_limit < globalLimits.bumpLimit.min - || req.body.bump_limit > globalLimits.bumpLimit.max)) { - errors.push(`Bump Limit must be ${globalLimits.bumpLimit.min}-${globalLimits.bumpLimit.max}`); - } - if (typeof req.body.thread_limit === 'number' - && (req.body.thread_limit < globalLimits.threadLimit.min - || req.body.thread_limit > globalLimits.threadLimit.max)) { - errors.push(`Threads Limit must be ${globalLimits.threadLimit.min}-${globalLimits.threadLimit.max}`); - } - if (typeof req.body.max_files === 'number' && (req.body.max_files < 0 || req.body.max_files > globalLimits.postFiles.max)) { - errors.push(`Max files must be 0-${globalLimits.postFiles.max}`); - } - - //make sure new min/max message dont conflict - if (typeof req.body.min_thread_message_length === 'number' - && typeof req.body.max_thread_message_length === 'number' - && req.body.min_thread_message_length - && req.body.max_thread_message_length - && req.body.min_thread_message_length > req.body.max_thread_message_length) { - errors.push('Min and max thread message lengths must not violate eachother'); - } - if (typeof req.body.min_reply_message_length === 'number' - && typeof req.body.max_reply_message_length === 'number' - && req.body.min_reply_message_length > req.body.max_reply_message_length) { - errors.push('Min and max reply message lengths must not violate eachother'); - } - //make sure existing min/max message dont conflict - const minThread = Math.min(globalLimits.fieldLength.message, res.locals.board.settings.maxThreadMessageLength) || globalLimits.fieldLength.message; - if (typeof req.body.min_thread_message_length === 'number' - && (req.body.min_thread_message_length < 0 - || req.body.min_thread_message_length > minThread)) { - errors.push(`Min thread message length must be 0-${globalLimits.fieldLength.message} and not more than "Max Thread Message Length" (currently ${res.locals.board.settings.maxThreadMessageLength})`); - } - const minReply = Math.min(globalLimits.fieldLength.message, res.locals.board.settings.maxReplyMessageLength) || globalLimits.fieldLength.message; - if (typeof req.body.min_reply_message_length === 'number' - && (req.body.min_reply_message_length < 0 - || req.body.min_reply_message_length > minReply)) { - errors.push(`Min reply message length must be 0-${globalLimits.fieldLength.message} and not more than "Max Reply Message Length" (currently ${res.locals.board.settings.maxReplyMessageLength})`); - } - if (typeof req.body.max_thread_message_length === 'number' - && (req.body.max_thread_message_length < 0 - || req.body.max_thread_message_length > globalLimits.fieldLength.message - || (req.body.max_thread_message_length - && req.body.max_thread_message_length < res.locals.board.settings.minThreadMessageLength))) { - errors.push(`Max thread message length must be 0-${globalLimits.fieldLength.message} and not less than "Min Thread Message Length" (currently ${res.locals.board.settings.minThreadMessageLength})`); - } - if (typeof req.body.max_reply_message_length === 'number' - && (req.body.max_reply_message_length < 0 - || req.body.max_reply_message_length > globalLimits.fieldLength.message - || (req.body.max_reply_message_length - && req.body.max_reply_message_length < res.locals.board.settings.minReplyMessageLength))) { - errors.push(`Max reply message length must be 0-${globalLimits.fieldLength.message} and not less than "Min Reply Message Length" (currently ${res.locals.board.settings.minReplyMessageLength})`); - } + //make sure new min/max message dont conflict + if (typeof req.body.min_thread_message_length === 'number' + && typeof req.body.max_thread_message_length === 'number' + && req.body.min_thread_message_length + && req.body.max_thread_message_length + && req.body.min_thread_message_length > req.body.max_thread_message_length) { + errors.push('Min and max thread message lengths must not violate eachother'); + } + if (typeof req.body.min_reply_message_length === 'number' + && typeof req.body.max_reply_message_length === 'number' + && req.body.min_reply_message_length > req.body.max_reply_message_length) { + errors.push('Min and max reply message lengths must not violate eachother'); + } - if (typeof req.body.lock_mode === 'number' && (req.body.lock_mode < 0 || req.body.lock_mode > 2)) { - errors.push('Invalid lock mode'); - } - if (typeof req.body.captcha_mode === 'number' && (req.body.captcha_mode < 0 || req.body.captcha_mode > 2)) { - errors.push('Invalid captcha mode'); - } - if (typeof req.body.filter_mode === 'number' && (req.body.filter_mode < 0 || req.body.filter_mode > 2)) { - errors.push('Invalid filter mode'); - } - if (typeof req.body.ban_duration === 'number' && req.body.ban_duration < 0) { - errors.push('Invalid filter auto ban duration'); - } - if (req.body.theme && !themes.includes(req.body.theme)) { - errors.push('Invalid theme'); - } - if (req.body.code_theme && !codeThemes.includes(req.body.code_theme)) { - errors.push('Invalid code theme'); - } + //make sure existing min/max message dont conflict + const minThread = Math.min(globalLimits.fieldLength.message, res.locals.board.settings.maxThreadMessageLength) || globalLimits.fieldLength.message; + if (typeof req.body.min_thread_message_length === 'number' + && (req.body.min_thread_message_length < 0 + || req.body.min_thread_message_length > minThread)) { + errors.push(`Min thread message length must be 0-${globalLimits.fieldLength.message} and not more than "Max Thread Message Length" (currently ${res.locals.board.settings.maxThreadMessageLength})`); + } + const minReply = Math.min(globalLimits.fieldLength.message, res.locals.board.settings.maxReplyMessageLength) || globalLimits.fieldLength.message; + if (typeof req.body.min_reply_message_length === 'number' + && (req.body.min_reply_message_length < 0 + || req.body.min_reply_message_length > minReply)) { + errors.push(`Min reply message length must be 0-${globalLimits.fieldLength.message} and not more than "Max Reply Message Length" (currently ${res.locals.board.settings.maxReplyMessageLength})`); + } + if (typeof req.body.max_thread_message_length === 'number' + && (req.body.max_thread_message_length < 0 + || req.body.max_thread_message_length > globalLimits.fieldLength.message + || (req.body.max_thread_message_length + && req.body.max_thread_message_length < res.locals.board.settings.minThreadMessageLength))) { + errors.push(`Max thread message length must be 0-${globalLimits.fieldLength.message} and not less than "Min Thread Message Length" (currently ${res.locals.board.settings.minThreadMessageLength})`); + } + if (typeof req.body.max_reply_message_length === 'number' + && (req.body.max_reply_message_length < 0 + || req.body.max_reply_message_length > globalLimits.fieldLength.message + || (req.body.max_reply_message_length + && req.body.max_reply_message_length < res.locals.board.settings.minReplyMessageLength))) { + errors.push(`Max reply message length must be 0-${globalLimits.fieldLength.message} and not less than "Min Reply Message Length" (currently ${res.locals.board.settings.minReplyMessageLength})`); + } - if (typeof req.body.tph_trigger === 'number' && (req.body.tph_trigger < 0 || req.body.tph_trigger > 10000)) { - errors.push('Invalid tph trigger threshold'); - } - if (typeof req.body.tph_trigger_action === 'number' && (req.body.tph_trigger_action < 0 || req.body.tph_trigger_action > 4)) { - errors.push('Invalid tph trigger action'); - } - if (typeof req.body.pph_trigger === 'number' && (req.body.pph_trigger < 0 || req.body.pph_trigger > 10000)) { - errors.push('Invalid pph trigger threshold'); - } - if (typeof req.body.pph_trigger_action === 'number' && (req.body.pph_trigger_action < 0 || req.body.pph_trigger_action > 4)) { - errors.push('Invalid pph trigger action'); - } - if (typeof req.body.lock_reset === 'number' && (req.body.lock_reset < 0 || req.body.lock_reset > 2)) { - errors.push('Invalid trigger reset lock'); - } - if (typeof req.body.captcha_reset === 'number' && (req.body.captcha_reset < 0 || req.body.captcha_reset > 2)) { - errors.push('Invalid trigger reset captcha'); - } + if (typeof req.body.lock_mode === 'number' && (req.body.lock_mode < 0 || req.body.lock_mode > 2)) { + errors.push('Invalid lock mode'); + } + if (typeof req.body.captcha_mode === 'number' && (req.body.captcha_mode < 0 || req.body.captcha_mode > 2)) { + errors.push('Invalid captcha mode'); + } + if (typeof req.body.filter_mode === 'number' && (req.body.filter_mode < 0 || req.body.filter_mode > 2)) { + errors.push('Invalid filter mode'); + } + if (typeof req.body.ban_duration === 'number' && req.body.ban_duration < 0) { + errors.push('Invalid filter auto ban duration'); + } + if (req.body.theme && !themes.includes(req.body.theme)) { + errors.push('Invalid theme'); + } + if (req.body.code_theme && !codeThemes.includes(req.body.code_theme)) { + errors.push('Invalid code theme'); + } - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/manage/settings.html` - }); - } + if (typeof req.body.tph_trigger === 'number' && (req.body.tph_trigger < 0 || req.body.tph_trigger > 10000)) { + errors.push('Invalid tph trigger threshold'); + } + if (typeof req.body.tph_trigger_action === 'number' && (req.body.tph_trigger_action < 0 || req.body.tph_trigger_action > 4)) { + errors.push('Invalid tph trigger action'); + } + if (typeof req.body.pph_trigger === 'number' && (req.body.pph_trigger < 0 || req.body.pph_trigger > 10000)) { + errors.push('Invalid pph trigger threshold'); + } + if (typeof req.body.pph_trigger_action === 'number' && (req.body.pph_trigger_action < 0 || req.body.pph_trigger_action > 4)) { + errors.push('Invalid pph trigger action'); + } + if (typeof req.body.lock_reset === 'number' && (req.body.lock_reset < 0 || req.body.lock_reset > 2)) { + errors.push('Invalid trigger reset lock'); + } + if (typeof req.body.captcha_reset === 'number' && (req.body.captcha_reset < 0 || req.body.captcha_reset > 2)) { + errors.push('Invalid trigger reset captcha'); + } - if (res.locals.permLevel > 1) { //if not global staff or above - const ratelimitBoard = await Ratelimits.incrmentQuota(req.params.board, 'settings', rateLimitCost.boardSettings); //2 changes a minute -// const ratelimitIp = await Ratelimits.incrmentQuota(res.locals.ip.single, 'settings', rateLimitCost.boardSettings); - if (ratelimitBoard > 100 /* || ratelimitIp > 100 */) { - return dynamicResponse(req, res, 429, 'message', { - 'title': 'Ratelimited', - 'error': 'You are changing settings too quickly, please wait a minute and try again', + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, 'redirect': `/${req.params.board}/manage/settings.html` }); } - } - try { - await changeBoardSettings(req, res, next); - } catch (err) { - return next(err); + if (res.locals.permLevel > 1) { //if not global staff or above + const ratelimitBoard = await Ratelimits.incrmentQuota(req.params.board, 'settings', rateLimitCost.boardSettings); //2 changes a minute + // const ratelimitIp = await Ratelimits.incrmentQuota(res.locals.ip.single, 'settings', rateLimitCost.boardSettings); + if (ratelimitBoard > 100 /* || ratelimitIp > 100 */) { + return dynamicResponse(req, res, 429, 'message', { + 'title': 'Ratelimited', + 'error': 'You are changing settings too quickly, please wait a minute and try again', + 'redirect': `/${req.params.board}/manage/settings.html` + }); + } + } + + try { + await changeBoardSettings(req, res, next); + } catch (err) { + return next(err); + } + } } diff --git a/controllers/forms/changepassword.js b/controllers/forms/changepassword.js index 39443e64..c7be6e40 100644 --- a/controllers/forms/changepassword.js +++ b/controllers/forms/changepassword.js @@ -1,55 +1,66 @@ 'use strict'; const changePassword = require(__dirname+'/../../models/forms/changepassword.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + trimFields: ['username', 'password', 'newpassword', 'newpasswordconfirm'], + }), - //check exist - if (!req.body.username || req.body.username.length <= 0) { - errors.push('Missing username'); - } - if (!req.body.password || req.body.password.length <= 0) { - errors.push('Missing password'); - } - if (!req.body.newpassword || req.body.newpassword.length <= 0) { - errors.push('Missing new password'); - } - if (!req.body.newpasswordconfirm || req.body.newpasswordconfirm.length <= 0) { - errors.push('Missing new password confirmation'); - } + controller: async (req, res, next) => { - //check too long - if (req.body.username && req.body.username.length > 50) { - errors.push('Username must be 50 characters or less'); - } - if (req.body.password && req.body.password.length > 100) { - errors.push('Password must be 100 characters or less'); - } - if (req.body.newpassword && req.body.newpassword.length > 100) { - errors.push('Password must be 100 characters or less'); - } - if (req.body.newpasswordconfirm && req.body.newpasswordconfirm.length > 100) { - errors.push('Password confirmation must be 100 characters or less'); - } - if (req.body.newpassword != req.body.newpasswordconfirm) { - errors.push('New password and password confirmation must match'); - } + const errors = []; - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/changepassword.html' - }) - } + //check exist + if (!req.body.username || req.body.username.length <= 0) { + errors.push('Missing username'); + } + if (!req.body.password || req.body.password.length <= 0) { + errors.push('Missing password'); + } + if (!req.body.newpassword || req.body.newpassword.length <= 0) { + errors.push('Missing new password'); + } + if (!req.body.newpasswordconfirm || req.body.newpasswordconfirm.length <= 0) { + errors.push('Missing new password confirmation'); + } + + //check too long + if (req.body.username && req.body.username.length > 50) { + errors.push('Username must be 50 characters or less'); + } + if (req.body.password && req.body.password.length > 100) { + errors.push('Password must be 100 characters or less'); + } + if (req.body.newpassword && req.body.newpassword.length > 100) { + errors.push('Password must be 100 characters or less'); + } + if (req.body.newpasswordconfirm && req.body.newpasswordconfirm.length > 100) { + errors.push('Password confirmation must be 100 characters or less'); + } + if (req.body.newpassword != req.body.newpasswordconfirm) { + errors.push('New password and password confirmation must match'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/changepassword.html' + }) + } + + try { + await changePassword(req, res, next); + } catch (err) { + return next(err); + } - try { - await changePassword(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/create.js b/controllers/forms/create.js index 228a3c44..89e92dd9 100644 --- a/controllers/forms/create.js +++ b/controllers/forms/create.js @@ -4,56 +4,67 @@ const createBoard = require(__dirname+'/../../models/forms/create.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , config = require(__dirname+'/../../config.js') , alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { enableUserBoardCreation, globalLimits } = config.get; + paramConverter: paramConverter({ + trimFields: ['name', 'uri', 'description'], + }), - if (enableUserBoardCreation === false && res.locals.permLevel > 1) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'error': 'User board creation is currently disabled', - 'redirect': '/create.html' - }); - } + controller: async (req, res, next) => { - const errors = []; + const { enableUserBoardCreation, globalLimits } = config.get; - //check exist - if (!req.body.uri || req.body.uri.length <= 0) { - errors.push('Missing URI'); - } - if (!req.body.name || req.body.name.length <= 0) { - errors.push('Missing name'); - } - //other validation - if (req.body.uri) { - if (req.body.uri.length > globalLimits.fieldLength.uri) { - errors.push(`URI must be ${globalLimits.fieldLength.uri} characters or less`); + if (enableUserBoardCreation === false && res.locals.permLevel > 1) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'error': 'User board creation is currently disabled', + 'redirect': '/create.html' + }); } - if (alphaNumericRegex.test(req.body.uri) !== true) { - errors.push('URI must contain a-z 0-9 only'); + + const errors = []; + + //check exist + if (!req.body.uri || req.body.uri.length <= 0) { + errors.push('Missing URI'); + } + if (!req.body.name || req.body.name.length <= 0) { + errors.push('Missing name'); + } + //other validation + if (req.body.uri) { + if (req.body.uri.length > globalLimits.fieldLength.uri) { + errors.push(`URI must be ${globalLimits.fieldLength.uri} characters or less`); + } + if (alphaNumericRegex.test(req.body.uri) !== true) { + errors.push('URI must contain a-z 0-9 only'); + } + } + if (req.body.name && req.body.name.length > globalLimits.fieldLength.boardname) { + errors.push(`Name must be ${globalLimits.fieldLength.boardname} characters or less`); + } + if (req.body.description && req.body.description.length > globalLimits.fieldLength.description) { + errors.push(`Description must be ${globalLimits.fieldLength.description} characters or less`); } - } - if (req.body.name && req.body.name.length > globalLimits.fieldLength.boardname) { - errors.push(`Name must be ${globalLimits.fieldLength.boardname} characters or less`); - } - if (req.body.description && req.body.description.length > globalLimits.fieldLength.description) { - errors.push(`Description must be ${globalLimits.fieldLength.description} characters or less`); - } - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/create.html' - }); - } + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/create.html' + }); + } + + try { + await createBoard(req, res, next); + } catch (err) { + return next(err); + } - try { - await createBoard(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/deleteaccount.js b/controllers/forms/deleteaccount.js index f87c3b58..7562ff5e 100644 --- a/controllers/forms/deleteaccount.js +++ b/controllers/forms/deleteaccount.js @@ -2,36 +2,45 @@ const deleteAccount = require(__dirname+'/../../models/forms/deleteaccount.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - if (!req.body.confirm) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'error': 'Missing confirmation', - 'redirect': '/account.html', - }); - } + //paramConverter: paramConverter({}), + + controller: async (req, res, next) => { + + if (!req.body.confirm) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'error': 'Missing confirmation', + 'redirect': '/account.html', + }); + } - const { modBoards, ownedBoards } = res.locals.user; - if (ownedBoards.length > 0 || modBoards.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'message': 'You cannot delete your account while you hold staff position on any board', - 'redirect': '/account.html', + const { modBoards, ownedBoards } = res.locals.user; + if (ownedBoards.length > 0 || modBoards.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'message': 'You cannot delete your account while you hold staff position on any board', + 'redirect': '/account.html', + }); + } + + try { + await deleteAccount(res.locals.user.username); + } catch (err) { + return next(err); + } + + return dynamicResponse(req, res, 200, 'message', { + 'title': 'Success', + 'message': 'Account deleted', + 'redirect': '/', }); - } - try { - await deleteAccount(res.locals.user.username); - } catch (err) { - return next(err); } - return dynamicResponse(req, res, 200, 'message', { - 'title': 'Success', - 'message': 'Account deleted', - 'redirect': '/', - }); - } diff --git a/controllers/forms/deletebanners.js b/controllers/forms/deletebanners.js index cdad66f6..530c0600 100644 --- a/controllers/forms/deletebanners.js +++ b/controllers/forms/deletebanners.js @@ -1,39 +1,61 @@ 'use strict'; const deleteBanners = require(__dirname+'/../../models/forms/deletebanners.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); - -module.exports = async (req, res, next) => { - - const errors = []; - - if (!req.body.checkedbanners || req.body.checkedbanners.length === 0) { - errors.push('Must select at least one banner to delete'); - } - - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/manage/assets.html` - }) - } + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); + +module.exports = { + + options: { + timeFields: [], + trimFields: [], + allowedArrays: [], + processThreadIdParam: [], + processDateParam: [], + processMessageLength: [], + numberFields: [], + numberArrays: [], + objectIdFields: [], + objectIdArrays: [] + }, + + paramConverter: paramConverter(module.exports.options), + + controller: async (req, res, next) => { + + const errors = []; + + if (!req.body.checkedbanners || req.body.checkedbanners.length === 0) { + errors.push('Must select at least one banner to delete'); + } - for (let i = 0; i < req.body.checkedbanners.length; i++) { - if (!res.locals.board.banners.includes(req.body.checkedbanners[i])) { + if (errors.length > 0) { return dynamicResponse(req, res, 400, 'message', { 'title': 'Bad request', - 'message': 'Invalid banners selected', + 'errors': errors, 'redirect': `/${req.params.board}/manage/assets.html` }) } - } - try { - await deleteBanners(req, res, next); - } catch (err) { - console.error(err); - return next(err); + for (let i = 0; i < req.body.checkedbanners.length; i++) { + if (!res.locals.board.banners.includes(req.body.checkedbanners[i])) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'message': 'Invalid banners selected', + 'redirect': `/${req.params.board}/manage/assets.html` + }) + } + } + + try { + await deleteBanners(req, res, next); + } catch (err) { + console.error(err); + return next(err); + } + } } diff --git a/controllers/forms/deleteboard.js b/controllers/forms/deleteboard.js index 28598a78..54087489 100644 --- a/controllers/forms/deleteboard.js +++ b/controllers/forms/deleteboard.js @@ -4,56 +4,67 @@ const { Boards } = require(__dirname+'/../../db/') , deleteBoard = require(__dirname+'/../../models/forms/deleteboard.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + trimFields: ['uri'], + }), - if (!req.body.confirm) { - errors.push('Missing confirmation'); - } - if (!req.body.uri) { - errors.push('Missing URI'); - } - let board; - if (alphaNumericRegex.test(req.body.uri) !== true) { - errors.push('URI must contain a-z 0-9 only'); - } else { - //no need to check these if the board name is completely invalid - if (req.params.board != null && req.params.board !== req.body.uri) { - //board manage page to not be able to delete other boards; - //req.params.board will be null on global delete, so this wont happen - errors.push('URI does not match current board'); + controller: async (req, res, next) => { + + const errors = []; + + if (!req.body.confirm) { + errors.push('Missing confirmation'); } + if (!req.body.uri) { + errors.push('Missing URI'); + } + let board; + if (alphaNumericRegex.test(req.body.uri) !== true) { + errors.push('URI must contain a-z 0-9 only'); + } else { + //no need to check these if the board name is completely invalid + if (req.params.board != null && req.params.board !== req.body.uri) { + //board manage page to not be able to delete other boards; + //req.params.board will be null on global delete, so this wont happen + errors.push('URI does not match current board'); + } + try { + board = await Boards.findOne(req.body.uri) + } catch (err) { + return next(err); + } + if (!board) { + //global must check exists because the route skips Boards.exists middleware + errors.push(`Board /${req.body.uri}/ does not exist`); + } + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': req.params.board ? `/${req.params.board}/manage/settings.html` : '/globalmanage/settings.html' + }); + } + try { - board = await Boards.findOne(req.body.uri) + await deleteBoard(board._id, board); } catch (err) { return next(err); } - if (!board) { - //global must check exists because the route skips Boards.exists middleware - errors.push(`Board /${req.body.uri}/ does not exist`); - } - } - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': req.params.board ? `/${req.params.board}/manage/settings.html` : '/globalmanage/settings.html' + return dynamicResponse(req, res, 200, 'message', { + 'title': 'Success', + 'message': 'Board deleted', + 'redirect': req.params.board ? '/' : '/globalmanage/settings.html' }); - } - try { - await deleteBoard(board._id, board); - } catch (err) { - return next(err); } - return dynamicResponse(req, res, 200, 'message', { - 'title': 'Success', - 'message': 'Board deleted', - 'redirect': req.params.board ? '/' : '/globalmanage/settings.html' - }); - } diff --git a/controllers/forms/deletecustompage.js b/controllers/forms/deletecustompage.js index 85a6c026..25cbcdf0 100644 --- a/controllers/forms/deletecustompage.js +++ b/controllers/forms/deletecustompage.js @@ -1,28 +1,39 @@ 'use strict'; const deleteCustomPage = require(__dirname+'/../../models/forms/deletecustompage.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + allowedArrays: ['checkedcustompages'], + }), - if (!req.body.checkedcustompages || req.body.checkedcustompages.length === 0) { - errors.push('Must select at least one custom page to delete'); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/manage/custompages.html` - }) - } + const errors = []; + + if (!req.body.checkedcustompages || req.body.checkedcustompages.length === 0) { + errors.push('Must select at least one custom page to delete'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}/manage/custompages.html` + }) + } + + try { + await deleteCustomPage(req, res, next); + } catch (err) { + return next(err); + } - try { - await deleteCustomPage(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/deleteflags.js b/controllers/forms/deleteflags.js index 21a08bf0..da9ef2e9 100644 --- a/controllers/forms/deleteflags.js +++ b/controllers/forms/deleteflags.js @@ -1,39 +1,50 @@ 'use strict'; const deleteFlags = require(__dirname+'/../../models/forms/deleteflags.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + allowedArrays: ['checkedflags'], + }), - if (!req.body.checkedflags || req.body.checkedflags.length === 0) { - errors.push('Must select at least one flag to delete'); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/manage/assets.html` - }) - } + const errors = []; - for (let i = 0; i < req.body.checkedflags.length; i++) { - if (!res.locals.board.flags[req.body.checkedflags[i]]) { + if (!req.body.checkedflags || req.body.checkedflags.length === 0) { + errors.push('Must select at least one flag to delete'); + } + + if (errors.length > 0) { return dynamicResponse(req, res, 400, 'message', { 'title': 'Bad request', - 'message': 'Invalid flags selected', + 'errors': errors, 'redirect': `/${req.params.board}/manage/assets.html` }) } - } - try { - await deleteFlags(req, res, next); - } catch (err) { - console.error(err); - return next(err); + for (let i = 0; i < req.body.checkedflags.length; i++) { + if (!res.locals.board.flags[req.body.checkedflags[i]]) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'message': 'Invalid flags selected', + 'redirect': `/${req.params.board}/manage/assets.html` + }) + } + } + + try { + await deleteFlags(req, res, next); + } catch (err) { + console.error(err); + return next(err); + } + } } diff --git a/controllers/forms/deletenews.js b/controllers/forms/deletenews.js index b7a1b9cc..02d2470f 100644 --- a/controllers/forms/deletenews.js +++ b/controllers/forms/deletenews.js @@ -1,28 +1,40 @@ 'use strict'; const deleteNews = require(__dirname+'/../../models/forms/deletenews.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + allowedArrays: ['checkednews'], + objectIdArrays: ['checkednews'] + }), - if (!req.body.checkednews || req.body.checkednews.length === 0) { - errors.push('Must select at least one newspost to delete'); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/globalmanage/news.html' - }) - } + const errors = []; + + if (!req.body.checkednews || req.body.checkednews.length === 0) { + errors.push('Must select at least one newspost to delete'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/globalmanage/news.html' + }) + } + + try { + await deleteNews(req, res, next); + } catch (err) { + return next(err); + } - try { - await deleteNews(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/editaccounts.js b/controllers/forms/editaccounts.js index 9edc0444..4fac7969 100644 --- a/controllers/forms/editaccounts.js +++ b/controllers/forms/editaccounts.js @@ -1,34 +1,46 @@ 'use strict'; const editAccounts = require(__dirname+'/../../models/forms/editaccounts.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + allowedArrays: ['checkedaccounts'], + numberFields: ['auth_level'], + }), - if (!req.body.checkedaccounts || req.body.checkedaccounts.length === 0) { - errors.push('Must select at least one account'); - } - if (typeof req.body.auth_level !== 'number' && !req.body.delete_account) { - errors.push('Missing auth level or delete action'); - } - if (typeof req.body.auth_level === 'number' && req.body.auth_level < 0 || req.body.auth_level > 4) { - errors.push('Auth level must be 0-4'); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/globalmanage/accounts.html' - }) - } + const errors = []; + + if (!req.body.checkedaccounts || req.body.checkedaccounts.length === 0) { + errors.push('Must select at least one account'); + } + if (typeof req.body.auth_level !== 'number' && !req.body.delete_account) { + errors.push('Missing auth level or delete action'); + } + if (typeof req.body.auth_level === 'number' && req.body.auth_level < 0 || req.body.auth_level > 4) { + errors.push('Auth level must be 0-4'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/globalmanage/accounts.html' + }) + } + + try { + await editAccounts(req, res, next); + } catch (err) { + return next(err); + } - try { - await editAccounts(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/editbans.js b/controllers/forms/editbans.js index d420c40c..d419ed30 100644 --- a/controllers/forms/editbans.js +++ b/controllers/forms/editbans.js @@ -2,47 +2,60 @@ const removeBans = require(__dirname+'/../../models/forms/removebans.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , denyAppeals = require(__dirname+'/../../models/forms/denybanappeals.js'); + , denyAppeals = require(__dirname+'/../../models/forms/denybanappeals.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + trimFields: ['option'], + allowedArrays: ['checkedbans'], + objectIdArrays: ['checkedbans'] + }), - if (!req.body.checkedbans || req.body.checkedbans.length === 0) { - errors.push('Must select at least one ban'); - } - if (!req.body.option || (req.body.option !== 'unban' && req.body.option !== 'deny_appeal')) { - errors.push('Invalid ban action') - } + controller: async (req, res, next) => { - const redirect = req.params.board ? `/${req.params.board}/manage/bans.html` : '/globalmanage/bans.html'; + const errors = []; - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, + if (!req.body.checkedbans || req.body.checkedbans.length === 0) { + errors.push('Must select at least one ban'); + } + if (!req.body.option || (req.body.option !== 'unban' && req.body.option !== 'deny_appeal')) { + errors.push('Invalid ban action') + } + + const redirect = req.params.board ? `/${req.params.board}/manage/bans.html` : '/globalmanage/bans.html'; + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + redirect + }); + } + + let amount = 0; + let message; + try { + if (req.body.option === 'unban') { + amount = await removeBans(req, res, next); + message = `Removed ${amount} bans`; + } else { + amount = await denyAppeals(req, res, next); + message = `Denied ${amount} appeals`; + } + } catch (err) { + return next(err); + } + + return dynamicResponse(req, res, 200, 'message', { + 'title': 'Success', + message, redirect }); - } - let amount = 0; - let message; - try { - if (req.body.option === 'unban') { - amount = await removeBans(req, res, next); - message = `Removed ${amount} bans`; - } else { - amount = await denyAppeals(req, res, next); - message = `Denied ${amount} appeals`; - } - } catch (err) { - return next(err); } - return dynamicResponse(req, res, 200, 'message', { - 'title': 'Success', - message, - redirect - }); - } diff --git a/controllers/forms/editnews.js b/controllers/forms/editnews.js index f913954d..5b17282d 100644 --- a/controllers/forms/editnews.js +++ b/controllers/forms/editnews.js @@ -1,40 +1,53 @@ 'use strict'; const editNews = require(__dirname+'/../../models/forms/editnews.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + trimFields: ['message', 'title'], + processMessageLength: true, + objectIdFields: ['news_id'], + }), - if (!req.body.news_id) { - errors.push('Missing news id'); - } - if (!req.body.message || res.locals.messageLength === 0) { - errors.push('Missing message'); - } - if (res.locals.messageLength > 10000) { - errors.push('Message must be 10000 characters or less'); - } - if (!req.body.title || req.body.title.length === 0) { - errors.push('Missing title'); - } - if (req.body.title.length > 50) { - errors.push('Title must be 50 characters or less'); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': req.headers.referer || '/globalmanage/news.html' - }); - } + const errors = []; + + if (!req.body.news_id) { + errors.push('Missing news id'); + } + if (!req.body.message || res.locals.messageLength === 0) { + errors.push('Missing message'); + } + if (res.locals.messageLength > 10000) { + errors.push('Message must be 10000 characters or less'); + } + if (!req.body.title || req.body.title.length === 0) { + errors.push('Missing title'); + } + if (req.body.title.length > 50) { + errors.push('Title must be 50 characters or less'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': req.headers.referer || '/globalmanage/news.html' + }); + } + + try { + await editNews(req, res, next); + } catch (err) { + return next(err); + } - try { - await editNews(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/editpost.js b/controllers/forms/editpost.js index 49c9b77c..eb865af7 100644 --- a/controllers/forms/editpost.js +++ b/controllers/forms/editpost.js @@ -3,66 +3,79 @@ const editPost = require(__dirname+'/../../models/forms/editpost.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , config = require(__dirname+'/../../config.js') - , { Ratelimits, Posts, Boards } = require(__dirname+'/../../db/'); + , { Ratelimits, Posts, Boards } = require(__dirname+'/../../db/') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { rateLimitCost, globalLimits } = config.get; - const errors = []; + paramConverter: paramConverter({ + trimFields: ['board', 'message', 'name', 'subject', 'email', 'log_message'], + processMessageLength: true, + numberFields: ['postId'], + }), - if ((!req.body.board || req.body.board.length === 0) - || (!req.body.postId || typeof req.body.postId !== 'number')) { - errors.push('Missing board and postId form data'); - } - // message, subject, email, name, limited length - if (req.body.message && res.locals.messageLength > globalLimits.fieldLength.message) { - errors.push(`Message must be ${globalLimits.fieldLength.message} characters or less`); - } - if (req.body.name && req.body.name.length > globalLimits.fieldLength.name) { - errors.push(`Name must be ${globalLimits.fieldLength.name} characters or less`); - } - if (req.body.subject && req.body.subject.length > globalLimits.fieldLength.subject) { - errors.push(`Subject must be ${globalLimits.fieldLength.subject} characters or less`); - } - if (req.body.email && req.body.email.length > globalLimits.fieldLength.email) { - errors.push(`Email must be ${globalLimits.fieldLength.email} characters or less`); - } - if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { - errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); - } + controller: async (req, res, next) => { - try { - res.locals.post = await Posts.getPost(req.body.board, req.body.postId); - } catch (err) { - return next(err); - } + const { rateLimitCost, globalLimits } = config.get; + const errors = []; - if (!res.locals.board || !res.locals.post) { - errors.push(`Post doesn't exist`); - } + if ((!req.body.board || req.body.board.length === 0) + || (!req.body.postId || typeof req.body.postId !== 'number')) { + errors.push('Missing board and postId form data'); + } + // message, subject, email, name, limited length + if (req.body.message && res.locals.messageLength > globalLimits.fieldLength.message) { + errors.push(`Message must be ${globalLimits.fieldLength.message} characters or less`); + } + if (req.body.name && req.body.name.length > globalLimits.fieldLength.name) { + errors.push(`Name must be ${globalLimits.fieldLength.name} characters or less`); + } + if (req.body.subject && req.body.subject.length > globalLimits.fieldLength.subject) { + errors.push(`Subject must be ${globalLimits.fieldLength.subject} characters or less`); + } + if (req.body.email && req.body.email.length > globalLimits.fieldLength.email) { + errors.push(`Email must be ${globalLimits.fieldLength.email} characters or less`); + } + if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { + errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); + } - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - }); - } + try { + res.locals.post = await Posts.getPost(req.body.board, req.body.postId); + } catch (err) { + return next(err); + } + + if (!res.locals.board || !res.locals.post) { + errors.push(`Post doesn't exist`); + } - if (res.locals.permLevel > 1) { //if not global staff or above - const ratelimitUser = await Ratelimits.incrmentQuota(req.session.user, 'edit', rateLimitCost.editPost); -// const ratelimitIp = await Ratelimits.incrmentQuota(res.locals.ip.single, 'edit', rateLimitCost.editPost); - if (ratelimitUser > 100 /* || ratelimitIp > 100 */) { - return dynamicResponse(req, res, 429, 'message', { - 'title': 'Ratelimited', - 'error': 'You are editing posts too quickly, please wait a minute and try again', + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, }); } - } - try { - await editPost(req, res, next); - } catch (err) { - return next(err); + if (res.locals.permLevel > 1) { //if not global staff or above + const ratelimitUser = await Ratelimits.incrmentQuota(req.session.user, 'edit', rateLimitCost.editPost); + // const ratelimitIp = await Ratelimits.incrmentQuota(res.locals.ip.single, 'edit', rateLimitCost.editPost); + if (ratelimitUser > 100 /* || ratelimitIp > 100 */) { + return dynamicResponse(req, res, 429, 'message', { + 'title': 'Ratelimited', + 'error': 'You are editing posts too quickly, please wait a minute and try again', + }); + } + } + + try { + await editPost(req, res, next); + } catch (err) { + return next(err); + } + } } diff --git a/controllers/forms/globalactions.js b/controllers/forms/globalactions.js index 915d36a3..5f68bad4 100644 --- a/controllers/forms/globalactions.js +++ b/controllers/forms/globalactions.js @@ -4,94 +4,115 @@ const { Posts } = require(__dirname+'/../../db/') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , config = require(__dirname+'/../../config.js') , actionHandler = require(__dirname+'/../../models/forms/actionhandler.js') - , actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js'); + , actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { globalLimits } = config.get; - const errors = []; + options: { + timeFields: [], + trimFields: [], + allowedArrays: [], + processThreadIdParam: [], + processDateParam: [], + processMessageLength: [], + numberFields: [], + numberArrays: [], + objectIdFields: [], + objectIdArrays: [] + }, - if (!req.body.globalcheckedposts || req.body.globalcheckedposts.length === 0) { - errors.push(`Must select at least one post`); - } else if (globalLimits.multiInputs.posts.staff - && req.body.globalcheckedposts.length > globalLimits.multiInputs.posts.staff) { - errors.push(`Must not select >${globalLimits.multiInputs.posts.staff} posts per request`); - } + paramConverter: paramConverter(module.exports.options), + + controller: async (req, res, next) => { - //checked reports - if (req.body.checkedreports) { - if (!req.body.global_report_ban) { - errors.push('Must select a report action if checked reports'); + const { globalLimits } = config.get; + const errors = []; + + if (!req.body.globalcheckedposts || req.body.globalcheckedposts.length === 0) { + errors.push(`Must select at least one post`); + } else if (globalLimits.multiInputs.posts.staff + && req.body.globalcheckedposts.length > globalLimits.multiInputs.posts.staff) { + errors.push(`Must not select >${globalLimits.multiInputs.posts.staff} posts per request`); } - if (!req.body.globalcheckedposts) { - errors.push('Must check parent post if checking reports for report action'); - } else if (req.body.checkedreports.length > req.body.globalcheckedposts.length*5) { - //5 reports max per post - errors.push('Invalid number of reports checked'); + + //checked reports + if (req.body.checkedreports) { + if (!req.body.global_report_ban) { + errors.push('Must select a report action if checked reports'); + } + if (!req.body.globalcheckedposts) { + errors.push('Must check parent post if checking reports for report action'); + } else if (req.body.checkedreports.length > req.body.globalcheckedposts.length*5) { + //5 reports max per post + errors.push('Invalid number of reports checked'); + } + } else if (!req.body.checkedreports && req.body.global_report_ban) { + errors.push('Must select posts+reports to report ban'); } - } else if (!req.body.checkedreports && req.body.global_report_ban) { - errors.push('Must select posts+reports to report ban'); - } - res.locals.actions = actionChecker(req); + res.locals.actions = actionChecker(req); - //make sure they have any global actions, and that they only selected global actions - if (res.locals.actions.numGlobal === 0 || res.locals.actions.validActions.length > res.locals.actions.numGlobal) { - errors.push('Invalid actions selected'); - } + //make sure they have any global actions, and that they only selected global actions + if (res.locals.actions.numGlobal === 0 || res.locals.actions.validActions.length > res.locals.actions.numGlobal) { + errors.push('Invalid actions selected'); + } - //check that actions are valid - if (req.body.edit && req.body.globalcheckedposts.length > 1) { - errors.push('Must select only 1 post for edit action'); - } - if (req.body.postpassword && req.body.postpassword.length > globalLimits.fieldLength.postpassword) { - errors.push(`Password must be ${globalLimits.fieldLength.postpassword} characters or less`); - } - if (req.body.ban_reason && req.body.ban_reason.length > globalLimits.fieldLength.ban_reason) { - errors.push(`Ban reason must be ${globalLimits.fieldLength.ban_reason} characters or less`); - } - if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { - errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); - } + //check that actions are valid + if (req.body.edit && req.body.globalcheckedposts.length > 1) { + errors.push('Must select only 1 post for edit action'); + } + if (req.body.postpassword && req.body.postpassword.length > globalLimits.fieldLength.postpassword) { + errors.push(`Password must be ${globalLimits.fieldLength.postpassword} characters or less`); + } + if (req.body.ban_reason && req.body.ban_reason.length > globalLimits.fieldLength.ban_reason) { + errors.push(`Ban reason must be ${globalLimits.fieldLength.ban_reason} characters or less`); + } + if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { + errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); + } - //return the errors - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/globalmanage/reports.html' - }) - } + //return the errors + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/globalmanage/reports.html' + }) + } - //get posts with global ids only - try { - res.locals.posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true); - } catch (err) { - return next(err); - } - if (!res.locals.posts || res.locals.posts.length === 0) { - return dynamicResponse(req, res, 404, 'message', { - 'title': 'Not found', - 'errors': 'Selected posts not found', - 'redirect': '/globalmanage/reports.html' - }) - } + //get posts with global ids only + try { + res.locals.posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true); + } catch (err) { + return next(err); + } + if (!res.locals.posts || res.locals.posts.length === 0) { + return dynamicResponse(req, res, 404, 'message', { + 'title': 'Not found', + 'errors': 'Selected posts not found', + 'redirect': '/globalmanage/reports.html' + }) + } - if (req.body.edit) { - //edit post, only allowing one - return res.render('editpost', { - 'post': res.locals.posts[0], - 'csrf': req.csrfToken(), - 'referer': (req.headers.referer || `/${res.locals.posts[0].board}/manage/thread/${res.locals.posts[0].thread || res.locals.posts[0].postId}.html`) + `#${res.locals.posts[0].postId}`, - }); - } + if (req.body.edit) { + //edit post, only allowing one + return res.render('editpost', { + 'post': res.locals.posts[0], + 'csrf': req.csrfToken(), + 'referer': (req.headers.referer || `/${res.locals.posts[0].board}/manage/thread/${res.locals.posts[0].thread || res.locals.posts[0].postId}.html`) + `#${res.locals.posts[0].postId}`, + }); + } + + try { + await actionHandler(req, res, next); + } catch (err) { + console.error(err); + return next(err); + } - try { - await actionHandler(req, res, next); - } catch (err) { - console.error(err); - return next(err); } } - diff --git a/controllers/forms/globalsettings.js b/controllers/forms/globalsettings.js index 0d847bf1..048a7d6b 100644 --- a/controllers/forms/globalsettings.js +++ b/controllers/forms/globalsettings.js @@ -4,169 +4,189 @@ const changeGlobalSettings = require(__dirname+'/../../models/forms/changeglobal , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , themeHelper = require(__dirname+'/../../helpers/themes.js') , config = require(__dirname+'/../../config.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { globalLimits } = config.get; + options: { + timeFields: [], + trimFields: [], + allowedArrays: [], + processThreadIdParam: [], + processDateParam: [], + processMessageLength: [], + numberFields: [], + numberArrays: [], + objectIdFields: [], + objectIdArrays: [] + }, - const schema = [ - { result: () => { - if (req.body.thumb_extension) { - return /\.[a-z0-9]+/i.test(req.body.thumb_extension); - } - return false; - }, expected: true, error: 'Thumb extension must be like .xxx' }, - { result: () => { - if (req.body.other_mime_types) { - return req.body.other_mime_types - .split('\n') - .some(m => { - return !m.match(/\w+\/\w+/i); - }); - } - return false; - }, expected: false, error: 'Extra mime types must be like type/subtype' }, - { result: lengthBody(req.body.global_announcement, 0, 10000), expected: false, error: 'Global announcement must not exceed 10000 characters' }, - { result: lengthBody(req.body.filters, 0, 5000), expected: false, error: 'Filter text cannot exceed 5000 characters' }, - { result: numberBody(req.body.filter_mode, 0, 2), expected: true, error: 'Filter mode must be a number from 0-2' }, - { result: numberBody(req.body.ban_duration), expected: true, error: 'Invalid filter auto ban duration' }, - { result: lengthBody(req.body.allowed_hosts, 0, 10000), expected: false, error: 'Allowed hosts must not exceed 10000 characters' }, - { result: lengthBody(req.body.country_code_header, 0, 100), expected: false, error: 'Country code header length must not exceed 100 characters' }, - { result: lengthBody(req.body.ip_header, 0, 100), expected: false, error: 'IP header length must not exceed 100 characters' }, - { result: lengthBody(req.body.meta_site_name, 0, 100), expected: false, error: 'Meta site name must not exceed 100 characters' }, - { result: lengthBody(req.body.meta_url, 0, 100), expected: false, error: 'Meta url must not exceed 100 characters' }, - { result: inArrayBody(req.body.captcha_options_type, ['grid', 'text', 'google', 'hcaptcha']), expected: true, error: 'Invalid captcha options type' }, - { result: numberBody(req.body.captcha_options_generate_limit, 1), expected: true, error: 'Captcha options generate limit must be a number > 0' }, - { result: numberBody(req.body.captcha_options_grid_size, 2, 6), expected: true, error: 'Captcha options grid size must be a number from 2-6' }, - { result: numberBody(req.body.captcha_options_image_size, 50, 500), expected: true, error: 'Captcha options image size must be a number from 50-500' }, - { result: numberBody(req.body.captcha_options_grid_icon_y_offset, 0, 50), expected: true, error: 'Captcha options icon y offset must be a number from 0-50' }, - { result: numberBody(req.body.captcha_options_num_distorts_min, 0, 10), expected: true, error: 'Captcha options min distorts must be a number from 0-10' }, - { result: numberBody(req.body.captcha_options_num_distorts_max, 0, 10), expected: true, error: 'Captcha options max distorts must be a number from 0-10' }, - { result: minmaxBody(req.body.captcha_options_num_distorts_min, req.body.captcha_options_num_distorts_max), expected: true, error: 'Captcha options distorts min must be less than max' }, - { result: numberBody(req.body.captcha_options_distortion, 0, 50), expected: true, error: 'Captcha options distortion must be a number from 0-50' }, - { result: numberBody(req.body.dnsbl_cache_time), expected: true, error: 'Invalid dnsbl cache time' }, - { result: numberBody(req.body.flood_timers_same_content_same_ip), expected: true, error: 'Invalid flood time same content same ip' }, - { result: numberBody(req.body.flood_timers_same_content_any_ip), expected: true, error: 'Invalid flood time same contenet any ip' }, - { result: numberBody(req.body.flood_timers_any_content_same_ip), expected: true, error: 'Invalid flood time any content same ip' }, - { result: numberBody(req.body.block_bypass_expire_after_uses), expected: true, error: 'Block bypass expire after uses must be a number > 0' }, - { result: numberBody(req.body.block_bypass_expire_after_time), expected: true, error: 'Invalid block bypass expire after time' }, - { result: numberBody(req.body.ip_hash_perm_level, -1), expected: true, error: 'Invalid ip hash perm level' }, - { result: numberBody(req.body.delete_board_perm_level, 0, 4), expected: true, error: 'Invalid delete board perm level' }, - { result: numberBody(req.body.perm_levels_markdown_green, 0, 4), expected: true, error: 'Invalid greentext markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_pink, 0, 4), expected: true, error: 'Invalid pinktext markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_title, 0, 4), expected: true, error: 'Invalid title markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_bold, 0, 4), expected: true, error: 'Invalid bold markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_underline, 0, 4), expected: true, error: 'Invalid underline markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_strike, 0, 4), expected: true, error: 'Invalid strike markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_italic, 0, 4), expected: true, error: 'Invalid italicmarkdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_mono, 0, 4), expected: true, error: 'Invalid mono markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_code, 0, 4), expected: true, error: 'Invalid code block markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_spoiler, 0, 4), expected: true, error: 'Invalid spoiler markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_detected, 0, 4), expected: true, error: 'Invalid detected markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_link, 0, 4), expected: true, error: 'Invalid link markdown perm level' }, - { result: numberBody(req.body.perm_levels_markdown_dice, 0, 4), expected: true, error: 'Invalid dice markdown perm level' }, - { result: numberBody(req.body.rate_limit_cost_captcha, 1, 100), expected: true, error: 'Rate limit cost captcha must be a number from 1-100' }, - { result: numberBody(req.body.rate_limit_cost_board_settings, 1, 100), expected: true, error: 'Rate limit cost board settings must be a number from 1-100' }, - { result: numberBody(req.body.rate_limit_cost_edit_post, 1, 100), expected: true, error: 'Rate limit cost edit post must be a number from 1-100' }, - { result: numberBody(req.body.overboard_limit), expected: true, error: 'Invalid overboard limit' }, - { result: numberBody(req.body.overboard_catalog_limit), expected: true, error: 'Invalid overboard catalog limit' }, - { result: numberBody(req.body.lock_wait), expected: true, error: 'Invalid lock wait' }, - { result: numberBody(req.body.prune_modlogs), expected: true, error: 'Prune modlogs must be a number of days' }, - { result: numberBody(req.body.prune_ips), expected: true, error: 'Prune ips must be a number of days' }, - { result: lengthBody(req.body.thumb_extension, 1), expected: false, error: 'Thumbnail extension must be at least 1 character' }, - { result: numberBody(req.body.thumb_size), expected: true, error: 'Invalid thumbnail size' }, - { result: numberBody(req.body.video_thumb_percentage, 0, 100), expected: true, error: 'Video thumbnail percentage must be a number from 1-100' }, - { result: numberBody(req.body.default_ban_duration), expected: true, error: 'Invalid default ban duration' }, - { result: numberBody(req.body.quote_limit), expected: true, error: 'Quote limit must be a number' }, - { result: numberBody(req.body.preview_replies), expected: true, error: 'Preview replies must be a number' }, - { result: numberBody(req.body.sticky_preview_replies), expected: true, error: 'Sticky preview replies must be a number' }, - { result: numberBody(req.body.early_404_fraction), expected: true, error: 'Early 404 fraction must be a number' }, - { result: numberBody(req.body.early_404_replies), expected: true, error: 'Early 404 fraction must be a number' }, - { result: numberBody(req.body.max_recent_news), expected: true, error: 'Max recent news must be a number' }, - { result: lengthBody(req.body.space_file_name_replacement, 1, 1), expected: false, error: 'Space file name replacement must be 1 character' }, - { result: lengthBody(req.body.highlight_options_language_subset, 0, 10000), expected: false, error: 'Highlight options language subset must not exceed 10000 characters' }, - { result: lengthBody(req.body.highlight_options_threshold), expected: false, error: 'Highlight options threshold must be a number' }, - { result: numberBody(req.body.global_limits_thread_limit_min), expected: true, error: 'Global thread limit minimum must be a number' }, - { result: numberBody(req.body.global_limits_thread_limit_max), expected: true, error: 'Global thread limit maximum must be a number' }, - { result: minmaxBody(req.body.global_limits_thread_limit_min, req.body.global_limits_thread_limit_max), expected: true, error: 'Global thread limit min must be less than max' }, - { result: numberBody(req.body.global_limits_reply_limit_min), expected: true, error: 'Global reply limit minimum must be a number' }, - { result: numberBody(req.body.global_limits_reply_limit_max), expected: true, error: 'Global reply limit maximum must be a number' }, - { result: minmaxBody(req.body.global_limits_reply_limit_min, req.body.global_limits_reply_limit_max), expected: true, error: 'Global reply limit min must be less than max' }, - { result: numberBody(req.body.global_limits_bump_limit_min), expected: true, error: 'Global bump limit minimum must be a number' }, - { result: numberBody(req.body.global_limits_bump_limit_max), expected: true, error: 'Global bump limit minimum must be a number' }, - { result: minmaxBody(req.body.global_limits_bump_limit_min, req.body.global_limits_bump_limit_max), expected: true, error: 'Global bump limit min must be less than max' }, - { result: numberBody(req.body.global_limits_post_files_max), expected: true, error: 'Post files max must be a number' }, - { result: numberBody(req.body.global_limits_post_files_size_max), expected: true, error: 'Post files size must be a number' }, - { result: numberBody(req.body.global_limits_banner_files_width, 1), expected: true, error: 'Banner files height must be a number > 0' }, - { result: numberBody(req.body.global_limits_banner_files_height, 1), expected: true, error: 'Banner files width must be a number > 0' }, - { result: numberBody(req.body.global_limits_banner_files_size_max), expected: true, error: 'Banner files size must be a number' }, - { result: numberBody(req.body.global_limits_banner_files_max), expected: true, error: 'Banner files max must be a number' }, - { result: numberBody(req.body.global_limits_banner_files_total), expected: true, error: 'Banner files total must be a number' }, - { result: numberBody(req.body.global_limits_flag_files_size_max), expected: true, error: 'Flag files size must be a number' }, - { result: numberBody(req.body.global_limits_flag_files_max), expected: true, error: 'Flag files max must be a number' }, - { result: numberBody(req.body.global_limits_flag_files_total), expected: true, error: 'Flag files total must be a number' }, - { result: numberBody(req.body.global_limits_field_length_name), expected: true, error: 'Global limit name field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_email), expected: true, error: 'Global limit email field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_subject), expected: true, error: 'Global limit subject field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_postpassword), expected: true, error: 'Global limit postpassword field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_message), expected: true, error: 'Global limit message field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_report_reason), expected: true, error: 'Global limit report reason field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_ban_reason), expected: true, error: 'Global limit ban reason field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_log_message), expected: true, error: 'Global limit log message field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_uri), expected: true, error: 'Global limit board uri field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_boardname), expected: true, error: 'Global limit board name field length must be a number' }, - { result: numberBody(req.body.global_limits_field_length_description), expected: true, error: 'Global limit board description field length must be a number' }, - { result: numberBody(req.body.global_limits_multi_input_posts_anon), expected: true, error: 'Multi input anon limit must be a number' }, - { result: numberBody(req.body.global_limits_multi_input_posts_staff), expected: true, error: 'Multi input staff limit must be a number' }, - { result: numberBody(req.body.global_limits_custom_css_max), expected: true, error: 'Custom css max must be a number' }, - { result: lengthBody(req.body.global_limits_custom_css_filters, 0, 10000), expected: false, error: 'Custom css filters must not exceed 10000 characters' }, - { result: numberBody(req.body.global_limits_custom_pages_max), expected: true, error: 'Custom pages max must be a number' }, - { result: numberBody(req.body.global_limits_custom_pages_max_length), expected: true, error: 'Custom pages max length must be a number' }, - { result: inArrayBody(req.body.board_defaults_theme, themeHelper.themes), expected: true, error: 'Invalid board default theme' }, - { result: inArrayBody(req.body.board_defaults_code_theme, themeHelper.codeThemes), expected: true, error: 'Invalid board default code theme' }, - { result: numberBody(req.body.board_defaults_lock_mode, 0, 2), expected: true, error: 'Board default lock mode must be a number from 0-2' }, - { result: numberBody(req.body.board_defaults_file_r9k_mode, 0, 2), expected: true, error: 'Board default file r9k mode must be a number from 0-2' }, - { result: numberBody(req.body.board_defaults_message_r9k_mode, 0, 2), expected: true, error: 'Board default message r9k mode must be a number from 0-2' }, - { result: numberBody(req.body.board_defaults_captcha_mode, 0, 2), expected: true, error: 'Board default captcha mode must be a number from 0-2' }, - { result: numberBody(req.body.board_defaults_tph_trigger), expected: true, error: 'Board default tph trigger must be a number' }, - { result: numberBody(req.body.board_defaults_pph_trigger), expected: true, error: 'Board default pph trigger must be a number' }, - { result: numberBody(req.body.board_defaults_pph_trigger_action, 0, 4), expected: true, error: 'Board default pph trigger action must be a number from 0-4' }, - { result: numberBody(req.body.board_defaults_tph_trigger_action, 0, 4), expected: true, error: 'Board default tph trigger action must be a number from 0-4' }, - { result: numberBody(req.body.board_defaults_captcha_reset, 0, 2), expected: true, error: 'Board defaults captcha reset must be a number from 0-2' }, - { result: numberBody(req.body.board_defaults_lock_reset, 0, 2), expected: true, error: 'Board defaults lock reset must be a number from 0-2' }, - { result: numberBodyVariable(req.body.board_defaults_reply_limit, req.body.global_limits_reply_limit_min, globalLimits.replyLimit.min, req.body.global_limits_reply_limit_max, globalLimits.replyLimit.max), expected: true, error: `Board defaults reply limit must be within global limits` }, - { result: numberBodyVariable(req.body.board_defaults_thread_limit, req.body.global_limits_thread_limit_min, globalLimits.threadLimit.min, req.body.global_limits_thread_limit_max, globalLimits.threadLimit.max), expected: true, error: `Board defaults thread limit must be within global limits` }, - { result: numberBodyVariable(req.body.board_defaults_bump_limit, req.body.global_limits_bump_limit_min, globalLimits.bumpLimit.min, req.body.global_limits_bump_limit_max, globalLimits.bumpLimit.max), expected: true, error: `Board defaults bump limit must be within global limits` }, - { result: numberBodyVariable(req.body.board_defaults_max_files, 0, 0, req.body.global_limits_post_files_max, globalLimits.postFiles.max), expected: true, error: `Board defaults max files must be within global limits` }, - { result: numberBodyVariable(req.body.board_defaults_max_thread_message_length, 0, 0, req.body.global_limits_field_length_message, globalLimits.fieldLength.message), expected: true, error: `Board defaults max thread message length must be within global limits` }, - { result: numberBodyVariable(req.body.board_defaults_max_reply_message_length, 0, 0, req.body.global_limits_field_length_message, globalLimits.fieldLength.message), expected: true, error: `Board defaults max reply message length must be within global limits` }, - { result: numberBody(req.body.board_defaults_min_thread_message_length), expected: true, error: 'Board defaults min thread message length must be a number' }, - { result: numberBody(req.body.board_defaults_min_reply_message_length), expected: true, error: 'Board defaults min reply message length must be a number' }, - { result: minmaxBody(req.body.board_defaults_min_thread_message_length, req.body.board_defaults_max_thread_message_length), expected: true, error: 'Board defaults thread message length min must be less than max' }, - { result: minmaxBody(req.body.board_defaults_min_reply_message_length, req.body.board_defaults_max_reply_message_length), expected: true, error: 'Board defaults reply message length min must be less than max' }, - { result: numberBody(req.body.board_defaults_filter_mode, 0, 2), expected: true, error: 'Board defaults filter most must be a number from 0-2' }, - { result: numberBody(req.body.board_defaults_filter_ban_duration), expected: true, error: 'Board defaults filter ban duration must be a number' }, - { result: lengthBody(req.body.webring_following, 0, 10000), expected: false, error: 'Webring following list must not exceed 10000 characters' }, - { result: lengthBody(req.body.webring_blacklist, 0, 10000), expected: false, error: 'Webring blacklist must not exceed 10000 characters' }, - { result: lengthBody(req.body.webring_logos, 0, 10000), expected: false, error: 'Webring logos list must not exceed 10000 characters' }, - ]; + paramConverter: paramConverter(module.exports.options), - const errors = await checkSchema(schema); + controller: async (req, res, next) => { - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/globalmanage/settings.html' - }); - } + const { globalLimits } = config.get; + + const schema = [ + { result: () => { + if (req.body.thumb_extension) { + return /\.[a-z0-9]+/i.test(req.body.thumb_extension); + } + return false; + }, expected: true, error: 'Thumb extension must be like .xxx' }, + { result: () => { + if (req.body.other_mime_types) { + return req.body.other_mime_types + .split('\n') + .some(m => { + return !m.match(/\w+\/\w+/i); + }); + } + return false; + }, expected: false, error: 'Extra mime types must be like type/subtype' }, + { result: lengthBody(req.body.global_announcement, 0, 10000), expected: false, error: 'Global announcement must not exceed 10000 characters' }, + { result: lengthBody(req.body.filters, 0, 5000), expected: false, error: 'Filter text cannot exceed 5000 characters' }, + { result: numberBody(req.body.filter_mode, 0, 2), expected: true, error: 'Filter mode must be a number from 0-2' }, + { result: numberBody(req.body.ban_duration), expected: true, error: 'Invalid filter auto ban duration' }, + { result: lengthBody(req.body.allowed_hosts, 0, 10000), expected: false, error: 'Allowed hosts must not exceed 10000 characters' }, + { result: lengthBody(req.body.country_code_header, 0, 100), expected: false, error: 'Country code header length must not exceed 100 characters' }, + { result: lengthBody(req.body.ip_header, 0, 100), expected: false, error: 'IP header length must not exceed 100 characters' }, + { result: lengthBody(req.body.meta_site_name, 0, 100), expected: false, error: 'Meta site name must not exceed 100 characters' }, + { result: lengthBody(req.body.meta_url, 0, 100), expected: false, error: 'Meta url must not exceed 100 characters' }, + { result: inArrayBody(req.body.captcha_options_type, ['grid', 'text', 'google', 'hcaptcha']), expected: true, error: 'Invalid captcha options type' }, + { result: numberBody(req.body.captcha_options_generate_limit, 1), expected: true, error: 'Captcha options generate limit must be a number > 0' }, + { result: numberBody(req.body.captcha_options_grid_size, 2, 6), expected: true, error: 'Captcha options grid size must be a number from 2-6' }, + { result: numberBody(req.body.captcha_options_image_size, 50, 500), expected: true, error: 'Captcha options image size must be a number from 50-500' }, + { result: numberBody(req.body.captcha_options_grid_icon_y_offset, 0, 50), expected: true, error: 'Captcha options icon y offset must be a number from 0-50' }, + { result: numberBody(req.body.captcha_options_num_distorts_min, 0, 10), expected: true, error: 'Captcha options min distorts must be a number from 0-10' }, + { result: numberBody(req.body.captcha_options_num_distorts_max, 0, 10), expected: true, error: 'Captcha options max distorts must be a number from 0-10' }, + { result: minmaxBody(req.body.captcha_options_num_distorts_min, req.body.captcha_options_num_distorts_max), expected: true, error: 'Captcha options distorts min must be less than max' }, + { result: numberBody(req.body.captcha_options_distortion, 0, 50), expected: true, error: 'Captcha options distortion must be a number from 0-50' }, + { result: numberBody(req.body.dnsbl_cache_time), expected: true, error: 'Invalid dnsbl cache time' }, + { result: numberBody(req.body.flood_timers_same_content_same_ip), expected: true, error: 'Invalid flood time same content same ip' }, + { result: numberBody(req.body.flood_timers_same_content_any_ip), expected: true, error: 'Invalid flood time same contenet any ip' }, + { result: numberBody(req.body.flood_timers_any_content_same_ip), expected: true, error: 'Invalid flood time any content same ip' }, + { result: numberBody(req.body.block_bypass_expire_after_uses), expected: true, error: 'Block bypass expire after uses must be a number > 0' }, + { result: numberBody(req.body.block_bypass_expire_after_time), expected: true, error: 'Invalid block bypass expire after time' }, + { result: numberBody(req.body.ip_hash_perm_level, -1), expected: true, error: 'Invalid ip hash perm level' }, + { result: numberBody(req.body.delete_board_perm_level, 0, 4), expected: true, error: 'Invalid delete board perm level' }, + { result: numberBody(req.body.perm_levels_markdown_green, 0, 4), expected: true, error: 'Invalid greentext markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_pink, 0, 4), expected: true, error: 'Invalid pinktext markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_title, 0, 4), expected: true, error: 'Invalid title markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_bold, 0, 4), expected: true, error: 'Invalid bold markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_underline, 0, 4), expected: true, error: 'Invalid underline markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_strike, 0, 4), expected: true, error: 'Invalid strike markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_italic, 0, 4), expected: true, error: 'Invalid italicmarkdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_mono, 0, 4), expected: true, error: 'Invalid mono markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_code, 0, 4), expected: true, error: 'Invalid code block markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_spoiler, 0, 4), expected: true, error: 'Invalid spoiler markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_detected, 0, 4), expected: true, error: 'Invalid detected markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_link, 0, 4), expected: true, error: 'Invalid link markdown perm level' }, + { result: numberBody(req.body.perm_levels_markdown_dice, 0, 4), expected: true, error: 'Invalid dice markdown perm level' }, + { result: numberBody(req.body.rate_limit_cost_captcha, 1, 100), expected: true, error: 'Rate limit cost captcha must be a number from 1-100' }, + { result: numberBody(req.body.rate_limit_cost_board_settings, 1, 100), expected: true, error: 'Rate limit cost board settings must be a number from 1-100' }, + { result: numberBody(req.body.rate_limit_cost_edit_post, 1, 100), expected: true, error: 'Rate limit cost edit post must be a number from 1-100' }, + { result: numberBody(req.body.overboard_limit), expected: true, error: 'Invalid overboard limit' }, + { result: numberBody(req.body.overboard_catalog_limit), expected: true, error: 'Invalid overboard catalog limit' }, + { result: numberBody(req.body.lock_wait), expected: true, error: 'Invalid lock wait' }, + { result: numberBody(req.body.prune_modlogs), expected: true, error: 'Prune modlogs must be a number of days' }, + { result: numberBody(req.body.prune_ips), expected: true, error: 'Prune ips must be a number of days' }, + { result: lengthBody(req.body.thumb_extension, 1), expected: false, error: 'Thumbnail extension must be at least 1 character' }, + { result: numberBody(req.body.thumb_size), expected: true, error: 'Invalid thumbnail size' }, + { result: numberBody(req.body.video_thumb_percentage, 0, 100), expected: true, error: 'Video thumbnail percentage must be a number from 1-100' }, + { result: numberBody(req.body.default_ban_duration), expected: true, error: 'Invalid default ban duration' }, + { result: numberBody(req.body.quote_limit), expected: true, error: 'Quote limit must be a number' }, + { result: numberBody(req.body.preview_replies), expected: true, error: 'Preview replies must be a number' }, + { result: numberBody(req.body.sticky_preview_replies), expected: true, error: 'Sticky preview replies must be a number' }, + { result: numberBody(req.body.early_404_fraction), expected: true, error: 'Early 404 fraction must be a number' }, + { result: numberBody(req.body.early_404_replies), expected: true, error: 'Early 404 fraction must be a number' }, + { result: numberBody(req.body.max_recent_news), expected: true, error: 'Max recent news must be a number' }, + { result: lengthBody(req.body.space_file_name_replacement, 1, 1), expected: false, error: 'Space file name replacement must be 1 character' }, + { result: lengthBody(req.body.highlight_options_language_subset, 0, 10000), expected: false, error: 'Highlight options language subset must not exceed 10000 characters' }, + { result: lengthBody(req.body.highlight_options_threshold), expected: false, error: 'Highlight options threshold must be a number' }, + { result: numberBody(req.body.global_limits_thread_limit_min), expected: true, error: 'Global thread limit minimum must be a number' }, + { result: numberBody(req.body.global_limits_thread_limit_max), expected: true, error: 'Global thread limit maximum must be a number' }, + { result: minmaxBody(req.body.global_limits_thread_limit_min, req.body.global_limits_thread_limit_max), expected: true, error: 'Global thread limit min must be less than max' }, + { result: numberBody(req.body.global_limits_reply_limit_min), expected: true, error: 'Global reply limit minimum must be a number' }, + { result: numberBody(req.body.global_limits_reply_limit_max), expected: true, error: 'Global reply limit maximum must be a number' }, + { result: minmaxBody(req.body.global_limits_reply_limit_min, req.body.global_limits_reply_limit_max), expected: true, error: 'Global reply limit min must be less than max' }, + { result: numberBody(req.body.global_limits_bump_limit_min), expected: true, error: 'Global bump limit minimum must be a number' }, + { result: numberBody(req.body.global_limits_bump_limit_max), expected: true, error: 'Global bump limit minimum must be a number' }, + { result: minmaxBody(req.body.global_limits_bump_limit_min, req.body.global_limits_bump_limit_max), expected: true, error: 'Global bump limit min must be less than max' }, + { result: numberBody(req.body.global_limits_post_files_max), expected: true, error: 'Post files max must be a number' }, + { result: numberBody(req.body.global_limits_post_files_size_max), expected: true, error: 'Post files size must be a number' }, + { result: numberBody(req.body.global_limits_banner_files_width, 1), expected: true, error: 'Banner files height must be a number > 0' }, + { result: numberBody(req.body.global_limits_banner_files_height, 1), expected: true, error: 'Banner files width must be a number > 0' }, + { result: numberBody(req.body.global_limits_banner_files_size_max), expected: true, error: 'Banner files size must be a number' }, + { result: numberBody(req.body.global_limits_banner_files_max), expected: true, error: 'Banner files max must be a number' }, + { result: numberBody(req.body.global_limits_banner_files_total), expected: true, error: 'Banner files total must be a number' }, + { result: numberBody(req.body.global_limits_flag_files_size_max), expected: true, error: 'Flag files size must be a number' }, + { result: numberBody(req.body.global_limits_flag_files_max), expected: true, error: 'Flag files max must be a number' }, + { result: numberBody(req.body.global_limits_flag_files_total), expected: true, error: 'Flag files total must be a number' }, + { result: numberBody(req.body.global_limits_field_length_name), expected: true, error: 'Global limit name field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_email), expected: true, error: 'Global limit email field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_subject), expected: true, error: 'Global limit subject field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_postpassword), expected: true, error: 'Global limit postpassword field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_message), expected: true, error: 'Global limit message field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_report_reason), expected: true, error: 'Global limit report reason field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_ban_reason), expected: true, error: 'Global limit ban reason field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_log_message), expected: true, error: 'Global limit log message field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_uri), expected: true, error: 'Global limit board uri field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_boardname), expected: true, error: 'Global limit board name field length must be a number' }, + { result: numberBody(req.body.global_limits_field_length_description), expected: true, error: 'Global limit board description field length must be a number' }, + { result: numberBody(req.body.global_limits_multi_input_posts_anon), expected: true, error: 'Multi input anon limit must be a number' }, + { result: numberBody(req.body.global_limits_multi_input_posts_staff), expected: true, error: 'Multi input staff limit must be a number' }, + { result: numberBody(req.body.global_limits_custom_css_max), expected: true, error: 'Custom css max must be a number' }, + { result: lengthBody(req.body.global_limits_custom_css_filters, 0, 10000), expected: false, error: 'Custom css filters must not exceed 10000 characters' }, + { result: numberBody(req.body.global_limits_custom_pages_max), expected: true, error: 'Custom pages max must be a number' }, + { result: numberBody(req.body.global_limits_custom_pages_max_length), expected: true, error: 'Custom pages max length must be a number' }, + { result: inArrayBody(req.body.board_defaults_theme, themeHelper.themes), expected: true, error: 'Invalid board default theme' }, + { result: inArrayBody(req.body.board_defaults_code_theme, themeHelper.codeThemes), expected: true, error: 'Invalid board default code theme' }, + { result: numberBody(req.body.board_defaults_lock_mode, 0, 2), expected: true, error: 'Board default lock mode must be a number from 0-2' }, + { result: numberBody(req.body.board_defaults_file_r9k_mode, 0, 2), expected: true, error: 'Board default file r9k mode must be a number from 0-2' }, + { result: numberBody(req.body.board_defaults_message_r9k_mode, 0, 2), expected: true, error: 'Board default message r9k mode must be a number from 0-2' }, + { result: numberBody(req.body.board_defaults_captcha_mode, 0, 2), expected: true, error: 'Board default captcha mode must be a number from 0-2' }, + { result: numberBody(req.body.board_defaults_tph_trigger), expected: true, error: 'Board default tph trigger must be a number' }, + { result: numberBody(req.body.board_defaults_pph_trigger), expected: true, error: 'Board default pph trigger must be a number' }, + { result: numberBody(req.body.board_defaults_pph_trigger_action, 0, 4), expected: true, error: 'Board default pph trigger action must be a number from 0-4' }, + { result: numberBody(req.body.board_defaults_tph_trigger_action, 0, 4), expected: true, error: 'Board default tph trigger action must be a number from 0-4' }, + { result: numberBody(req.body.board_defaults_captcha_reset, 0, 2), expected: true, error: 'Board defaults captcha reset must be a number from 0-2' }, + { result: numberBody(req.body.board_defaults_lock_reset, 0, 2), expected: true, error: 'Board defaults lock reset must be a number from 0-2' }, + { result: numberBodyVariable(req.body.board_defaults_reply_limit, req.body.global_limits_reply_limit_min, globalLimits.replyLimit.min, req.body.global_limits_reply_limit_max, globalLimits.replyLimit.max), expected: true, error: `Board defaults reply limit must be within global limits` }, + { result: numberBodyVariable(req.body.board_defaults_thread_limit, req.body.global_limits_thread_limit_min, globalLimits.threadLimit.min, req.body.global_limits_thread_limit_max, globalLimits.threadLimit.max), expected: true, error: `Board defaults thread limit must be within global limits` }, + { result: numberBodyVariable(req.body.board_defaults_bump_limit, req.body.global_limits_bump_limit_min, globalLimits.bumpLimit.min, req.body.global_limits_bump_limit_max, globalLimits.bumpLimit.max), expected: true, error: `Board defaults bump limit must be within global limits` }, + { result: numberBodyVariable(req.body.board_defaults_max_files, 0, 0, req.body.global_limits_post_files_max, globalLimits.postFiles.max), expected: true, error: `Board defaults max files must be within global limits` }, + { result: numberBodyVariable(req.body.board_defaults_max_thread_message_length, 0, 0, req.body.global_limits_field_length_message, globalLimits.fieldLength.message), expected: true, error: `Board defaults max thread message length must be within global limits` }, + { result: numberBodyVariable(req.body.board_defaults_max_reply_message_length, 0, 0, req.body.global_limits_field_length_message, globalLimits.fieldLength.message), expected: true, error: `Board defaults max reply message length must be within global limits` }, + { result: numberBody(req.body.board_defaults_min_thread_message_length), expected: true, error: 'Board defaults min thread message length must be a number' }, + { result: numberBody(req.body.board_defaults_min_reply_message_length), expected: true, error: 'Board defaults min reply message length must be a number' }, + { result: minmaxBody(req.body.board_defaults_min_thread_message_length, req.body.board_defaults_max_thread_message_length), expected: true, error: 'Board defaults thread message length min must be less than max' }, + { result: minmaxBody(req.body.board_defaults_min_reply_message_length, req.body.board_defaults_max_reply_message_length), expected: true, error: 'Board defaults reply message length min must be less than max' }, + { result: numberBody(req.body.board_defaults_filter_mode, 0, 2), expected: true, error: 'Board defaults filter most must be a number from 0-2' }, + { result: numberBody(req.body.board_defaults_filter_ban_duration), expected: true, error: 'Board defaults filter ban duration must be a number' }, + { result: lengthBody(req.body.webring_following, 0, 10000), expected: false, error: 'Webring following list must not exceed 10000 characters' }, + { result: lengthBody(req.body.webring_blacklist, 0, 10000), expected: false, error: 'Webring blacklist must not exceed 10000 characters' }, + { result: lengthBody(req.body.webring_logos, 0, 10000), expected: false, error: 'Webring logos list must not exceed 10000 characters' }, + ]; + + const errors = await checkSchema(schema); + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/globalmanage/settings.html' + }); + } + + try { + await changeGlobalSettings(req, res, next); + } catch (err) { + return next(err); + } - try { - await changeGlobalSettings(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/index.js b/controllers/forms/index.js new file mode 100644 index 00000000..cccef6be --- /dev/null +++ b/controllers/forms/index.js @@ -0,0 +1,37 @@ +'use strict'; + +module.exports = { + + deleteBoardController: require(__dirname+'/deleteboard.js'), + editBansController: require(__dirname+'/editbans.js'), + appealController: require(__dirname+'/appeal.js'), + globalActionController: require(__dirname+'/globalactions.js'), + actionController: require(__dirname+'/actions.js'), + addCustomPageController: require(__dirname+'/addcustompage.js'), + deleteCustomPageController: require(__dirname+'/deletecustompage.js'), + addNewsController: require(__dirname+'/addnews.js'), + editNewsController: require(__dirname+'/editnews.js'), + deleteNewsController: require(__dirname+'/deletenews.js'), + uploadBannersController: require(__dirname+'/uploadbanners.js'), + deleteBannersController: require(__dirname+'/deletebanners.js'), + addFlagsController: require(__dirname+'/addflags.js'), + deleteFlagsController: require(__dirname+'/deleteflags.js'), + boardSettingsController: require(__dirname+'/boardsettings.js'), + transferController: require(__dirname+'/transfer.js'), + resignController: require(__dirname+'/resign.js'), + deleteAccountController: require(__dirname+'/deleteaccount.js'), + loginController: require(__dirname+'/login.js'), + registerController: require(__dirname+'/register.js'), + changePasswordController: require(__dirname+'/changepassword.js'), + editAccountsController: require(__dirname+'/editaccounts.js'), + globalSettingsController: require(__dirname+'/globalsettings.js'), + createBoardController: require(__dirname+'/create.js'), + makePostController: require(__dirname+'/makepost.js'), + editPostController: require(__dirname+'/editpost.js'), + + //these dont have a "real" controller + newCaptcha: require(__dirname+'/../../models/forms/newcaptcha.js'), + blockBypass: require(__dirname+'/../../models/forms/blockbypass.js'), + logout: require(__dirname+'/../../models/forms/logout.js'), + +}; diff --git a/controllers/forms/login.js b/controllers/forms/login.js index bdd6cfd6..1cc5a6dd 100644 --- a/controllers/forms/login.js +++ b/controllers/forms/login.js @@ -1,40 +1,37 @@ 'use strict'; const loginAccount = require(__dirname+'/../../models/forms/login.js') - , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js'); - -module.exports = async (req, res, next) => { - - const errors = []; - - //check exist - if (!req.body.username || req.body.username.length <= 0) { - errors.push('Missing username'); - } - if (!req.body.password || req.body.password.length <= 0) { - errors.push('Missing password'); - } - - //check too long - if (req.body.username && req.body.username.length > 50) { - errors.push('Username must be 50 characters or less'); - } - if (req.body.password && req.body.password.length > 100) { - errors.push('Password must be 100 characters or less'); - } - - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/login.html' - }) - } - - try { - await loginAccount(req, res, next); - } catch (err) { - return next(err); - } + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); + +module.exports = { + + paramConverter: paramConverter({ + trimFields: ['username', 'password'], + }), + + controller: async (req, res, next) => { + const schema = [ + { result: existsBody(req.body.username), expected: true, error: 'Missing username' }, + { result: existsBody(req.body.password), expected: true, error: 'Missing password' }, + { result: lengthBody(req.body.username, 1, 50), expected: false, error: 'Username must be 50 characters or less' }, + { result: lengthBody(req.body.password, 1, 100), expected: false, error: 'Password must be 100 characters or less' }, + ]; + const errors = await checkSchema(schema); + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/login.html' + }) + } + try { + await loginAccount(req, res, next); + } catch (err) { + return next(err); + } + }, } diff --git a/controllers/forms/makepost.js b/controllers/forms/makepost.js index dab61622..ab3e9dc9 100644 --- a/controllers/forms/makepost.js +++ b/controllers/forms/makepost.js @@ -5,97 +5,119 @@ const makePost = require(__dirname+'/../../models/forms/makepost.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , { func: pruneFiles } = require(__dirname+'/../../schedules/tasks/prune.js') , config = require(__dirname+'/../../config.js') - , { Files } = require(__dirname+'/../../db/'); + , { Files } = require(__dirname+'/../../db/') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { pruneImmediately, globalLimits, disableAnonymizerFilePosting } = config.get; - const errors = []; + options: { + timeFields: [], + trimFields: [], + allowedArrays: [], + processThreadIdParam: [], + processDateParam: [], + processMessageLength: [], + numberFields: [], + numberArrays: [], + objectIdFields: [], + objectIdArrays: [] + }, - // even if force file and message are off, the post must contain one of either. - if ((!req.body.message || res.locals.messageLength === 0) && res.locals.numFiles === 0) { - errors.push('Posts must include a message or file'); - } - if (res.locals.anonymizer - && (disableAnonymizerFilePosting || res.locals.board.settings.disableAnonymizerFilePosting) - && res.locals.numFiles > 0) { - errors.push(`Posting files through anonymizers has been disabled ${disableAnonymizerFilePosting ? 'globally' : 'on this board'}`); - } - if (res.locals.numFiles > res.locals.board.settings.maxFiles) { - errors.push(`Too many files. Max files per post ${res.locals.board.settings.maxFiles < globalLimits.postFiles.max ? 'on this board ' : ''}is ${res.locals.board.settings.maxFiles}`); - } - // check file, subject and message enforcement according to board settings - if (!req.body.subject || req.body.subject.length === 0) { - if (!req.body.thread && res.locals.board.settings.forceThreadSubject) { - errors.push('Threads must include a subject'); - } //no option to force op subject, seems useless - } - if (globalLimits.postFiles.max !== 0 && res.locals.board.settings.maxFiles !== 0 && res.locals.numFiles === 0) { - if (!req.body.thread && res.locals.board.settings.forceThreadFile) { - errors.push('Threads must include a file'); - } else if (req.body.thread && res.locals.board.settings.forceReplyFile) { - errors.push('Posts must include a file'); + paramConverter: paramConverter(module.exports.options), + + controller: async (req, res, next) => { + + const { pruneImmediately, globalLimits, disableAnonymizerFilePosting } = config.get; + const errors = []; + + // even if force file and message are off, the post must contain one of either. + if ((!req.body.message || res.locals.messageLength === 0) && res.locals.numFiles === 0) { + errors.push('Posts must include a message or file'); } - } - if (!req.body.message || res.locals.messageLength === 0) { - if (!req.body.thread && res.locals.board.settings.forceThreadMessage) { - errors.push('Threads must include a message'); - } else if (req.body.therad && res.locals.board.settings.forceReplyMessage) { - errors.push('Posts must include a message'); + if (res.locals.anonymizer + && (disableAnonymizerFilePosting || res.locals.board.settings.disableAnonymizerFilePosting) + && res.locals.numFiles > 0) { + errors.push(`Posting files through anonymizers has been disabled ${disableAnonymizerFilePosting ? 'globally' : 'on this board'}`); } - } - if (req.body.message) { - if (res.locals.messageLength > globalLimits.fieldLength.message) { - errors.push(`Message must be ${globalLimits.fieldLength.message} characters or less`); - } else if (!req.body.thread - && res.locals.board.settings.maxThreadMessageLength - && res.locals.messageLength > res.locals.board.settings.maxThreadMessageLength) { - errors.push(`Thread messages must be ${res.locals.board.settings.maxThreadLength} characters or less`); - } else if (req.body.thread - && res.locals.board.settings.maxReplyMessageLength - && res.locals.messageLength > res.locals.board.settings.maxReplyMessageLength) { - errors.push(`Reply messages must be ${res.locals.board.settings.maxReplyMessageLength} characters or less`); - } else if (!req.body.thread && res.locals.messageLength < res.locals.board.settings.minThreadMessageLength) { - errors.push(`Thread messages must be at least ${res.locals.board.settings.minThreadMessageLength} characters long`); - } else if (req.body.thread && res.locals.messageLength < res.locals.board.settings.minReplyMessageLength) { - errors.push(`Reply messages must be at least ${res.locals.board.settings.minReplyMessageLength} characters long`); + if (res.locals.numFiles > res.locals.board.settings.maxFiles) { + errors.push(`Too many files. Max files per post ${res.locals.board.settings.maxFiles < globalLimits.postFiles.max ? 'on this board ' : ''}is ${res.locals.board.settings.maxFiles}`); + } + // check file, subject and message enforcement according to board settings + if (!req.body.subject || req.body.subject.length === 0) { + if (!req.body.thread && res.locals.board.settings.forceThreadSubject) { + errors.push('Threads must include a subject'); + } //no option to force op subject, seems useless + } + if (globalLimits.postFiles.max !== 0 && res.locals.board.settings.maxFiles !== 0 && res.locals.numFiles === 0) { + if (!req.body.thread && res.locals.board.settings.forceThreadFile) { + errors.push('Threads must include a file'); + } else if (req.body.thread && res.locals.board.settings.forceReplyFile) { + errors.push('Posts must include a file'); + } + } + if (!req.body.message || res.locals.messageLength === 0) { + if (!req.body.thread && res.locals.board.settings.forceThreadMessage) { + errors.push('Threads must include a message'); + } else if (req.body.therad && res.locals.board.settings.forceReplyMessage) { + errors.push('Posts must include a message'); + } + } + if (req.body.message) { + if (res.locals.messageLength > globalLimits.fieldLength.message) { + errors.push(`Message must be ${globalLimits.fieldLength.message} characters or less`); + } else if (!req.body.thread + && res.locals.board.settings.maxThreadMessageLength + && res.locals.messageLength > res.locals.board.settings.maxThreadMessageLength) { + errors.push(`Thread messages must be ${res.locals.board.settings.maxThreadLength} characters or less`); + } else if (req.body.thread + && res.locals.board.settings.maxReplyMessageLength + && res.locals.messageLength > res.locals.board.settings.maxReplyMessageLength) { + errors.push(`Reply messages must be ${res.locals.board.settings.maxReplyMessageLength} characters or less`); + } else if (!req.body.thread && res.locals.messageLength < res.locals.board.settings.minThreadMessageLength) { + errors.push(`Thread messages must be at least ${res.locals.board.settings.minThreadMessageLength} characters long`); + } else if (req.body.thread && res.locals.messageLength < res.locals.board.settings.minReplyMessageLength) { + errors.push(`Reply messages must be at least ${res.locals.board.settings.minReplyMessageLength} characters long`); + } } - } - // subject, email, name, password limited length - if (req.body.postpassword && req.body.postpassword.length > globalLimits.fieldLength.postpassword) { - errors.push(`Password must be ${globalLimits.fieldLength.postpassword} characters or less`); - } - if (req.body.name && req.body.name.length > globalLimits.fieldLength.name) { - errors.push(`Name must be ${globalLimits.fieldLength.name} characters or less`); - } - if (req.body.subject && req.body.subject.length > globalLimits.fieldLength.subject) { - errors.push(`Subject must be ${globalLimits.fieldLength.subject} characters or less`); - } - if (req.body.email && req.body.email.length > globalLimits.fieldLength.email) { - errors.push(`Email must be ${globalLimits.fieldLength.email} characters or less`); - } + // subject, email, name, password limited length + if (req.body.postpassword && req.body.postpassword.length > globalLimits.fieldLength.postpassword) { + errors.push(`Password must be ${globalLimits.fieldLength.postpassword} characters or less`); + } + if (req.body.name && req.body.name.length > globalLimits.fieldLength.name) { + errors.push(`Name must be ${globalLimits.fieldLength.name} characters or less`); + } + if (req.body.subject && req.body.subject.length > globalLimits.fieldLength.subject) { + errors.push(`Subject must be ${globalLimits.fieldLength.subject} characters or less`); + } + if (req.body.email && req.body.email.length > globalLimits.fieldLength.email) { + errors.push(`Email must be ${globalLimits.fieldLength.email} characters or less`); + } - if (errors.length > 0) { - await deleteTempFiles(req).catch(e => console.error); - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}` - }); - } + if (errors.length > 0) { + await deleteTempFiles(req).catch(e => console.error); + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}` + }); + } - try { - await makePost(req, res, next); - } catch (err) { - await deleteTempFiles(req).catch(e => console.error); - if (res.locals.numFiles > 0) { - const incedFiles = req.files.file.filter(x => x.inced === true && x.filename != null); - const incedFileNames = incedFiles.map(x => x.filename); - await Files.decrement(incedFileNames).catch(e => console.error); - await pruneFiles(incedFileNames); + try { + await makePost(req, res, next); + } catch (err) { + await deleteTempFiles(req).catch(e => console.error); + if (res.locals.numFiles > 0) { + const incedFiles = req.files.file.filter(x => x.inced === true && x.filename != null); + const incedFileNames = incedFiles.map(x => x.filename); + await Files.decrement(incedFileNames).catch(e => console.error); + await pruneFiles(incedFileNames); + } + return next(err); } - return next(err); + } } diff --git a/controllers/forms/register.js b/controllers/forms/register.js index 092f8036..7cf8fd1b 100644 --- a/controllers/forms/register.js +++ b/controllers/forms/register.js @@ -3,64 +3,75 @@ const alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , config = require(__dirname+'/../../config.js') - , registerAccount = require(__dirname+'/../../models/forms/register.js'); + , registerAccount = require(__dirname+'/../../models/forms/register.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { enableUserAccountCreation } = config.get; + paramConverter: paramConverter({ + trimFields: ['username', 'password', 'passwordconfirm'], + }), - if (enableUserAccountCreation === false && res.locals.permLevel > 1) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'error': 'Acount creation is disabled', - 'redirect': '/register.html' - }); - } + controller: async (req, res, next) => { - const errors = []; + const { enableUserAccountCreation } = config.get; - //check exist - if (!req.body.username || req.body.username.length <= 0) { - errors.push('Missing username'); - } - if (!req.body.password || req.body.password.length <= 0) { - errors.push('Missing password'); - } - if (!req.body.passwordconfirm || req.body.passwordconfirm.length <= 0) { - errors.push('Missing password confirmation'); - } + if (enableUserAccountCreation === false && res.locals.permLevel > 1) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'error': 'Acount creation is disabled', + 'redirect': '/register.html' + }); + } - //check - if (req.body.username) { - if (req.body.username.length > 50) { - errors.push('Username must be 50 characters or less'); + const errors = []; + + //check exist + if (!req.body.username || req.body.username.length <= 0) { + errors.push('Missing username'); } - if (alphaNumericRegex.test(req.body.username) !== true) { - errors.push('Username must contain a-z 0-9 only'); + if (!req.body.password || req.body.password.length <= 0) { + errors.push('Missing password'); + } + if (!req.body.passwordconfirm || req.body.passwordconfirm.length <= 0) { + errors.push('Missing password confirmation'); } - } - if (req.body.password && req.body.password.length > 100) { - errors.push('Password must be 100 characters or less'); - } - if (req.body.passwordconfirm && req.body.passwordconfirm.length > 100) { - errors.push('Password confirmation must be 100 characters or less'); - } - if (req.body.password != req.body.passwordconfirm) { - errors.push('Password and password confirmation must match'); - } - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': '/register.html' - }) - } + //check + if (req.body.username) { + if (req.body.username.length > 50) { + errors.push('Username must be 50 characters or less'); + } + if (alphaNumericRegex.test(req.body.username) !== true) { + errors.push('Username must contain a-z 0-9 only'); + } + } + if (req.body.password && req.body.password.length > 100) { + errors.push('Password must be 100 characters or less'); + } + if (req.body.passwordconfirm && req.body.passwordconfirm.length > 100) { + errors.push('Password confirmation must be 100 characters or less'); + } + if (req.body.password != req.body.passwordconfirm) { + errors.push('Password and password confirmation must match'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': '/register.html' + }) + } + + try { + await registerAccount(req, res, next); + } catch (err) { + return next(err); + } - try { - await registerAccount(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/resign.js b/controllers/forms/resign.js index 6b92d26e..67ddc628 100644 --- a/controllers/forms/resign.js +++ b/controllers/forms/resign.js @@ -3,42 +3,53 @@ const { Boards } = require(__dirname+'/../../db/') , resignFromBoard = require(__dirname+'/../../models/forms/resign.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js'); + , alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + trimFields: ['board'], + }), + + controller: async (req, res, next) => { + + const errors = []; + + if (!req.body.confirm) { + errors.push('Missing confirmation'); + } + if (!req.body.board || req.body.board.length === 0) { + errors.push('You did not select a board'); + } else if (alphaNumericRegex.test(req.body.board) !== true) { + errors.push('URI must contain a-z 0-9 only'); + } else { + try { + res.locals.board = await Boards.findOne(req.body.board); + } catch (err) { + return next(err); + } + if (!res.locals.board) { + errors.push(`Board /${req.body.board}/ does not exist`); + } + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/account.html` + }) + } - if (!req.body.confirm) { - errors.push('Missing confirmation'); - } - if (!req.body.board || req.body.board.length === 0) { - errors.push('You did not select a board'); - } else if (alphaNumericRegex.test(req.body.board) !== true) { - errors.push('URI must contain a-z 0-9 only'); - } else { try { - res.locals.board = await Boards.findOne(req.body.board); + await resignFromBoard(req, res, next); } catch (err) { return next(err); } - if (!res.locals.board) { - errors.push(`Board /${req.body.board}/ does not exist`); - } - } - - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/account.html` - }) - } - try { - await resignFromBoard(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/transfer.js b/controllers/forms/transfer.js index 979f29ab..195348f7 100644 --- a/controllers/forms/transfer.js +++ b/controllers/forms/transfer.js @@ -2,37 +2,48 @@ const transferBoard = require(__dirname+'/../../models/forms/transferboard.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') - , alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js'); + , alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const errors = []; + paramConverter: paramConverter({ + trimFields: ['username'], + }), - if (!req.body.username || req.body.username.length === 0) { - errors.push('Missing transfer username'); - } - if (req.body.username && req.body.username.length > 50) { - errors.push('Transfer username must be 50 characters or less'); - } - if (req.body.username === res.locals.board.owner) { - errors.push('New owner username must not be same as old owner'); - } - if (alphaNumericRegex.test(req.body.username) !== true) { - errors.push('Username must contain a-z 0-9 only'); - } - - if (errors.length > 0) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/manage/settings.html` - }); - } + controller: async (req, res, next) => { + + const errors = []; + + if (!req.body.username || req.body.username.length === 0) { + errors.push('Missing transfer username'); + } + if (req.body.username && req.body.username.length > 50) { + errors.push('Transfer username must be 50 characters or less'); + } + if (req.body.username === res.locals.board.owner) { + errors.push('New owner username must not be same as old owner'); + } + if (alphaNumericRegex.test(req.body.username) !== true) { + errors.push('Username must contain a-z 0-9 only'); + } + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}/manage/settings.html` + }); + } + + try { + await transferBoard(req, res, next); + } catch (err) { + return next(err); + } - try { - await transferBoard(req, res, next); - } catch (err) { - return next(err); } } diff --git a/controllers/forms/uploadbanners.js b/controllers/forms/uploadbanners.js index 5429c189..375fafe6 100644 --- a/controllers/forms/uploadbanners.js +++ b/controllers/forms/uploadbanners.js @@ -3,35 +3,43 @@ const uploadBanners = require(__dirname+'/../../models/forms/uploadbanners.js') , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') , deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js') - , config = require(__dirname+'/../../config.js'); + , config = require(__dirname+'/../../config.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js'); -module.exports = async (req, res, next) => { +module.exports = { - const { globalLimits } = config.get; - const errors = []; + //paramConverter: paramConverter({}), - if (res.locals.numFiles === 0) { - errors.push('Must provide a file'); - } else if (res.locals.numFiles > globalLimits.bannerFiles.max) { - errors.push(`Exceeded max banner uploads in one request of ${globalLimits.bannerFiles.max}`); - } else if (res.locals.board.banners.length+res.locals.numFiles > globalLimits.bannerFiles.total) { - errors.push(`Total number of banners would exceed global limit of ${globalLimits.bannerFiles.total}`); - } + controller: async (req, res, next) => { - if (errors.length > 0) { - await deleteTempFiles(req).catch(e => console.error); - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/manage/assets.html` - }) - } + const { globalLimits } = config.get; + const errors = []; + + if (res.locals.numFiles === 0) { + errors.push('Must provide a file'); + } else if (res.locals.numFiles > globalLimits.bannerFiles.max) { + errors.push(`Exceeded max banner uploads in one request of ${globalLimits.bannerFiles.max}`); + } else if (res.locals.board.banners.length+res.locals.numFiles > globalLimits.bannerFiles.total) { + errors.push(`Total number of banners would exceed global limit of ${globalLimits.bannerFiles.total}`); + } + + if (errors.length > 0) { + await deleteTempFiles(req).catch(e => console.error); + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}/manage/assets.html` + }) + } + + try { + await uploadBanners(req, res, next); + } catch (err) { + await deleteTempFiles(req).catch(e => console.error); + return next(err); + } - try { - await uploadBanners(req, res, next); - } catch (err) { - await deleteTempFiles(req).catch(e => console.error); - return next(err); } } diff --git a/controllers/pages.js b/controllers/pages.js index bd1b9f5e..8966b2a2 100644 --- a/controllers/pages.js +++ b/controllers/pages.js @@ -22,7 +22,10 @@ const express = require('express') globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs } = require(__dirname+'/../models/pages/globalmanage/') , { changePassword, blockBypass, home, register, login, create, editNews, board, catalog, banners, randombanner, news, captchaPage, overboard, overboardCatalog, - captcha, thread, modlog, modloglist, account, boardlist, customPage } = require(__dirname+'/../models/pages/'); + captcha, thread, modlog, modloglist, account, boardlist, customPage } = require(__dirname+'/../models/pages/') + , threadParamConverter = paramConverter({ processThreadIdParam: true }) + , logParamConverter = paramConverter({ processDateParam: true }) + , newsParamConverter = paramConverter({ objectIdFields: ['newsid'] }); //homepage router.get('/index.html', home); @@ -38,11 +41,11 @@ router.get('/overboard.html', overboard); //overboard router.get('/catalog.html', overboardCatalog); //overboard catalog view //board pages -router.get('/:board/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).(html|json)', Boards.exists, paramConverter, board); //index -router.get('/:board/thread/:id([1-9][0-9]{0,}).(html|json)', Boards.exists, paramConverter, Posts.exists, thread); //thread view +router.get('/:board/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).(html|json)', Boards.exists, board); //index +router.get('/:board/thread/:id([1-9][0-9]{0,}).(html|json)', Boards.exists, threadParamConverter, Posts.exists, thread); //thread view router.get('/:board/catalog.(html|json)', Boards.exists, catalog); //catalog router.get('/:board/logs.html', Boards.exists, modloglist);//modlog list -router.get('/:board/logs/:date(\\d{2}-\\d{2}-\\d{4}).html', Boards.exists, paramConverter, modlog); //daily log +router.get('/:board/logs/:date(\\d{2}-\\d{2}-\\d{4}).html', Boards.exists, logParamConverter, modlog); //daily log router.get('/:board/custompage/:page.html', Boards.exists, customPage); //board custom page router.get('/:board/banners.html', Boards.exists, banners); //banners router.get('/randombanner', randombanner); //random banner @@ -56,8 +59,8 @@ router.get('/:board/manage/settings.html', useSession, sessionRefresh, isLoggedI router.get('/:board/manage/assets.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageAssets); router.get('/:board/manage/custompages.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageCustomPages); router.get('/:board/manage/catalog.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageCatalog); -router.get('/:board/manage/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, paramConverter, calcPerms, hasPerms(3), csrf, manageBoard); -router.get('/:board/manage/thread/:id([1-9][0-9]{0,}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, paramConverter, calcPerms, hasPerms(3), csrf, Posts.exists, manageThread); +router.get('/:board/manage/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageBoard); +router.get('/:board/manage/thread/:id([1-9][0-9]{0,}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, threadParamConverter, calcPerms, hasPerms(3), csrf, Posts.exists, manageThread); //global manage pages router.get('/globalmanage/reports.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageReports); @@ -70,7 +73,7 @@ router.get('/globalmanage/accounts.html', useSession, sessionRefresh, isLoggedIn router.get('/globalmanage/settings.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, globalManageSettings); //edit pages -router.get('/editnews/:newsid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, paramConverter, editNews); +router.get('/editnews/:newsid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, newsParamConverter, editNews); //TODO: edit post get endpoint //TODO: edit board custom page get endpoint diff --git a/helpers/paramconverter.js b/helpers/paramconverter.js index ca1922ba..15e9aa85 100644 --- a/helpers/paramconverter.js +++ b/helpers/paramconverter.js @@ -1,7 +1,12 @@ 'use strict'; const { ObjectId } = require(__dirname+'/../db/db.js') - //todo: separate these into a schema/set for differ ent routes and inject it before the controller, to prevent checkign a bunch of other shit for every post + , timeFieldRegex = /^(?[\d]+y)?(?[\d]+mo)?(?[\d]+w)?(?[\d]+d)?(?[\d]+h)?(?[\d]+m)?(?[\d]+s)?$/ + , timeUtils = require(__dirname+'/timeutils.js') + , dynamicResponse = require(__dirname+'/dynamic.js') + , makeArrayIfSingle = (obj) => !Array.isArray(obj) ? [obj] : obj; + +/* , allowedArrays = new Set(['captcha', 'checkedcustompages', 'checkednews', 'checkedposts', 'globalcheckedposts', 'spoiler', 'strip_filename', 'checkedreports', 'checkedbans', 'checkedbanners', 'checkedaccounts', 'checkedflags', 'countries']) , trimFields = ['allowed_hosts', 'dnsbl_blacklists', 'other_mime_types', 'highlight_options_language_subset', 'themes', 'code_themes', @@ -32,115 +37,63 @@ const { ObjectId } = require(__dirname+'/../db/db.js') 'perm_levels_markdown_italic', 'perm_levels_markdown_title', 'perm_levels_markdown_spoiler', 'perm_levels_markdown_mono', 'perm_levels_markdown_code', 'perm_levels_markdown_link', 'perm_levels_markdown_detected', 'perm_levels_markdown_dice'] //convert these to numbers before they hit our routes , timeFields = ['ban_duration', 'board_defaults_filter_ban_duration', 'default_ban_duration', 'block_bypass_expire_after_time', 'dnsbl_cache_time'] - , timeFieldRegex = /^(?[\d]+y)?(?[\d]+mo)?(?[\d]+w)?(?[\d]+d)?(?[\d]+h)?(?[\d]+m)?(?[\d]+s)?$/ - , timeUtils = require(__dirname+'/timeutils.js') - , dynamicResponse = require(__dirname+'/dynamic.js') - , makeArrayIfSingle = (obj) => !Array.isArray(obj) ? [obj] : obj; +objectIdFields: newsid, news_id +objectIdArrays: globalcheckedposts, checkednews, checkedbans +numberArryas: checkedposts +*/ -module.exports = (req, res, next) => { - - const bodyfields = Object.keys(req.body); - for (let i = 0; i < bodyfields.length; i++) { - const key = bodyfields[i]; - const val = req.body[key]; - /* - bodyparser can form arrays e.g. for multiple files, but we only want arrays in fields we - expect, to prevent issues when validating/using them later on. - */ - if (!allowedArrays.has(key) && Array.isArray(val)) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'message': 'Malformed input' - }); - } else if (allowedArrays.has(key) && !Array.isArray(val)) { - req.body[key] = makeArrayIfSingle(req.body[key]); //convert to arrays with single item for simpler case batch handling later - } - } +//might remove or add some to thislater +const defaultOptions = { + timeFields: [], + trimFields: [], + allowedArrays: [], + numberFields: [], + numberArrays: [], + objectIdFields: [], + objectIdArrays: [], + processThreadIdParam: false, + processDateParam: false, + processMessageLength: false, +}; - for (let i = 0; i < trimFields.length; i++) { - const field = trimFields[i]; - if (req.body[field]) { - //trimEnd() because trailing whitespace doesnt affect how a post appear and if it is all whitespace, trimEnd will get it all anyway - req.body[field] = req.body[field].trimEnd(); - } - } - - //proper length check for CRLF vs just LF, because browsers dont count CRLF as 2 characters like the server does (and like it technically is) - if (req.body.message) { - res.locals.messageLength = req.body.message.replace(/\r\n/igm, '\n').length; - } - - for (let i = 0; i < numberFields.length; i++) { - const field = numberFields[i]; - if (req.body[field] != null) { - const num = parseInt(req.body[field], 10); - if (Number.isSafeInteger(num)) { - req.body[field] = num; - } else { - req.body[field] = null; +module.exports = (options) => { + + options = { ...defaultOptions, ...options }; + + return (req, res, next) => { + + const { timeFields, trimFields, allowedArrays, + processThreadIdParam, processDateParam, processMessageLength, + numberFields, numberArrays, objectIdFields, objectIdArrays } = options; + /* check all body fields, body-parser prevents this array being too big, so no worry. + whitelist for fields that can be arrays, and convert singular of those fields to 1 length array */ + const bodyFields = Object.keys(req.body); + for (let i = 0; i < bodyFields.length; i++) { + const key = bodyFields[i]; + const val = req.body[key]; + if (!allowedArrays.includes(key) && Array.isArray(val)) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'message': 'Malformed input' + }); + } else if (allowedArrays.includes(key) && !Array.isArray(val)) { + req.body[key] = makeArrayIfSingle(req.body[key]); //convert to arrays with single item for simpler case batch handling later } } - } - try { - //ids for newspost editing - if (req.params.newsid) { - req.params.newsid = ObjectId(req.params.newsid); - } - if (req.body.news_id) { - req.body.news_id = ObjectId(req.body.news_id); - } - //convert checked reports to number - if (req.body.checkedposts) { - req.body.checkedposts = req.body.checkedposts.map(Number); - } - //convert checked global reports to mongoid - if (req.body.globalcheckedposts) { - req.body.globalcheckedposts = req.body.globalcheckedposts.map(ObjectId) - } - if (req.body.checkednews) { - req.body.checkednews = req.body.checkednews.map(ObjectId) - } - //convert checked bans to mongoid - if (req.body.checkedbans) { - req.body.checkedbans = req.body.checkedbans.map(ObjectId) - } - /* - //convert checked reports to mongoid - if (req.body.checkedreports) { - req.body.checkedreports = req.body.checkedreports.map(ObjectId) + //process trimFields to remove excess white space + for (let i = 0; i < trimFields.length; i++) { + const field = trimFields[i]; + if (req.body[field]) { + //trimEnd() because trailing whitespace doesnt affect how a post appear and if it is all whitespace, trimEnd will get it all anyway + req.body[field] = req.body[field].trimEnd(); + } } - */ - } catch (e) { - return dynamicResponse(req, res, 400, 'message', { - 'title': 'Bad request', - 'message': 'Malformed input' - }); - } - - //convert duration string to time in ms - for (let i = 0; i < timeFields.length; i++) { - const field = timeFields[i]; - if (req.body[field] != null) { - const matches = req.body[field].match(timeFieldRegex); - if (matches && matches.groups) { - const groups = matches.groups; - let duration = 0; - const groupKeys = Object.keys(groups); - for (let i = 0; i < groupKeys.length; i++) { - const key = groupKeys[i]; - if (!groups[key]) { - continue; - } - const mult = +groups[key].replace(/\D+/, ''); //remove the unit - if (Number.isSafeInteger(mult) //if the multiplier is safe int - && Number.isSafeInteger(mult*timeUtils[key]) //and multiplying it is safe int - && Number.isSafeInteger((mult*timeUtils[key])+duration)) { //and adding it to the total is safe - duration += mult*timeUtils[key]; - } - } - req.body[field] = duration; - } else { + + //convert numberFields into number + for (let i = 0; i < numberFields.length; i++) { + const field = numberFields[i]; + if (req.body[field] != null) { const num = parseInt(req.body[field], 10); if (Number.isSafeInteger(num)) { req.body[field] = num; @@ -149,23 +102,90 @@ module.exports = (req, res, next) => { } } } - } - - //thread id - if (req.params.id) { - req.params.id = +req.params.id; - } - - //moglog date - if (req.params.date) { - let [ month, day, year ] = req.params.date.split('-'); - month = month-1; - const date = new Date(Date.UTC(year, month, day, 0, 0, 0, 0)); - if (date !== 'Invalid Date') { - res.locals.date = { month, day, year, date }; + + //convert timeFields duration string to time in ms + for (let i = 0; i < timeFields.length; i++) { + const field = timeFields[i]; + if (req.body[field] != null) { + const matches = req.body[field].match(timeFieldRegex); + if (matches && matches.groups) { + const groups = matches.groups; + let duration = 0; + const groupKeys = Object.keys(groups); + for (let i = 0; i < groupKeys.length; i++) { + const key = groupKeys[i]; + if (!groups[key]) { + continue; + } + const mult = +groups[key].replace(/\D+/, ''); //remove the unit + if (Number.isSafeInteger(mult) //if the multiplier is safe int + && Number.isSafeInteger(mult*timeUtils[key]) //and multiplying it is safe int + && Number.isSafeInteger((mult*timeUtils[key])+duration)) { //and adding it to the total is safe + duration += mult*timeUtils[key]; + } + } + req.body[field] = duration; + } else { + const num = parseInt(req.body[field], 10); + if (Number.isSafeInteger(num)) { + req.body[field] = num; + } else { + req.body[field] = null; + } + } + } } - } - next(); + //convert/map some fields to ObjectId or Number + try { + for (let i = 0; i < objectIdFields.length; i++) { + const field = objectIdFields[i]; + if (req.body[field]) { + req.body[field] = ObjectId(req.body[field]); + } + } + for (let i = 0; i < objectIdArrays.length; i++) { + const field = objectIdArrays[i]; + if (req.body[field]) { + req.body[field] = req.body[field].map(ObjectId); + } + } + for (let i = 0; i < numberArrays.length; i++) { + const field = numberArrays[i]; + if (req.body[field]) { + req.body[field] = req.body[field].map(Number); + } + } + } catch (e) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'message': 'Malformed input' + }); + } + + //thread id + if (processThreadIdParam && req.params.id) { + req.params.id = +req.params.id; + } + + //moglog date + if (processDateParam && req.params.date) { + let [ month, day, year ] = req.params.date.split('-'); + month = month-1; + const date = new Date(Date.UTC(year, month, day, 0, 0, 0, 0)); + if (date !== 'Invalid Date') { + res.locals.date = { month, day, year, date }; + } + } + + /* normalise message length check for CRLF vs just LF, because String.length depending on browser wont count CRLF as + 2 characters, so user gets "message too long" at the right length. */ + if (processMessageLength && req.body.message) { + res.locals.messageLength = req.body.message.replace(/\r\n/igm, '\n').length; + } + + next(); + + }; }