refactor, all orm controllers now separate ^-^

merge-requests/208/head
fatchan 5 years ago
parent c8ee801750
commit 12f1df0e9c
  1. 679
      controllers/forms.js
  2. 81
      controllers/forms/actions.js
  3. 66
      controllers/forms/boardsettings.js
  4. 52
      controllers/forms/changepassword.js
  5. 59
      controllers/forms/create.js
  6. 38
      controllers/forms/deletebanners.js
  7. 57
      controllers/forms/deleteboard.js
  8. 62
      controllers/forms/globalactions.js
  9. 39
      controllers/forms/login.js
  10. 85
      controllers/forms/makepost.js
  11. 51
      controllers/forms/register.js
  12. 37
      controllers/forms/removebans.js
  13. 42
      controllers/forms/uploadbanners.js
  14. 4
      helpers/checks/boarduriregex.js
  15. 13
      models/forms/create.js
  16. 14
      models/forms/login.js
  17. 12
      models/forms/makepost.js
  18. 15
      models/forms/register.js

@ -6,8 +6,6 @@ const express = require('express')
, Boards = require(__dirname+'/../db/boards.js')
, Posts = require(__dirname+'/../db/posts.js')
, upload = require('express-fileupload')
, path = require('path')
, alphaNumericRegex = /^[a-zA-Z0-9]+$/
, postFiles = upload({
createParentPath: true,
safeFileNames: /[^\w-]+/g,
@ -18,684 +16,77 @@ const express = require('express')
},
abortOnLimit: true,
useTempFiles: true,
tempFileDir: path.join(__dirname+'/../tmp/')
tempFileDir: __dirname+'/../tmp/'
})
, bannerFiles = upload({
createParentPath: true,
safeFileNames: /[^\w-]+/g,
preserveExtension: 4,
preserveExtension: 3,
limits: {
fileSize: 10 * 1024 * 1024,
files: 10
},
abortOnLimit: true,
useTempFiles: true,
tempFileDir: path.join(__dirname+'/../tmp/')
tempFileDir: __dirname+'/../tmp/'
})
, removeBans = require(__dirname+'/../models/forms/removebans.js')
, makePost = require(__dirname+'/../models/forms/makepost.js')
, deleteTempFiles = require(__dirname+'/../helpers/files/deletetempfiles.js')
, uploadBanners = require(__dirname+'/../models/forms/uploadbanners.js')
, deleteBanners = require(__dirname+'/../models/forms/deletebanners.js')
, deleteBoard = require(__dirname+'/../models/forms/deleteboard.js')
, loginAccount = require(__dirname+'/../models/forms/login.js')
, changePassword = require(__dirname+'/../models/forms/changepassword.js')
, changeBoardSettings = require(__dirname+'/../models/forms/changeboardsettings.js')
, registerAccount = require(__dirname+'/../models/forms/register.js')
, createBoard = require(__dirname+'/../models/forms/create.js')
//controllers
, deleteBoardController = require(__dirname+'/forms/deleteboard.js')
, removeBansController = require(__dirname+'/forms/removebans.js')
, globalActionController = require(__dirname+'/forms/globalactions.js')
, actionController = require(__dirname+'/forms/actions.js')
, uploadBannersController = require(__dirname+'/forms/uploadbanners.js')
, deleteBannersController = require(__dirname+'/forms/deletebanners.js')
, boardSettingsController = require(__dirname+'/forms/boardsettings.js')
, loginController = require(__dirname+'/forms/login.js')
, registerController = require(__dirname+'/forms/register.js')
, changePasswordController = require(__dirname+'/forms/changepassword.js')
, createBoardController = require(__dirname+'/forms/create.js')
, makePostController = require(__dirname+'/forms/makepost.js')
//middlewarechecks
, calcPerms = require(__dirname+'/../helpers/checks/calcpermsmiddleware.js')
, hasPerms = require(__dirname+'/../helpers/checks/haspermsmiddleware.js')
, spamCheck = require(__dirname+'/../helpers/checks/spamcheck.js')
, paramConverter = require(__dirname+'/../helpers/paramconverter.js')
, banCheck = require(__dirname+'/../helpers/checks/bancheck.js')
, isLoggedIn = require(__dirname+'/../helpers/checks/isloggedin.js')
, verifyCaptcha = require(__dirname+'/../helpers/captcha/captchaverify.js')
, actionHandler = require(__dirname+'/../models/forms/actionhandler.js')
, csrf = require(__dirname+'/../helpers/checks/csrfmiddleware.js')
, uploadDirectory = require(__dirname+'/../helpers/files/uploadDirectory.js')
, actionChecker = require(__dirname+'/../helpers/checks/actionchecker.js');
// login to account
router.post('/login', 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 res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/login.html'
})
}
try {
await loginAccount(req, res, next);
} catch (err) {
return next(err);
}
});
//change password
router.post('/changepassword', verifyCaptcha, 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');
}
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 res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/changepassword.html'
})
}
try {
await changePassword(req, res, next);
} catch (err) {
return next(err);
}
});
//create board
router.post('/create', csrf, isLoggedIn, verifyCaptcha, calcPerms, hasPerms(4), (req, res, next) => {
if (enableUserBoards === false && res.locals.permLevel !== 0) {
//only board admin can create boards when user board creation disabled
return res.status(400).render('message', {
'title': 'Bad request',
'error': 'Board creation is only available to site administration',
'redirect': '/'
})
}
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');
}
if (!req.body.description || req.body.description.length <= 0) {
errors.push('Missing description');
}
//other validation
if (req.body.uri) {
if (req.body.uri.length > 50) {
errors.push('URI must be 50 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 > 50) {
errors.push('Name must be 50 characters or less');
}
if (req.body.description && req.body.description.length > 50) {
errors.push('Description must be 50 characters or less');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/create.html'
});
}
createBoard(req, res, next);
});
//register account
router.post('/register', verifyCaptcha, (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');
}
if (!req.body.passwordconfirm || req.body.passwordconfirm.length <= 0) {
errors.push('Missing password confirmation');
}
//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 res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/register.html'
})
}
registerAccount(req, res, next);
});
//accounts
router.post('/login', loginController);
router.post('/register', verifyCaptcha, registerController);
router.post('/changepassword', verifyCaptcha, changePasswordController);
// create board
router.post('/create', csrf, isLoggedIn, verifyCaptcha, calcPerms, hasPerms(4), createBoardController);
// make new post
router.post('/board/:board/post', Boards.exists, calcPerms, banCheck, postFiles, paramConverter, verifyCaptcha, async (req, res, next) => {
if (req.files && req.files.file) {
if (Array.isArray(req.files.file)) {
res.locals.numFiles = req.files.file.filter(file => file.size > 0).length;
} else {
res.locals.numFiles = req.files.file.size > 0 ? 1 : 0;
req.files.file = [req.files.file];
}
res.locals.numFiles = Math.min(res.locals.numFiles, res.locals.board.settings.maxFiles)
}
const errors = [];
// even if force file and message are off, the post must contain one of either.
if (!req.body.message && res.locals.numFiles === 0) {
errors.push('Posts must include a message or file');
}
// 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 (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 (res.locals.board.settings.forceReplyFile) {
errors.push('Posts must include a file');
}
}
if (!req.body.message || req.body.message.length === 0) {
if (!req.body.thread && res.locals.board.settings.forceThreadMessage) {
errors.push('Threads must include a message');
} else if (res.locals.board.settings.forceReplyMessage) {
errors.push('Posts must include a message');
}
}
if (req.body.message) {
if (req.body.message.length > 4000) {
errors.push('Message must be 4000 characters or less');
} else if (!req.body.thread && req.body.message.length < res.locals.board.settings.minThreadMessageLength) {
errors.push(`Thread messages must be at least ${res.locals.board.settings.minMessageLength} characters long`);
} else if (req.body.thread && req.body.message.length < res.locals.board.settings.minReplyMessageLength) {
errors.push(`Reply messages must be at least ${res.locals.board.settings.minMessageLength} characters long`);
}
}
// subject, email, name, password limited length
if (req.body.name && req.body.name.length > 50) {
errors.push('Name must be 50 characters or less');
}
if (req.body.subject && req.body.subject.length > 50) {
errors.push('Subject must be 50 characters or less');
}
if (req.body.email && req.body.email.length > 50) {
errors.push('Email must be 50 characters or less');
}
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (errors.length > 0) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`
});
}
const flood = await spamCheck(req, res);
if (flood) {
deleteTempFiles(req).catch(e => console.error);
return res.status(429).render('message', {
'title': 'Flood detected',
'message': 'Please wait before making another post, or a post similar to another user',
'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);
return next(err);
}
});
//board settings
router.post('/board/:board/settings', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), paramConverter, async (req, res, next) => {
const errors = [];
if (req.body.description && (req.body.description.length < 1 || req.body.description.length > 50)) {
errors.push('Board description must be 1-50 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.name && (req.body.name.length < 1 || req.body.name.length > 50)) {
errors.push('Board name must be 1-50 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 < 1 || req.body.reply_limit > 1000)) {
errors.push('Reply Limit must be from 1-1000');
}
if (typeof req.body.thread_limit === 'number' && (req.body.thread_limit < 10 || req.body.thread_limit > 250)) {
errors.push('Threads Limit must be 10-250');
}
if (typeof req.body.max_files === 'number' && (req.body.max_files < 0 || req.body.max_files > 3)) {
errors.push('Max files must be 0-3');
}
if (typeof req.body.min_thread_message_length === 'number' && (req.body.min_thread_message_length < 0 || req.body.min_thread_message_length > 4000)) {
errors.push('Min thread message length must be 0-4000. 0 is disabled.');
}
if (typeof req.body.min_reply_message_length === 'number' && (req.body.min_reply_message_length < 0 || req.body.min_reply_message_length > 4000)) {
errors.push('Min reply message length must be 0-4000. 0 is disabled.');
}
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.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 > 3)) {
errors.push('Invalid tph trigger action.')
}
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 (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage.html`
})
}
try {
await changeBoardSettings(req, res, next);
} catch (err) {
return next(err);
}
});
//upload banners
router.post('/board/:board/addbanners', bannerFiles, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), paramConverter, async (req, res, next) => {
if (req.files && req.files.file) {
if (Array.isArray(req.files.file)) {
res.locals.numFiles = req.files.file.filter(file => file.size > 0).length;
} else {
res.locals.numFiles = req.files.file.size > 0 ? 1 : 0;
req.files.file = [req.files.file];
}
}
const errors = [];
if (res.locals.numFiles === 0) {
errors.push('Must provide a file');
}
if (res.locals.board.banners.length+res.locals.numFiles > 100) {
errors.push('Number of uploads would exceed 100 banner limit');
}
if (errors.length > 0) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage.html`
})
}
try {
await uploadBanners(req, res, next);
} catch (err) {
await deleteTempFiles(req).catch(e => console.error);
return next(err);
}
});
//delete banners
router.post('/board/:board/deletebanners', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), paramConverter, async (req, res, next) => {
const errors = [];
if (!req.body.checkedbanners || req.body.checkedbanners.length === 0 || req.body.checkedbanners.length > 10) {
errors.push('Must select 1-10 banners to delete');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage.html`
})
}
for (let i = 0; i < req.body.checkedbanners.length; i++) {
if (!res.locals.board.banners.includes(req.body.checkedbanners[i])) {
return res.status(400).render('message', {
'title': 'Bad request',
'message': 'Invalid banners selected',
'redirect': `/${req.params.board}/manage.html`
})
}
}
try {
await deleteBanners(req, res, next);
} catch (err) {
console.error(err);
return next(err);
}
});
//actions for a specific board
router.post('/board/:board/actions', Boards.exists, calcPerms, banCheck, paramConverter, verifyCaptcha, boardActionController); //Captcha on regular actions
router.post('/board/:board/modactions', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), paramConverter, boardActionController); //CSRF for mod actions
async function boardActionController(req, res, next) {
const errors = [];
//make sure they checked 1-10 posts
if (!req.body.checkedposts || req.body.checkedposts.length === 0 || req.body.checkedposts.length > 10) {
errors.push('Must select 1-10 posts');
}
res.locals.actions = actionChecker(req);
//make sure they selected at least 1 action
if (!res.locals.actions.anyValid) {
errors.push('No actions selected');
}
//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');
}
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.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (req.body.report_reason && req.body.report_reason.length > 50) {
errors.push('Report must be 50 characters or less');
}
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 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 (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/`
})
}
router.post('/board/:board/post', Boards.exists, calcPerms, banCheck, postFiles, paramConverter, verifyCaptcha, makePostController);
res.locals.posts = await Posts.getPosts(req.params.board, req.body.checkedposts, true);
if (!res.locals.posts || res.locals.posts.length === 0) {
return res.status(404).render('message', {
'title': 'Not found',
'error': 'Selected posts not found',
'redirect': `/${req.params.board}/`
})
}
// post actions for a specific board e.g. reports
router.post('/board/:board/actions', Boards.exists, calcPerms, banCheck, paramConverter, verifyCaptcha, actionController); //Captcha on regular actions
router.post('/board/:board/modactions', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), paramConverter, actionController); //CSRF for mod actions
router.post('/global/actions', csrf, calcPerms, isLoggedIn, hasPerms(1), paramConverter, globalActionController); //global manage page version (muilti-board, uses mongoids
try {
await actionHandler(req, res, next);
} catch (err) {
console.error(err);
return next(err);
}
// board settings
router.post('/board/:board/settings', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), paramConverter, boardSettingsController);
}
//add/remove banners
router.post('/board/:board/addbanners', bannerFiles, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), paramConverter, uploadBannersController);
router.post('/board/:board/deletebanners', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), paramConverter, deleteBannersController);
//global actions (global manage page)
router.post('/global/actions', csrf, calcPerms, isLoggedIn, hasPerms(1), paramConverter, globalActionController);
async function globalActionController(req, res, next) {
const errors = [];
//make sure they checked 1-10 posts
if (!req.body.globalcheckedposts || req.body.globalcheckedposts.length === 0 || req.body.globalcheckedposts.length > 10) {
errors.push('Must select 1-10 posts')
}
res.locals.actions = actionChecker(req);
//make sure they have any global actions, and that they only selected global actions
if (!res.locals.actions.anyGlobal || res.locals.actions.anyValid > res.locals.actions.anyGlobal) {
errors.push('Invalid actions selected');
}
//check that actions are valid
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 characters or less');
}
//return the errors
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/globalmanage.html'
})
}
//get posts with global ids only
res.locals.posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true);
if (!res.locals.posts || res.locals.posts.length === 0) {
return res.status(404).render('message', {
'title': 'Not found',
'errors': 'Selected posts not found',
'redirect': '/globalmanage.html'
})
}
try {
await actionHandler(req, res, next);
} catch (err) {
console.error(err);
return next(err);
}
}
//unban
//unbans
router.post('/global/unban', csrf, calcPerms, isLoggedIn, hasPerms(1), paramConverter, removeBansController);
router.post('/board/:board/unban', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), paramConverter, removeBansController);
async function removeBansController(req, res, next) {
//keep this for later in case i add other options to unbans
const errors = [];
if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) {
errors.push('Must select 1-10 bans')
}
const redirect = req.params.board ? `/${req.params.board}/manage.html` : '/globalmanage.html';
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
redirect
});
}
let amount = 0;
try {
amount = await removeBans(req, res, next);
} catch (err) {
return next(err);
}
return res.render('message', {
'title': 'Success',
'message': `Removed ${amount} bans`,
redirect
});
}
//delete board
router.post('/board/:board/deleteboard', csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), deleteBoardController);
router.post('/global/deleteboard', csrf, calcPerms, isLoggedIn, hasPerms(1), deleteBoardController);
async function deleteBoardController(req, res, next) {
const errors = [];
if (!req.body.confirm) {
errors.push('Missing confirmation');
}
if (!req.body.uri) {
errors.push('Missing URI');
}
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;
errors.push('URI does not match current board');
} else if (!(await Boards.findOne(req.body.uri))) {
//global must chech exist because it skips Boards.exists middleware
errors.push(`Board /${req.body.uri}/ does not exist`);
}
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.params.board ? `/${req.params.board}/manage.html` : '/globalmanage.html'
});
}
try {
await deleteBoard(req.body.uri);
} catch (err) {
return next(err);
}
return res.render('message', {
'title': 'Success',
'message': 'Board deleted',
'redirect': req.params.board ? '/' : '/globalmanage.html'
});
}
router.post('/newcaptcha', async(req, res, next) => {
//does this really need a separate file? probs not
res.clearCookie('captchaid');
return res.redirect('/captcha.html');
});
module.exports = router;

@ -0,0 +1,81 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js')
, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
, actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js');
module.exports = async (req, res, next) => {
const errors = [];
//make sure they checked 1-10 posts
if (!req.body.checkedposts || req.body.checkedposts.length === 0 || req.body.checkedposts.length > 10) {
errors.push('Must select 1-10 posts');
}
res.locals.actions = actionChecker(req);
//make sure they selected at least 1 action
if (!res.locals.actions.anyValid) {
errors.push('No actions selected');
}
//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');
}
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.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (req.body.report_reason && req.body.report_reason.length > 50) {
errors.push('Report must be 50 characters or less');
}
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 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 (errors.length > 0) {
return res.status(400).render('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 (!res.locals.posts || res.locals.posts.length === 0) {
return res.status(404).render('message', {
'title': 'Not found',
'error': 'Selected posts not found',
'redirect': `/${req.params.board}/`
})
}
try {
await actionHandler(req, res, next);
} catch (err) {
return next(err);
}
}

@ -0,0 +1,66 @@
'use strict';
const changeBoardSettings = require(__dirname+'/../../models/forms/changeboardsettings.js');
module.exports = async (req, res, next) => {
const errors = [];
if (req.body.description && (req.body.description.length < 1 || req.body.description.length > 50)) {
errors.push('Board description must be 1-50 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.name && (req.body.name.length < 1 || req.body.name.length > 50)) {
errors.push('Board name must be 1-50 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 < 1 || req.body.reply_limit > 1000)) {
errors.push('Reply Limit must be from 1-1000');
}
if (typeof req.body.thread_limit === 'number' && (req.body.thread_limit < 10 || req.body.thread_limit > 250)) {
errors.push('Threads Limit must be 10-250');
}
if (typeof req.body.max_files === 'number' && (req.body.max_files < 0 || req.body.max_files > 3)) {
errors.push('Max files must be 0-3');
}
if (typeof req.body.min_thread_message_length === 'number' && (req.body.min_thread_message_length < 0 || req.body.min_thread_message_length > 4000)) {
errors.push('Min thread message length must be 0-4000. 0 is disabled.');
}
if (typeof req.body.min_reply_message_length === 'number' && (req.body.min_reply_message_length < 0 || req.body.min_reply_message_length > 4000)) {
errors.push('Min reply message length must be 0-4000. 0 is disabled.');
}
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.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 > 3)) {
errors.push('Invalid tph trigger action.')
}
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 (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage.html`
})
}
try {
await changeBoardSettings(req, res, next);
} catch (err) {
return next(err);
}
}

