From e823cad14ed15694e81ef0f38f57546184b58549 Mon Sep 17 00:00:00 2001 From: fatchan Date: Thu, 11 Apr 2019 16:25:40 +0000 Subject: [PATCH] scuffed bans. todo: custom duration, expiry in DB, show banned post --- controllers/forms.js | 193 ++++++++++++++++++++------------- db-models/bans.js | 29 +++++ helpers/bancheck.js | 22 ++++ models/forms/ban-poster.js | 52 +++++++++ models/forms/delete-post.js | 49 ++++----- models/forms/dismiss-report.js | 28 ++--- models/forms/edit-post.js | 26 ++--- models/forms/make-post.js | 2 +- models/forms/report-post.js | 15 +-- models/forms/spoiler-post.js | 62 +++++------ views/pages/manage.pug | 11 +- views/pages/message.pug | 4 + wipe.js | 3 + 13 files changed, 308 insertions(+), 188 deletions(-) create mode 100644 db-models/bans.js create mode 100644 helpers/bancheck.js create mode 100644 models/forms/ban-poster.js diff --git a/controllers/forms.js b/controllers/forms.js index 677d87bf..68ef34dc 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -5,92 +5,95 @@ const express = require('express') , Boards = require(__dirname+'/../db-models/boards.js') , Posts = require(__dirname+'/../db-models/posts.js') , Trips = require(__dirname+'/../db-models/trips.js') + , Bans = require(__dirname+'/../db-models/bans.js') + , banPoster = require(__dirname+'/../models/forms/ban-poster.js') , makePost = require(__dirname+'/../models/forms/make-post.js') , deletePosts = require(__dirname+'/../models/forms/delete-post.js') , spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js') , reportPosts = require(__dirname+'/../models/forms/report-post.js') , dismissReports = require(__dirname+'/../models/forms/dismiss-report.js') , loginAccount = require(__dirname+'/../models/forms/login.js') - , registerAccount = require(__dirname+'/../models/forms/register.js') - , numberConverter = require(__dirname+'/../helpers/number-converter.js'); + , registerAccount = require(__dirname+'/../models/forms/register.js') + , numberConverter = require(__dirname+'/../helpers/number-converter.js') + , banCheck = require(__dirname+'/../helpers/bancheck.js'); // login to account router.post('/login', (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' - }) - } - - loginAccount(req, res); + 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' + }) + } + + loginAccount(req, res); }); //register account router.post('/register', (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 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.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' - }) - } - - registerAccount(req, res); + 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 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.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' + }) + } + + registerAccount(req, res); }); // make new post -router.post('/board/:board', Boards.exists, numberConverter, (req, res, next) => { +router.post('/board/:board', Boards.exists, banCheck, numberConverter, async (req, res, next) => { let numFiles = 0; if (req.files && req.files.file) { @@ -139,7 +142,7 @@ router.post('/board/:board', Boards.exists, numberConverter, (req, res, next) => }); //report, delete, sticky, etc -router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, next) => { +router.post('/board/:board/posts', Boards.exists, banCheck, numberConverter, async (req, res, next) => { const errors = []; @@ -152,12 +155,20 @@ router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, ne if (req.body.reason && req.body.reason.length > 50) { errors.push('Report must be 50 characters or less'); } - if (!(req.body.report || req.body.delete || req.body.dismiss || req.body.spoiler)) { + if (!(req.body.report + || req.body.delete + || req.body.dismiss + || req.body.spoiler + || req.body.ban + || req.body.global_ban)) { errors.push('Must select an action') } if (req.body.report && (!req.body.reason || req.body.reason.length === 0)) { errors.push('Reports must have a reason') } + if ((req.body.ban || req.body.global_ban) && (!req.body.reason || req.body.reason.length === 0)) { + errors.push('Bans must have a reason') + } if (errors.length > 0) { return res.status(400).render('message', { @@ -167,16 +178,46 @@ router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, ne }) } - if (req.body.report) { - reportPosts(req, res); - } else if (req.body.delete) { - deletePosts(req, res); - } else if (req.body.spoiler) { - spoilerPosts(req, res); - } else if (req.body.dismiss) { - dismissReports(req, res); + const messages = []; + try { + + //TODO: maybe fetch the posts first instead of checking multiple times with multiple actions + + //global or board ban + if (req.body.global_ban) { + messages.push((await banPoster(req, res, null))); + } else if (req.body.ban) { + messages.push((await banPoster(req, res, req.params.board))); + } + + // then if not deleting, we can spoiler and report or dismiss reports + if (req.body.delete) { + messages.push((await deletePosts(req, res))); + } else { + if (req.body.spoiler) { + messages.push((await spoilerPosts(req, res))); + } + if (req.body.report) { + messages.push((await reportPosts(req, res))); + } else if (req.body.dismiss) { + messages.push((await dismissReports(req, res))); + } + } + + } catch (err) { + if (err.status) { + return res.status(err.status).render('message', err.message); + } + console.error(err); + return res.status(500).render('error'); } + return res.render('message', { + 'title': 'Success', + 'messages': messages, + 'redirect': `/${req.params.board}` + }); + }); diff --git a/db-models/bans.js b/db-models/bans.js new file mode 100644 index 00000000..3f235e74 --- /dev/null +++ b/db-models/bans.js @@ -0,0 +1,29 @@ +'use strict'; + +const Mongo = require(__dirname+'/../helpers/db.js') + , db = Mongo.client.db('jschan').collection('bans'); + +module.exports = { + + find: (ip, board) => { + return db.find({ + 'ip': ip, + 'board': { + '$in': [board, null] + } + }).toArray(); + }, + + insertOne: (ban) => { + return db.insertOne(ban); + }, + + insertMany: (bans) => { + return db.insertMany(bans); + }, + + deleteAll: () => { + return db.deleteMany({}); + }, + +} diff --git a/helpers/bancheck.js b/helpers/bancheck.js new file mode 100644 index 00000000..1d0f5fb5 --- /dev/null +++ b/helpers/bancheck.js @@ -0,0 +1,22 @@ +'use strict'; + +const Bans = require(__dirname+'/../db-models/bans.js') + , hasPerms = require(__dirname+'/has-perms.js'); + +module.exports = async (req, res, next) => { + + if (!hasPerms(req, res)) { + const ip = req.headers['x-real-ip'] || req.connection.remoteAddress; + const bans = await Bans.find(ip, res.locals.board._id); + if (bans && bans.length > 0) { + //TODO: show posts banned for, expiry, etc + return res.status(403).render('message', { + 'title': 'Forbidden', + 'message': 'You are banned', + 'redirect': '/' + }); + } + } + next(); + +} diff --git a/models/forms/ban-poster.js b/models/forms/ban-poster.js new file mode 100644 index 00000000..c017ca5c --- /dev/null +++ b/models/forms/ban-poster.js @@ -0,0 +1,52 @@ +'use strict'; + +const uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') + , hasPerms = require(__dirname+'/../../helpers/has-perms.js') + , Bans = require(__dirname+'/../../db-models/bans.js') + , Posts = require(__dirname+'/../../db-models/posts.js'); + +module.exports = async (req, res, board) => { + + //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}` + } + }; + } + + //get all posts that were checked + let posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts + + if (!posts || posts.length === 0) { + throw { + 'status': 400, + 'message': { + 'title': 'Bad requests', + 'message': 'No posts found', + 'redirect': `/${req.params.board}` + } + }; + } + + const bans = posts.map(post => { + return { + 'ip': post.ip, + 'board': board, + 'post': post, + 'issuer': req.session.user.username + } + }); + + let bannedIps = 0; + const result = await Bans.insertMany(bans, board); + console.log(result) + bannedIps = result.insertedCount; + + return `Banned ${bannedIps} ips`; + +} diff --git a/models/forms/delete-post.js b/models/forms/delete-post.js index 20d63536..7eb79f8e 100644 --- a/models/forms/delete-post.js +++ b/models/forms/delete-post.js @@ -11,20 +11,17 @@ const path = require('path') module.exports = async (req, res) => { //get all posts that were checked - let posts; - try { - posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + let posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts if (!posts || posts.length === 0) { - return res.status(400).render('message', { - 'title': 'Bad requests', - 'message': 'No posts found', - 'redirect': `/${req.params.board}` - }); + throw { + 'status': 400, + 'message': { + 'title': 'Bad request', + 'message': 'No posts found', + 'redirect': `/${req.params.board}` + } + }; } //if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null @@ -37,11 +34,14 @@ module.exports = async (req, res) => { && post.password == req.body.password }); if (posts.length === 0) { - return res.status(403).render('message', { - 'title': 'Forbidden', - 'message': 'Password did not match any selected posts', - 'redirect': `/${req.params.board}` - }); + throw { + 'status': 403, + 'message': { + 'title': 'Forbidden', + 'message': 'Password did not match any selected posts', + 'redirect': `/${req.params.board}` + } + }; } } @@ -60,13 +60,8 @@ module.exports = async (req, res) => { //delete posts from DB let deletedPosts = 0; - try { - const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x.postId)); - deletedPosts = result.deletedCount; - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x.postId)); + deletedPosts = result.deletedCount; //get filenames from all the posts let fileNames = []; @@ -84,10 +79,6 @@ module.exports = async (req, res) => { })); //hooray! - return res.render('message', { - 'title': 'Success', - 'message': `Deleted ${threadIds.length} threads and ${deletedPosts} posts`, - 'redirect': `/${req.params.board}` - }); + return `Deleted ${threadIds.length} threads and ${deletedPosts} posts` } diff --git a/models/forms/dismiss-report.js b/models/forms/dismiss-report.js index c22a663d..c7d8e46d 100644 --- a/models/forms/dismiss-report.js +++ b/models/forms/dismiss-report.js @@ -6,26 +6,18 @@ const Posts = require(__dirname+'/../../db-models/posts.js') module.exports = async (req, res) => { if (!hasPerms(req, res)) { - return res.status(403).render('message', { - 'title': 'Forbidden', - 'message': `You are not authorised to dismiss reports.`, - 'redirect': `/${req.params.board}` - }); + throw { + 'status': 403, + 'message': { + 'title': 'Forbidden', + 'message': `You are not authorised to dismiss reports.`, + 'redirect': `/${req.params.board}` + } + }; } - try { - //dismiss reports from all checked posts - await Posts.dismissReports(req.params.board, req.body.checked); - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + await Posts.dismissReports(req.params.board, req.body.checked); - //hooray! - return res.render('message', { - 'title': 'Success', - 'message': `Dismissed report(s) successfully`, - 'redirect': `/${req.params.board}/manage` - }); + return `Dismissed report(s) successfully` } diff --git a/models/forms/edit-post.js b/models/forms/edit-post.js index 6064c249..6c8bad63 100644 --- a/models/forms/edit-post.js +++ b/models/forms/edit-post.js @@ -7,19 +7,17 @@ const uuidv4 = require('uuid/v4') module.exports = async (req, res, numFiles) => { // get the post that we are trying to edit - let post; - try { - post = await Posts.getPost(req.params.board, req.body.id, true); - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + let post = await Posts.getPost(req.params.board, req.body.id, true); + if (!thread || thread.thread != null) { - return res.status(400).render('message', { - 'title': 'Bad request', - 'message': 'Post does not exist.', - 'redirect': redirect - }); + throw { + 'status': 400, + 'message': { + 'title': 'Bad request', + 'message': 'Post does not exist.', + 'redirect': redirect + } + }; } // sticky, lock, sage, spoiler, etc @@ -27,8 +25,6 @@ module.exports = async (req, res, numFiles) => { //TODO } - const post = await Posts.updateOne(req.params.board, data) - const successRedirect = `/${req.params.board}/thread/${req.body.thread || post.insertedId}`; + return ``; - return res.redirect(successRedirect); } diff --git a/models/forms/make-post.js b/models/forms/make-post.js index 65c0e436..5d5c76fd 100644 --- a/models/forms/make-post.js +++ b/models/forms/make-post.js @@ -178,7 +178,7 @@ module.exports = async (req, res, numFiles) => { return res.status(500).render('error'); } - const successRedirect = `/${req.params.board}/thread/${req.body.thread || postId}`; + const successRedirect = `/${req.params.board}/thread/${req.body.thread || postId}#${postId}`; return res.redirect(successRedirect); } diff --git a/models/forms/report-post.js b/models/forms/report-post.js index 4f454a93..89d012fb 100644 --- a/models/forms/report-post.js +++ b/models/forms/report-post.js @@ -11,19 +11,10 @@ module.exports = async (req, res) => { 'ip': ip } - try { - //push the report to all checked posts - await Posts.reportMany(req.params.board, req.body.checked, report); - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + //push the report to all checked posts + await Posts.reportMany(req.params.board, req.body.checked, report); //hooray! - return res.render('message', { - 'title': 'Success', - 'message': `Reported post(s) successfully`, - 'redirect': `/${req.params.board}` - }); + return `Reported post(s) successfully` } diff --git a/models/forms/spoiler-post.js b/models/forms/spoiler-post.js index 15847f77..ceababbb 100644 --- a/models/forms/spoiler-post.js +++ b/models/forms/spoiler-post.js @@ -11,20 +11,17 @@ const path = require('path') module.exports = async (req, res) => { //get all posts that were checked - let posts; - try { - posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + let posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts if (!posts || posts.length === 0) { - return res.status(400).render('message', { - 'title': 'Bad requests', - 'message': 'No posts found', - 'redirect': `/${req.params.board}` - }); + throw { + 'status': 400, + 'message': { + 'title': 'Bad requests', + 'message': 'No posts found', + 'redirect': `/${req.params.board}` + } + }; } //if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null @@ -37,11 +34,14 @@ module.exports = async (req, res) => { && post.password == req.body.password }); if (posts.length === 0) { - return res.status(403).render('message', { - 'title': 'Forbidden', - 'message': 'Password did not match any selected posts', - 'redirect': `/${req.params.board}` - }); + throw { + 'status': 403, + 'message': { + 'title': 'Forbidden', + 'message': 'Password did not match any selected posts', + 'redirect': `/${req.params.board}` + } + }; } } @@ -51,28 +51,22 @@ module.exports = async (req, res) => { return !post.spoiler }); if (posts.length === 0) { - return res.status(409).render('message', { - 'title': 'Conflict', - 'message': 'Selected posts are already spoilered', - 'redirect': `/${req.params.board}` - }); + throw { + 'status': 409, + 'message': { + 'title': 'Conflict', + 'message': 'Posts already spoilered', + 'redirect': `/${req.params.board}` + } + }; } // spoiler posts let spoileredPosts = 0; - try { - const result = await Posts.spoilerMany(req.params.board, posts.map(x => x.postId)); - spoileredPosts = result.modifiedCount; - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + const result = await Posts.spoilerMany(req.params.board, posts.map(x => x.postId)); + spoileredPosts = result.modifiedCount; //hooray! - return res.render('message', { - 'title': 'Success', - 'message': `Spoilered ${spoileredPosts} posts`, - 'redirect': `/${req.params.board}` - }); + return `Spoilered ${spoileredPosts} posts` } diff --git a/views/pages/manage.pug b/views/pages/manage.pug index a99461b1..cb6e86e7 100644 --- a/views/pages/manage.pug +++ b/views/pages/manage.pug @@ -2,7 +2,7 @@ extends ../layout.pug include ../mixins/post.pug block head - title Login + title Manage block content include ../includes/boardheader.pug @@ -28,8 +28,13 @@ block content | Spoiler span label - input.post-check(type='checkbox', name='dismiss' value=1) - | Dismiss + input.post-check(type='checkbox', name='ban' value=1) + | Ban + label + input.post-check(type='checkbox', name='global_ban' value=1) + | Global Ban + label + input#report(type='text', name='reason', placeholder='ban reason' autocomplete='off') input(type='submit', value='submit') diff --git a/views/pages/message.pug b/views/pages/message.pug index dab4ab0c..70fb77c1 100644 --- a/views/pages/message.pug +++ b/views/pages/message.pug @@ -7,6 +7,10 @@ block content h1 #{title} if message blockquote #{message} + if messages + ul + each msg in messages + li #{msg} if errors ul each error in errors diff --git a/wipe.js b/wipe.js index 59c62519..0189c090 100644 --- a/wipe.js +++ b/wipe.js @@ -12,6 +12,7 @@ const Mongo = require(__dirname+'/helpers/db.js') await Mongo.connect(); const Boards = require(__dirname+'/db-models/boards.js') , Posts = require(__dirname+'/db-models/posts.js') + , Bans = require(__dirname+'/db-models/bans.js') , Trips = require(__dirname+'/db-models/trips.js') , Accounts = require(__dirname+'/db-models/accounts.js'); console.log('deleting accounts') @@ -24,6 +25,8 @@ const Mongo = require(__dirname+'/helpers/db.js') await Boards.deleteIncrement('b'); await Boards.deleteAll(); await Trips.deleteAll(); + console.log('deleting bans'); + await Bans.deleteAll(); console.log('adding b and pol') await Boards.insertOne({ _id: 'pol',