scuffed bans. todo: custom duration, expiry in DB, show banned post

merge-requests/208/head
fatchan 5 years ago
parent cbc7135c90
commit e823cad14e
  1. 193
      controllers/forms.js
  2. 29
      db-models/bans.js
  3. 22
      helpers/bancheck.js
  4. 52
      models/forms/ban-poster.js
  5. 49
      models/forms/delete-post.js
  6. 28
      models/forms/dismiss-report.js
  7. 26
      models/forms/edit-post.js
  8. 2
      models/forms/make-post.js
  9. 15
      models/forms/report-post.js
  10. 62
      models/forms/spoiler-post.js
  11. 11
      views/pages/manage.pug
  12. 4
      views/pages/message.pug
  13. 3
      wipe.js

@ -5,92 +5,95 @@ const express = require('express')
, Boards = require(__dirname+'/../db-models/boards.js') , Boards = require(__dirname+'/../db-models/boards.js')
, Posts = require(__dirname+'/../db-models/posts.js') , Posts = require(__dirname+'/../db-models/posts.js')
, Trips = require(__dirname+'/../db-models/trips.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') , makePost = require(__dirname+'/../models/forms/make-post.js')
, deletePosts = require(__dirname+'/../models/forms/delete-post.js') , deletePosts = require(__dirname+'/../models/forms/delete-post.js')
, spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js') , spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js')
, reportPosts = require(__dirname+'/../models/forms/report-post.js') , reportPosts = require(__dirname+'/../models/forms/report-post.js')
, dismissReports = require(__dirname+'/../models/forms/dismiss-report.js') , dismissReports = require(__dirname+'/../models/forms/dismiss-report.js')
, loginAccount = require(__dirname+'/../models/forms/login.js') , loginAccount = require(__dirname+'/../models/forms/login.js')
, registerAccount = require(__dirname+'/../models/forms/register.js') , registerAccount = require(__dirname+'/../models/forms/register.js')
, numberConverter = require(__dirname+'/../helpers/number-converter.js'); , numberConverter = require(__dirname+'/../helpers/number-converter.js')
, banCheck = require(__dirname+'/../helpers/bancheck.js');
// login to account // login to account
router.post('/login', (req, res, next) => { router.post('/login', (req, res, next) => {
const errors = []; const errors = [];
//check exist //check exist
if (!req.body.username || req.body.username.length <= 0) { if (!req.body.username || req.body.username.length <= 0) {
errors.push('Missing username'); errors.push('Missing username');
} }
if (!req.body.password || req.body.password.length <= 0) { if (!req.body.password || req.body.password.length <= 0) {
errors.push('Missing password'); errors.push('Missing password');
} }
//check too long //check too long
if (req.body.username && req.body.username.length > 50) { if (req.body.username && req.body.username.length > 50) {
errors.push('Username must be 50 characters or less'); errors.push('Username must be 50 characters or less');
} }
if (req.body.password && req.body.password.length > 100) { if (req.body.password && req.body.password.length > 100) {
errors.push('Password must be 100 characters or less'); errors.push('Password must be 100 characters or less');
} }
if (errors.length > 0) { if (errors.length > 0) {
return res.status(400).render('message', { return res.status(400).render('message', {
'title': 'Bad request', 'title': 'Bad request',
'errors': errors, 'errors': errors,
'redirect': '/login' 'redirect': '/login'
}) })
} }
loginAccount(req, res); loginAccount(req, res);
}); });
//register account //register account
router.post('/register', (req, res, next) => { router.post('/register', (req, res, next) => {
const errors = []; const errors = [];
//check exist //check exist
if (!req.body.username || req.body.username.length <= 0) { if (!req.body.username || req.body.username.length <= 0) {
errors.push('Missing username'); errors.push('Missing username');
} }
if (!req.body.password || req.body.password.length <= 0) { if (!req.body.password || req.body.password.length <= 0) {
errors.push('Missing password'); errors.push('Missing password');
} }
if (!req.body.passwordconfirm || req.body.passwordconfirm.length <= 0) { if (!req.body.passwordconfirm || req.body.passwordconfirm.length <= 0) {
errors.push('Missing password confirmation'); errors.push('Missing password confirmation');
} }
//check too long //check too long
if (req.body.username && req.body.username.length > 50) { if (req.body.username && req.body.username.length > 50) {
errors.push('Username must be 50 characters or less'); errors.push('Username must be 50 characters or less');
} }
if (req.body.password && req.body.password.length > 100) { if (req.body.password && req.body.password.length > 100) {
errors.push('Password must be 100 characters or less'); errors.push('Password must be 100 characters or less');
} }
if (req.body.passwordconfirm && req.body.passwordconfirm.length > 100) { if (req.body.passwordconfirm && req.body.passwordconfirm.length > 100) {
errors.push('Password confirmation must be 100 characters or less'); errors.push('Password confirmation must be 100 characters or less');
} }
if (req.body.password != req.body.passwordconfirm) { if (req.body.password != req.body.passwordconfirm) {
errors.push('Password and password confirmation must match'); errors.push('Password and password confirmation must match');
} }
if (errors.length > 0) { if (errors.length > 0) {
return res.status(400).render('message', { return res.status(400).render('message', {
'title': 'Bad request', 'title': 'Bad request',
'errors': errors, 'errors': errors,
'redirect': '/register' 'redirect': '/register'
}) })
} }
registerAccount(req, res); registerAccount(req, res);
}); });
// make new post // 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; let numFiles = 0;
if (req.files && req.files.file) { if (req.files && req.files.file) {
@ -139,7 +142,7 @@ router.post('/board/:board', Boards.exists, numberConverter, (req, res, next) =>
}); });
//report, delete, sticky, etc //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 = []; 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) { if (req.body.reason && req.body.reason.length > 50) {
errors.push('Report must be 50 characters or less'); 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') errors.push('Must select an action')
} }
if (req.body.report && (!req.body.reason || req.body.reason.length === 0)) { if (req.body.report && (!req.body.reason || req.body.reason.length === 0)) {
errors.push('Reports must have a reason') 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) { if (errors.length > 0) {
return res.status(400).render('message', { 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) { const messages = [];
reportPosts(req, res); try {
} else if (req.body.delete) {
deletePosts(req, res); //TODO: maybe fetch the posts first instead of checking multiple times with multiple actions
} else if (req.body.spoiler) {
spoilerPosts(req, res); //global or board ban
} else if (req.body.dismiss) { if (req.body.global_ban) {
dismissReports(req, res); 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}`
});
}); });

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

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

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

@ -11,20 +11,17 @@ const path = require('path')
module.exports = async (req, res) => { module.exports = async (req, res) => {
//get all posts that were checked //get all posts that were checked
let posts; let posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts
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');
}
if (!posts || posts.length === 0) { if (!posts || posts.length === 0) {
return res.status(400).render('message', { throw {
'title': 'Bad requests', 'status': 400,
'message': 'No posts found', 'message': {
'redirect': `/${req.params.board}` '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 //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 && post.password == req.body.password
}); });
if (posts.length === 0) { if (posts.length === 0) {
return res.status(403).render('message', { throw {
'title': 'Forbidden', 'status': 403,
'message': 'Password did not match any selected posts', 'message': {
'redirect': `/${req.params.board}` '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 //delete posts from DB
let deletedPosts = 0; let deletedPosts = 0;
try { const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x.postId));
const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x.postId)); deletedPosts = result.deletedCount;
deletedPosts = result.deletedCount;
} catch (err) {
console.error(err);
return res.status(500).render('error');
}
//get filenames from all the posts //get filenames from all the posts
let fileNames = []; let fileNames = [];
@ -84,10 +79,6 @@ module.exports = async (req, res) => {
})); }));
//hooray! //hooray!
return res.render('message', { return `Deleted ${threadIds.length} threads and ${deletedPosts} posts`
'title': 'Success',
'message': `Deleted ${threadIds.length} threads and ${deletedPosts} posts`,
'redirect': `/${req.params.board}`
});
} }

@ -6,26 +6,18 @@ const Posts = require(__dirname+'/../../db-models/posts.js')
module.exports = async (req, res) => { module.exports = async (req, res) => {
if (!hasPerms(req, res)) { if (!hasPerms(req, res)) {
return res.status(403).render('message', { throw {
'title': 'Forbidden', 'status': 403,
'message': `You are not authorised to dismiss reports.`, 'message': {
'redirect': `/${req.params.board}` 'title': 'Forbidden',
}); 'message': `You are not authorised to dismiss reports.`,
'redirect': `/${req.params.board}`
}
};
} }
try { await Posts.dismissReports(req.params.board, req.body.checked);
//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');
}
//hooray! return `Dismissed report(s) successfully`
return res.render('message', {
'title': 'Success',
'message': `Dismissed report(s) successfully`,
'redirect': `/${req.params.board}/manage`
});
} }

@ -7,19 +7,17 @@ const uuidv4 = require('uuid/v4')
module.exports = async (req, res, numFiles) => { module.exports = async (req, res, numFiles) => {
// get the post that we are trying to edit // get the post that we are trying to edit
let post; let post = await Posts.getPost(req.params.board, req.body.id, true);
try {
post = await Posts.getPost(req.params.board, req.body.id, true);
} catch (err) {
console.error(err);
return res.status(500).render('error');
}
if (!thread || thread.thread != null) { if (!thread || thread.thread != null) {
return res.status(400).render('message', { throw {
'title': 'Bad request', 'status': 400,
'message': 'Post does not exist.', 'message': {
'redirect': redirect 'title': 'Bad request',
}); 'message': 'Post does not exist.',
'redirect': redirect
}
};
} }
// sticky, lock, sage, spoiler, etc // sticky, lock, sage, spoiler, etc
@ -27,8 +25,6 @@ module.exports = async (req, res, numFiles) => {
//TODO //TODO
} }
const post = await Posts.updateOne(req.params.board, data) return ``;
const successRedirect = `/${req.params.board}/thread/${req.body.thread || post.insertedId}`;
return res.redirect(successRedirect);
} }

@ -178,7 +178,7 @@ module.exports = async (req, res, numFiles) => {
return res.status(500).render('error'); 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); return res.redirect(successRedirect);
} }

@ -11,19 +11,10 @@ module.exports = async (req, res) => {
'ip': ip 'ip': ip
} }
try { //push the report to all checked posts
//push the report to all checked posts await Posts.reportMany(req.params.board, req.body.checked, report);
await Posts.reportMany(req.params.board, req.body.checked, report);
} catch (err) {
console.error(err);
return res.status(500).render('error');
}
//hooray! //hooray!
return res.render('message', { return `Reported post(s) successfully`
'title': 'Success',
'message': `Reported post(s) successfully`,
'redirect': `/${req.params.board}`
});
} }

@ -11,20 +11,17 @@ const path = require('path')
module.exports = async (req, res) => { module.exports = async (req, res) => {
//get all posts that were checked //get all posts that were checked
let posts; let posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts
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');
}
if (!posts || posts.length === 0) { if (!posts || posts.length === 0) {
return res.status(400).render('message', { throw {
'title': 'Bad requests', 'status': 400,
'message': 'No posts found', 'message': {
'redirect': `/${req.params.board}` '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 //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 && post.password == req.body.password
}); });
if (posts.length === 0) { if (posts.length === 0) {
return res.status(403).render('message', { throw {
'title': 'Forbidden', 'status': 403,
'message': 'Password did not match any selected posts', 'message': {
'redirect': `/${req.params.board}` '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 return !post.spoiler
}); });
if (posts.length === 0) { if (posts.length === 0) {
return res.status(409).render('message', { throw {
'title': 'Conflict', 'status': 409,
'message': 'Selected posts are already spoilered', 'message': {
'redirect': `/${req.params.board}` 'title': 'Conflict',
}); 'message': 'Posts already spoilered',
'redirect': `/${req.params.board}`
}
};
} }
// spoiler posts // spoiler posts
let spoileredPosts = 0; let spoileredPosts = 0;
try { const result = await Posts.spoilerMany(req.params.board, posts.map(x => x.postId));
const result = await Posts.spoilerMany(req.params.board, posts.map(x => x.postId)); spoileredPosts = result.modifiedCount;
spoileredPosts = result.modifiedCount;
} catch (err) {
console.error(err);
return res.status(500).render('error');
}
//hooray! //hooray!
return res.render('message', { return `Spoilered ${spoileredPosts} posts`
'title': 'Success',
'message': `Spoilered ${spoileredPosts} posts`,
'redirect': `/${req.params.board}`
});
} }

@ -2,7 +2,7 @@ extends ../layout.pug
include ../mixins/post.pug include ../mixins/post.pug
block head block head
title Login title Manage
block content block content
include ../includes/boardheader.pug include ../includes/boardheader.pug
@ -28,8 +28,13 @@ block content
| Spoiler | Spoiler
span span
label label
input.post-check(type='checkbox', name='dismiss' value=1) input.post-check(type='checkbox', name='ban' value=1)
| Dismiss | 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') input(type='submit', value='submit')

@ -7,6 +7,10 @@ block content
h1 #{title} h1 #{title}
if message if message
blockquote #{message} blockquote #{message}
if messages
ul
each msg in messages
li #{msg}
if errors if errors
ul ul
each error in errors each error in errors

@ -12,6 +12,7 @@ const Mongo = require(__dirname+'/helpers/db.js')
await Mongo.connect(); await Mongo.connect();
const Boards = require(__dirname+'/db-models/boards.js') const Boards = require(__dirname+'/db-models/boards.js')
, Posts = require(__dirname+'/db-models/posts.js') , Posts = require(__dirname+'/db-models/posts.js')
, Bans = require(__dirname+'/db-models/bans.js')
, Trips = require(__dirname+'/db-models/trips.js') , Trips = require(__dirname+'/db-models/trips.js')
, Accounts = require(__dirname+'/db-models/accounts.js'); , Accounts = require(__dirname+'/db-models/accounts.js');
console.log('deleting accounts') console.log('deleting accounts')
@ -24,6 +25,8 @@ const Mongo = require(__dirname+'/helpers/db.js')
await Boards.deleteIncrement('b'); await Boards.deleteIncrement('b');
await Boards.deleteAll(); await Boards.deleteAll();
await Trips.deleteAll(); await Trips.deleteAll();
console.log('deleting bans');
await Bans.deleteAll();
console.log('adding b and pol') console.log('adding b and pol')
await Boards.insertOne({ await Boards.insertOne({
_id: 'pol', _id: 'pol',

Loading…
Cancel
Save