@ -0,0 +1,52 @@
'use strict';
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');
}
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 res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/changepassword.html'
})
}
try {
await changePassword(req, res, next);
} catch (err) {
return next(err);
}
}

@ -0,0 +1,59 @@
'use strict';
const createBoard = require(__dirname+'/../../models/forms/create.js');
module.exports = async (req, res, next) => {
if (enableUserBoards === false && res.locals.permLevel !== 0) {
//only board admin can create boards when user board creation disabled
return res.status(400).render('message', {
'title': 'Bad request',
'error': 'Board creation is only available to site administration',
'redirect': '/'
})
}
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');
}
if (!req.body.description || req.body.description.length <= 0) {
errors.push('Missing description');
}
//other validation
if (req.body.uri) {
if (req.body.uri.length > 50) {
errors.push('URI must be 50 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 > 50) {
errors.push('Name must be 50 characters or less');
}
if (req.body.description && req.body.description.length > 50) {
errors.push('Description must be 50 characters or less');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/create.html'
});
}
try {
await createBoard(req, res, next);
} catch (err) {
return next(err);
}
}

@ -0,0 +1,38 @@
'use strict';
const deleteBanners = require(__dirname+'/../../models/forms/deletebanners.js');
module.exports = async (req, res, next) => {
const errors = [];
if (!req.body.checkedbanners || req.body.checkedbanners.length === 0 || req.body.checkedbanners.length > 10) {
errors.push('Must select 1-10 banners to delete');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage.html`
})
}
for (let i = 0; i < req.body.checkedbanners.length; i++) {
if (!res.locals.board.banners.includes(req.body.checkedbanners[i])) {
return res.status(400).render('message', {
'title': 'Bad request',
'message': 'Invalid banners selected',
'redirect': `/${req.params.board}/manage.html`
})
}
}
try {
await deleteBanners(req, res, next);
} catch (err) {
console.error(err);
return next(err);
}
}

@ -0,0 +1,57 @@
'use strict';
const Boards = require(__dirname+'/../../db/boards.js')
, deleteBoard = require(__dirname+'/../../models/forms/deleteboard.js')
, boardUriRegex = require(__dirname+'/../../helpers/checks/boarduriregex.js')
module.exports = async (req, res, next) => {
const errors = [];
if (!req.body.confirm) {
errors.push('Missing confirmation');
}
if (!req.body.uri) {
errors.push('Missing URI');
}
if (boardUriRegex.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;
errors.push('URI does not match current board');
}
let board;
try {
board = await Boards.findOne(req.body.uri)
} catch (err) {
return next(err);
}
if (board != null) {
//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 res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.params.board ? `/${req.params.board}/manage.html` : '/globalmanage.html'
});
}
try {
await deleteBoard(req.body.uri);
} catch (err) {
return next(err);
}
return res.render('message', {
'title': 'Success',
'message': 'Board deleted',
'redirect': req.params.board ? '/' : '/globalmanage.html'
});
}

@ -0,0 +1,62 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js')
, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
, actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js');
module.exports = async (req, res, next) => {
const errors = [];
//make sure they checked 1-10 posts
if (!req.body.globalcheckedposts || req.body.globalcheckedposts.length === 0 || req.body.globalcheckedposts.length > 10) {
errors.push('Must select 1-10 posts')
}
res.locals.actions = actionChecker(req);
//make sure they have any global actions, and that they only selected global actions
if (!res.locals.actions.anyGlobal || res.locals.actions.anyValid > res.locals.actions.anyGlobal) {
errors.push('Invalid actions selected');
}
//check that actions are valid
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 characters or less');
}
//return the errors
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/globalmanage.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 res.status(404).render('message', {
'title': 'Not found',
'errors': 'Selected posts not found',
'redirect': '/globalmanage.html'
})
}
try {
await actionHandler(req, res, next);
} catch (err) {
console.error(err);
return next(err);
}
}

@ -0,0 +1,39 @@
'use strict';
const loginAccount = require(__dirname+'/../../models/forms/login.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 res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/login.html'
})
}
try {
await loginAccount(req, res, next);
} catch (err) {
return next(err);
}
}

@ -0,0 +1,85 @@
'use strict';
const makePost = require(__dirname+'/../../models/forms/makepost.js')
, deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js');
module.exports = async (req, res, next) => {
if (req.files && req.files.file) {
if (Array.isArray(req.files.file)) {
res.locals.numFiles = req.files.file.filter(file => file.size > 0).length;
} else {
res.locals.numFiles = req.files.file.size > 0 ? 1 : 0;
req.files.file = [req.files.file];
}
res.locals.numFiles = Math.min(res.locals.numFiles, res.locals.board.settings.maxFiles)
}
const errors = [];
// even if force file and message are off, the post must contain one of either.
if (!req.body.message && res.locals.numFiles === 0) {
errors.push('Posts must include a message or file');
}
// 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 (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 (res.locals.board.settings.forceReplyFile) {
errors.push('Posts must include a file');
}
}
if (!req.body.message || req.body.message.length === 0) {
if (!req.body.thread && res.locals.board.settings.forceThreadMessage) {
errors.push('Threads must include a message');
} else if (res.locals.board.settings.forceReplyMessage) {
errors.push('Posts must include a message');
}
}
if (req.body.message) {
if (req.body.message.length > 4000) {
errors.push('Message must be 4000 characters or less');
} else if (!req.body.thread && req.body.message.length < res.locals.board.settings.minThreadMessageLength) {
errors.push(`Thread messages must be at least ${res.locals.board.settings.minMessageLength} characters long`);
} else if (req.body.thread && req.body.message.length < res.locals.board.settings.minReplyMessageLength) {
errors.push(`Reply messages must be at least ${res.locals.board.settings.minMessageLength} characters long`);
}
}
// subject, email, name, password limited length
if (req.body.name && req.body.name.length > 50) {
errors.push('Name must be 50 characters or less');
}
if (req.body.subject && req.body.subject.length > 50) {
errors.push('Subject must be 50 characters or less');
}
if (req.body.email && req.body.email.length > 50) {
errors.push('Email must be 50 characters or less');
}
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (errors.length > 0) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('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);
return next(err);
}
}

@ -0,0 +1,51 @@
'use strict';
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');
}
if (!req.body.passwordconfirm || req.body.passwordconfirm.length <= 0) {
errors.push('Missing password confirmation');
}
//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 res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/register.html'
})
}
try {
await registerAccount(req, res, next);
} catch (err) {
return next(err);
}
}

@ -0,0 +1,37 @@
'use strict';
const removeBans = require(__dirname+'/../../models/forms/removebans.js');
module.exports = async (req, res, next) => {
//keep this for later in case i add other options to unbans
const errors = [];
if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) {
errors.push('Must select 1-10 bans')
}
const redirect = req.params.board ? `/${req.params.board}/manage.html` : '/globalmanage.html';
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
redirect
});
}
let amount = 0;
try {
amount = await removeBans(req, res, next);
} catch (err) {
return next(err);
}
return res.render('message', {
'title': 'Success',
'message': `Removed ${amount} bans`,
redirect
});
}

@ -0,0 +1,42 @@
'use strict';
const uploadBanners = require(__dirname+'/../../models/forms/uploadbanners.js')
, deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js');
module.exports = async (req, res, next) => {
if (req.files && req.files.file) {
if (Array.isArray(req.files.file)) {
res.locals.numFiles = req.files.file.filter(file => file.size > 0).length;
} else {
res.locals.numFiles = req.files.file.size > 0 ? 1 : 0;
req.files.file = [req.files.file];
}
}
const errors = [];
if (res.locals.numFiles === 0) {
errors.push('Must provide a file');
}
if (res.locals.board.banners.length+res.locals.numFiles > 100) {
errors.push('Number of uploads would exceed 100 banner limit');
}
if (errors.length > 0) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage.html`
})
}
try {
await uploadBanners(req, res, next);
} catch (err) {
await deleteTempFiles(req).catch(e => console.error);
return next(err);
}
}

