diff --git a/controllers/forms.js b/controllers/forms.js index d307926e..919f2eb1 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -21,9 +21,13 @@ const express = require('express') , loginAccount = require(__dirname+'/../models/forms/login.js') , changePassword = require(__dirname+'/../models/forms/changepassword.js') , registerAccount = require(__dirname+'/../models/forms/register.js') - , hasPerms = require(__dirname+'/../helpers/haspermsmiddleware.js') + , checkPermsMiddleware = require(__dirname+'/../helpers/haspermsmiddleware.js') + , checkPerms = require(__dirname+'/../helpers/hasperms.js') , numberConverter = require(__dirname+'/../helpers/number-converter.js') - , banCheck = require(__dirname+'/../helpers/bancheck.js'); + , banCheck = require(__dirname+'/../helpers/bancheck.js') + , actionChecker = require(__dirname+'/../helpers/actionchecker.js') + , actions = ['report', 'global_report', 'spoiler', 'delete', 'delete_file', 'dismiss', 'global_dismiss', 'ban', 'global_ban'] + , authActions = ['dismiss', 'global_dismiss', 'ban', 'global_ban']; // login to account router.post('/login', (req, res, next) => { @@ -202,7 +206,7 @@ router.post('/board/:board/post', Boards.exists, banCheck, numberConverter, asyn }); //upload banners -router.post('/board/:board/addbanners', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => { +router.post('/board/:board/addbanners', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => { let numFiles = 0; if (req.files && req.files.file) { @@ -238,7 +242,7 @@ router.post('/board/:board/addbanners', Boards.exists, banCheck, hasPerms, numbe }); //delete banners -router.post('/board/:board/deletebanners', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => { +router.post('/board/:board/deletebanners', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => { const errors = []; @@ -278,9 +282,25 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a 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') } + + //get what type of actions + const { anyPasswords, anyAuthed, anyValid } = actionChecker(req); + + //make sure they selected at least 1 action + if (!anyValid) { + errors.push('No actions selected') + } + //check if they have permission to perform the actions + const hasPerms = checkPerms(req, res); + if(!hasPerms && anyAuthed) { + errors.push('No permission') + } + + //check that actions are valid if (req.body.password && req.body.password.length > 50) { errors.push('Password must be 50 characters or less'); } @@ -290,17 +310,6 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a 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.spoiler - || req.body.delete - || req.body.delete_file - || req.body.dismiss - || req.body.global_dismiss - || req.body.ban - || req.body.global_ban)) { - errors.push('Invalid actions selected') - } if (req.body.report && (!req.body.report_reason || req.body.report_reason.length === 0)) { errors.push('Reports must have a reason') } @@ -317,53 +326,61 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a if (!posts || posts.length === 0) { return res.status(404).render('message', { 'title': 'Not found', - 'errors': 'Selected posts not found', + 'error': 'Selected posts not found', 'redirect': `/${req.params.board}` }) } + let passwordPosts = posts; + if (!hasPerms && anyPasswords) { + //if any actions require passwords, filter to ones with correct password + passwordPosts = posts.filter(post => { + return post.password != null + && post.password.length > 0 + && post.password == req.body.password + }); + if (passwordPosts.length === 0) { + return res.status(403).render('message', { + 'title': 'Forbidden', + 'error': 'Password did not match any selected posts', + 'redirect': `/${req.params.board}` + }); + } + } + const messages = []; try { - - // if getting global banned, board ban doesnt matter - if (req.body.global_ban) { - messages.push((await banPoster(req, res, next, null, posts))); - } else if (req.body.ban) { - messages.push((await banPoster(req, res, next, req.params.board, posts))); + if (hasPerms) { + // if getting global banned, board ban doesnt matter + if (req.body.global_ban) { + messages.push((await banPoster(req, res, next, null, posts))); + } else if (req.body.ban) { + messages.push((await banPoster(req, res, next, req.params.board, posts))); + } } - - //ban before deleting if (req.body.delete) { - messages.push((await deletePosts(req, res, next, posts))); + messages.push((await deletePosts(req, res, next, passwordPosts))); } else { // if it was getting deleted, we cant do any of these if (req.body.delete_file) { - messages.push((await deletePostsFiles(req, res, next, posts))); - } if (req.body.spoiler) { - messages.push((await spoilerPosts(req, res, next, posts))); + messages.push((await deletePostsFiles(req, res, next, passwordPosts))); + } else if (req.body.spoiler) { + messages.push((await spoilerPosts(req, res, next, passwordPosts))); } // cannot report and dismiss at same time if (req.body.report) { messages.push((await reportPosts(req, res, next))); - } else if (req.body.dismiss) { + } else if (hasPerms && req.body.dismiss) { messages.push((await dismissReports(req, res, next))); } - // cannot report and dismiss at same time if (req.body.global_report) { messages.push((await globalReportPosts(req, res, next, posts))); - } else if (req.body.global_dismiss) { + } else if (hasPerms && req.body.global_dismiss) { messages.push((await dismissGlobalReports(req, res, next, posts))); } } - } catch (err) { - //something not right - if (err.status) { - // return out special error - return res.status(err.status).render('message', err.message); - } - //some other error, use regular error handler return next(err); } @@ -376,7 +393,7 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a }); //unban -router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => { +router.post('/board/:board/unban', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => { //keep this for later in case i add other options to unbans const errors = []; @@ -397,12 +414,6 @@ router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConv try { messages.push((await removeBans(req, res, next))); } catch (err) { - //something not right - if (err.status) { - // return out special error - return res.status(err.status).render('message', err.message); - } - //some other error, use regular error handler return next(err); } @@ -414,32 +425,46 @@ router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConv }); -router.post('/global/actions', hasPerms, numberConverter, async(req, res, next) => { +router.post('/global/actions', checkPermsMiddleware, numberConverter, 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') } + + const { anyGlobal } = actionChecker(req); + + //make sure they selected at least 1 global action + if (!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.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.spoiler - || req.body.delete - || req.body.delete_file - || req.body.global_dismiss - || req.body.global_ban)) { - errors.push('Invalid actions selected') + if (req.body.report && (!req.body.report_reason || req.body.report_reason.length === 0)) { + errors.push('Reports must have a reason') } + //return the errors if (errors.length > 0) { return res.status(400).render('message', { 'title': 'Bad request', 'errors': errors, - 'redirect': '/globalmanage' + 'redirect': `/${req.params.board}` }) } + //get posts with global ids only const posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true); if (!posts || posts.length === 0) { return res.status(404).render('message', { @@ -451,12 +476,10 @@ router.post('/global/actions', hasPerms, numberConverter, async(req, res, next) const messages = []; try { - //ban before delete if (req.body.global_ban) { messages.push((await banPoster(req, res, next, null, posts))); } - // if its getting deleted, we cant do anythign else if (req.body.delete) { messages.push((await deletePosts(req, res, next, posts))); @@ -471,9 +494,7 @@ router.post('/global/actions', hasPerms, numberConverter, async(req, res, next) messages.push((await dismissGlobalReports(req, res, next, posts))); } } - } catch (err) { - //something not right if (err.status) { // return out special error return res.status(err.status).render('message', err.message); @@ -490,9 +511,8 @@ router.post('/global/actions', hasPerms, numberConverter, async(req, res, next) }); -router.post('/global/unban', hasPerms, numberConverter, async(req, res, next) => { +router.post('/global/unban', checkPermsMiddleware, numberConverter, async(req, res, next) => { - //TODO const errors = []; if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) { @@ -511,12 +531,6 @@ router.post('/global/unban', hasPerms, numberConverter, async(req, res, next) => try { messages.push((await removeBans(req, res, next))); } catch (err) { - //something not right - if (err.status) { - // return out special error - return res.status(err.status).render('message', err.message); - } - //some other error, use regular error handler return next(err); } diff --git a/db/posts.js b/db/posts.js index 540929c2..efc3ced6 100644 --- a/db/posts.js +++ b/db/posts.js @@ -46,13 +46,15 @@ module.exports = { //reverse order for board page thread.replies = replies.reverse(); - //count omitted image and posts - const numPreviewImages = replies.reduce((acc, post) => { - return acc + post.files.length; - }, 0); - thread.omittedimages = thread.replyimages - numPreviewImages; - thread.omittedposts = thread.replyposts - replies.length; - + //temporary mitigation for deletion issue + if (replies.length >= 5) { + //count omitted image and posts + const numPreviewImages = replies.reduce((acc, post) => { + return acc + post.files.length; + }, 0); + thread.omittedimages = thread.replyimages - numPreviewImages; + thread.omittedposts = thread.replyposts - replies.length; + } })); return threads; @@ -262,6 +264,13 @@ module.exports = { '$exists': true }, 'board': board + }, { + 'projection': { + 'salt': 0, + 'password': 0, + 'ip': 0, + 'globalreports': 0, + } }).toArray(); }, @@ -289,7 +298,6 @@ module.exports = { 'password': 0, 'ip': 0, 'reports': 0, - 'globalreports': 0, } }).toArray(); }, diff --git a/helpers/actionchecker.js b/helpers/actionchecker.js new file mode 100644 index 00000000..c97743b9 --- /dev/null +++ b/helpers/actionchecker.js @@ -0,0 +1,48 @@ +'use strict'; + +const actions = [ + {name:'lock', global:false, auth:true, passwords:false}, + {name:'sticky', global:false, auth:true, passwords:false}, + {name:'report', global:false, auth:false, passwords:false}, + {name:'global_report', global:false, auth:false, passwords:false}, + {name:'spoiler', global:true, auth:false, passwords:true}, + {name:'delete', global:true, auth:false, passwords:true}, + {name:'delete_file', global:true, auth:false, passwords:true}, + {name:'dismiss', global:false, auth:true, passwords:false}, + {name:'global_dismiss', global:true, auth:true, passwords:false}, + {name:'ban', global:false, auth:true, passwords:false}, + {name:'global_ban', global:true, auth:true, passwords:false}, +]; + +module.exports = (req, res) => { + + let anyGlobal = false + , anyAuthed = false + , anyPasswords = false + , anyValid = false; + + for (let i = 0; i < actions.length; i++) { + const action = actions[i]; + const bodyHasAction = req.body[action.name]; + if (bodyHasAction) { + if (!anyGlobal && action.global) { + anyGlobal = true; + } + if (!anyAuthed && action.auth) { + anyAuthed = true; + } + if (!anyPasswords && action.passwords) { + anyPasswords = true; + } + if (!anyValid) { + anyValid = true; + } + } + if (anyGlobal && anyAuthed && anyValid) { + break; + } + } + + return { anyGlobal, anyAuthed, anyValid, anyPasswords }; + +} diff --git a/models/forms/ban-poster.js b/models/forms/ban-poster.js index 25f4218a..ace121ab 100644 --- a/models/forms/ban-poster.js +++ b/models/forms/ban-poster.js @@ -5,21 +5,7 @@ const uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') , Bans = require(__dirname+'/../../db/bans.js') , Posts = require(__dirname+'/../../db/posts.js'); -module.exports = async (req, res, next, board, checkedPosts) => { - - const posts = checkedPosts; - - //if user is not logged in or if logged in but not authed, they cannot ban - if (!hasPerms(req, res)) { - throw { - 'status': 403, - 'message': { - 'title': 'Forbidden', - 'message': 'You do not have permission to issue bans', - 'redirect': `/${req.params.board}` - } - }; - } +module.exports = async (req, res, next, board, posts) => { const bans = posts.map(post => { return { diff --git a/models/forms/delete-post.js b/models/forms/delete-post.js index 1087d19e..7d0d3fca 100644 --- a/models/forms/delete-post.js +++ b/models/forms/delete-post.js @@ -5,34 +5,10 @@ const path = require('path') , fs = require('fs') , unlink = util.promisify(fs.unlink) , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') - , hasPerms = require(__dirname+'/../../helpers/hasperms.js') , Mongo = require(__dirname+'/../../db/db.js') , Posts = require(__dirname+'/../../db/posts.js'); -module.exports = async (req, res, next, checkedPosts) => { - - let posts = checkedPosts; - - //if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null - if (!hasPerms(req, res)) { - // filter posts by password only if NOT board moderator or owner - posts = posts.filter(post => { - // only include posts that have a password and that matches - return post.password != null - && post.password.length > 0 - && post.password == req.body.password - }); - if (posts.length === 0) { - throw { - 'status': 403, - 'message': { - 'title': 'Forbidden', - 'message': 'Password did not match any selected posts', - 'redirect': `/${req.params.board}` - } - }; - } - } +module.exports = async (req, res, next, posts) => { //filter to threads, then get the board and thread for each const boardThreads = posts.filter(x => x.thread == null).map(x => { diff --git a/models/forms/deletepostsfiles.js b/models/forms/deletepostsfiles.js index ca954c7c..ec49f162 100644 --- a/models/forms/deletepostsfiles.js +++ b/models/forms/deletepostsfiles.js @@ -9,34 +9,7 @@ const path = require('path') , Mongo = require(__dirname+'/../../db/db.js') , Posts = require(__dirname+'/../../db/posts.js'); -module.exports = async (req, res, next, checkedPosts) => { - - let posts = checkedPosts; - - //if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null - if (!hasPerms(req, res)) { - // filter posts by password only if NOT board moderator or owner - posts = posts.filter(post => { - // only include posts that have a password and that matches - return post.password != null - && post.password.length > 0 - && post.password == req.body.password - }); - if (posts.length === 0) { - throw { - 'status': 403, - 'message': { - 'title': 'Forbidden', - 'message': 'Password did not match any selected posts', - 'redirect': `/${req.params.board}` - } - }; - } - } - - //delete posts from DB - const postMongoIds = posts.map(post => Mongo.ObjectId(post._id)) - const deletedFilesPosts = await Posts.deleteFilesMany(postMongoIds).then(result => result.deletedCount); +module.exports = async (req, res, next, posts) => { //get filenames from all the posts let fileNames = []; @@ -53,6 +26,10 @@ module.exports = async (req, res, next, checkedPosts) => { ]) })); + //delete posts from DB + const postMongoIds = posts.map(post => Mongo.ObjectId(post._id)) + const deletedFilesPosts = await Posts.deleteFilesMany(postMongoIds).then(result => result.deletedCount); + //hooray! return `Deleted ${fileNames.length} files across ${deletedFilesPosts} posts` diff --git a/models/forms/dismiss-report.js b/models/forms/dismiss-report.js index 4fac5fb0..090416b0 100644 --- a/models/forms/dismiss-report.js +++ b/models/forms/dismiss-report.js @@ -5,17 +5,6 @@ const Posts = require(__dirname+'/../../db/posts.js') module.exports = async (req, res, next) => { - if (!hasPerms(req, res)) { - throw { - 'status': 403, - 'message': { - 'title': 'Forbidden', - 'message': `You are not authorised to dismiss reports.`, - 'redirect': `/${req.params.board}` - } - }; - } - const dismissedReports = await Posts.dismissReports(req.params.board, req.body.checkedposts).then(result => result.modifiedCount); return `Dismissed ${dismissedReports} reports successfully`; diff --git a/models/forms/dismissglobalreport.js b/models/forms/dismissglobalreport.js index 3fbb1b2a..8d6ec4f9 100644 --- a/models/forms/dismissglobalreport.js +++ b/models/forms/dismissglobalreport.js @@ -6,17 +6,6 @@ const Mongo = require(__dirname+'/../../db/db.js') module.exports = async (req, res, next, posts) => { - if (!hasPerms(req, res)) { - throw { - 'status': 403, - 'message': { - 'title': 'Forbidden', - 'message': 'You are not authorised to dismiss global reports.', - 'redirect': '/' - } - }; - } - const postMongoIds = posts.map(post => Mongo.ObjectId(post._id)) const dismissedCount = await Posts.dismissGlobalReports(postMongoIds).then(result => result.modifiedCount); diff --git a/models/forms/spoiler-post.js b/models/forms/spoiler-post.js index 6dcabb93..1f1eac0f 100644 --- a/models/forms/spoiler-post.js +++ b/models/forms/spoiler-post.js @@ -9,46 +9,14 @@ const path = require('path') , Mongo = require(__dirname+'/../../db/db.js') , Posts = require(__dirname+'/../../db/posts.js'); -module.exports = async (req, res, next, checkedPosts) => { +module.exports = async (req, res, next, posts) => { - //get all posts that were checked - let posts = checkedPosts; - - //if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null - if (!hasPerms(req, res)) { - - //filter by password - posts = posts.filter(post => { - return post.password != null - && post.password.length > 0 - && post.password == req.body.password - }); - if (posts.length === 0) { - throw { - 'status': 403, - 'message': { - 'title': 'Forbidden', - 'message': 'Password did not match any selected posts', - 'redirect': '/' - } - }; - } - - } - - //filter by not spoilered. maybe i add filters with optiins in the controller where it gets the posts? + // filter to ones not spoilered posts = posts.filter(post => { return !post.spoiler }); if (posts.length === 0) { - throw { - 'status': 409, - 'message': { - 'title': 'Conflict', - 'message': 'Posts already spoilered', - 'redirect': '/' - } - }; + return 'Posts already spoilered'; } // spoiler posts diff --git a/views/mixins/post.pug b/views/mixins/post.pug index e9eb9fcb..3e0590e1 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -44,7 +44,7 @@ mixin post(post, truncate, manage=false, globalmanage=false) let truncatedMessage = post.mesage; let truncated = false; if (messageLines > 10 || post.message.length > 1000) { - truncatedMessage = splitPost.slice(0, 16).join('\n'); + truncatedMessage = splitPost.slice(0, 10).join('\n'); truncated = true; } if truncated diff --git a/views/pages/message.pug b/views/pages/message.pug index 34b9642e..a5fa5e21 100644 --- a/views/pages/message.pug +++ b/views/pages/message.pug @@ -8,6 +8,8 @@ block content ul if message li #{message} + if error + li #{error} if messages each msg in messages li #{msg}