reference #355 changes to paramconverter

got the changes to itself done to return the middleware function,
and for most routes i updated them
still TODO the more complex routes, and change them to the refactored schema checking
indiachan-spamvector
Thomas Lynch 3 years ago
parent 3eb3ba3a38
commit 42422d3d35
  1. 99
      controllers/forms.js
  2. 259
      controllers/forms/actions.js
  3. 51
      controllers/forms/addban.js
  4. 99
      controllers/forms/addcustompage.js
  5. 56
      controllers/forms/addflags.js
  6. 64
      controllers/forms/addnews.js
  7. 94
      controllers/forms/appeal.js
  8. 322
      controllers/forms/boardsettings.js
  9. 97
      controllers/forms/changepassword.js
  10. 93
      controllers/forms/create.js
  11. 59
      controllers/forms/deleteaccount.js
  12. 74
      controllers/forms/deletebanners.js
  13. 89
      controllers/forms/deleteboard.js
  14. 45
      controllers/forms/deletecustompage.js
  15. 55
      controllers/forms/deleteflags.js
  16. 46
      controllers/forms/deletenews.js
  17. 58
      controllers/forms/editaccounts.js
  18. 79
      controllers/forms/editbans.js
  19. 71
      controllers/forms/editnews.js
  20. 113
      controllers/forms/editpost.js
  21. 171
      controllers/forms/globalactions.js
  22. 330
      controllers/forms/globalsettings.js
  23. 37
      controllers/forms/index.js
  24. 67
      controllers/forms/login.js
  25. 184
      controllers/forms/makepost.js
  26. 107
      controllers/forms/register.js
  27. 67
      controllers/forms/resign.js
  28. 65
      controllers/forms/transfer.js
  29. 56
      controllers/forms/uploadbanners.js
  30. 17
      controllers/pages.js
  31. 260
      helpers/paramconverter.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);

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -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);
}
}

@ -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': '/'
});
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -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);
}
}

@ -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': '/',
});
}

@ -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);
}
}
}

@ -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'
});
}

@ -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);
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -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);
}
}

@ -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
});
}

@ -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);
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -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);
}
}

@ -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'),
};

@ -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);
}
},
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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

@ -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 = /^(?<YEAR>[\d]+y)?(?<MONTH>[\d]+mo)?(?<WEEK>[\d]+w)?(?<DAY>[\d]+d)?(?<HOUR>[\d]+h)?(?<MINUTE>[\d]+m)?(?<SECOND>[\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 = /^(?<YEAR>[\d]+y)?(?<MONTH>[\d]+mo)?(?<WEEK>[\d]+w)?(?<DAY>[\d]+d)?(?<HOUR>[\d]+h)?(?<MINUTE>[\d]+m)?(?<SECOND>[\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();
};
}

Loading…
Cancel
Save