@ -0,0 +1,4 @@
'use strict';
//literally just alphanumeric ¯\_(ツ)_/¯
module.exports = /^[a-zA-Z0-9]+$/

@ -7,12 +7,7 @@ module.exports = async (req, res, next) => {
const { name, description } = req.body
, uri = req.body.uri.toLowerCase();
let board;
try {
board = await Boards.findOne(uri);
} catch (err) {
return next(err);
}
const board = await Boards.findOne(uri);
// if board exists reject
if (board != null) {
@ -62,11 +57,7 @@ module.exports = async (req, res, next) => {
}
}
try {
await Boards.insertOne(newBoard);
} catch (err) {
return next(err);
}
await Boards.insertOne(newBoard);
return res.redirect(`/${uri}/index.html`);

@ -11,12 +11,7 @@ module.exports = async (req, res, next) => {
const failRedirect = `/login.html${goto ? '?goto='+goto : ''}`
//fetch an account
let account;
try {
account = await Accounts.findOne(username);
} catch (err) {
return next(err);
}
const account = await Accounts.findOne(username);
//if the account doesnt exist, reject
if (!account) {
@ -28,12 +23,7 @@ module.exports = async (req, res, next) => {
}
// bcrypt compare input to saved hash
let passwordMatch;
try {
passwordMatch = await bcrypt.compare(password, account.passwordHash);
} catch (err) {
return next(err);
}
const passwordMatch = await bcrypt.compare(password, account.passwordHash);
//if hashes matched
if (passwordMatch === true) {

@ -27,11 +27,23 @@ const path = require('path')
, deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js')
, msTime = require(__dirname+'/../../helpers/mstime.js')
, deletePosts = require(__dirname+'/deletepost.js')
, spamCheck = require(__dirname+'/../../helpers/checks/spamcheck.js')
, { postPasswordSecret } = require(__dirname+'/../../configs/main.json')
, { buildCatalog, buildThread, buildBoard, buildBoardMultiple } = require(__dirname+'/../../helpers/build.js');
module.exports = async (req, res, next) => {
//spam/flood check
const flood = await spamCheck(req, res);
if (flood) {
deleteTempFiles(req).catch(e => console.error);
return res.status(429).render('message', {
'title': 'Flood detected',
'message': 'Please wait before making another post, or a post similar to another user',
'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`
});
}
// check if this is responding to an existing thread
let redirect = `/${req.params.board}/`
let salt = null;

@ -8,12 +8,7 @@ module.exports = async (req, res, next) => {
const username = req.body.username.toLowerCase();
const password = req.body.password;
let account;
try {
account = await Accounts.findOne(username);
} catch (err) {
return next(err);
}
const account = await Accounts.findOne(username);
// if the account exists reject
if (account != null) {
@ -25,12 +20,8 @@ module.exports = async (req, res, next) => {
}
// add account to db. password is hashed in db model func for easier tests
try {
await Accounts.insertOne(username, password, 4);
} catch (err) {
return next(err);
}
await Accounts.insertOne(username, password, 4);
return res.redirect('/login.html')
return res.redirect('/login.html');
}

Loading…
Cancel
Save