* add ban appeals

* unnecessary ocmment from c/p
merge-requests/208/head
Tom 5 years ago committed by GitHub
parent 2c9fa8f9b6
commit 96597d558d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      controllers/forms.js
  2. 52
      controllers/forms/appeal.js
  3. 22
      db/bans.js
  4. 5
      gulp/res/css/style.css
  5. 1
      gulpfile.js
  6. 6
      helpers/checks/bancheck.js
  7. 9
      models/forms/appeal.js
  8. 5
      models/forms/banposter.js
  9. 5
      models/forms/deletepostsfiles.js
  10. 4
      models/pages/create.js
  11. 3
      views/includes/actionfooter.pug
  12. 3
      views/includes/actionfooter_globalmanage.pug
  13. 3
      views/includes/actionfooter_manage.pug
  14. 12
      views/mixins/ban.pug
  15. 14
      views/mixins/post.pug
  16. 33
      views/pages/ban.pug
  17. 2
      views/pages/captcha.pug
  18. 4
      views/pages/globalmanage.pug
  19. 4
      views/pages/manage.pug

@ -31,6 +31,7 @@ const express = require('express')
//controllers
, deleteBoardController = require(__dirname+'/forms/deleteboard.js')
, removeBansController = require(__dirname+'/forms/removebans.js')
, appealController = require(__dirname+'/forms/appeal.js')
, globalActionController = require(__dirname+'/forms/globalactions.js')
, actionController = require(__dirname+'/forms/actions.js')
, addNewsController = require(__dirname+'/forms/addnews.js')
@ -77,6 +78,8 @@ router.post('/board/:board/settings', csrf, Boards.exists, calcPerms, banCheck,
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);
//appeals
router.post('/appeal', paramConverter, verifyCaptcha, appealController);
//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);

@ -0,0 +1,52 @@
'use strict';
const appealBans = require(__dirname+'/../../models/forms/appeal.js')
, { Bans } = require(__dirname+'/../../db');
module.exports = async (req, res, next) => {
const errors = [];
if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) {
errors.push('Must select 1-10 bans');
}
if (!req.body.message || req.body.message.length === 0) {
errors.push('Appeals must include a message');
}
if (req.body.message.length > 2000) {
errors.push('Appeal message must be less than 2000 characters');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/'
});
}
let amount = 0;
try {
amount = await appealBans(req, res, next);
} catch (err) {
return next(err);
}
if (amount === 0) {
/*
this can occur if they selected invalid id, non-ip match, already appealed, or unappealable bans. prevented by databse filter, so we use
use the updatedCount return value to check if any appeals were made successfully. if not, we end up here.
*/
return res.status(400).render('message', {
'title': 'Bad request',
'error': 'Invalid bans selected',
'redirect': '/'
});
}
return res.render('message', {
'title': 'Success',
'message': `Appealed ${amount} bans successfully`,
'redirect': '/'
});
}

@ -17,29 +17,31 @@ module.exports = {
}).toArray();
},
findMany: (board, ids) => {
return db.find({
appeal: (ip, ids, appeal) => {
return db.updateMany({
'_id': {
'$in': ids
},
'board': board
}).toArray();
},
getAllBans: () => {
return db.find({}).toArray();
'ip': ip,
'allowAppeal': true,
'appeal': null
}, {
'$set': {
'appeal': appeal,
}
});
},
getGlobalBans: () => {
return db.find({
'board': null
}).toArray();
}).sort({ _id: -1 }).toArray();
},
getBoardBans: (board) => {
return db.find({
'board': board,
}).toArray();
}).sort({ _id: -1 }).toArray();
},
removeMany: (board, ids) => {

@ -149,6 +149,9 @@ pre {
.mv-10 {
margin: 10px 0;
}
.mt-10 {
margin-top: 10px;
}
.mv-5 {
margin: 5px 0;
}
@ -434,7 +437,7 @@ td, th {
.post-files {
float: left;
margin-right: 10px;
margin-right: 1.5em;
display: flex;
flex-flow: row wrap;
align-items: start;

@ -97,6 +97,7 @@ async function wipe() {
, Ratelimits.db.dropIndexes()
, Posts.db.dropIndexes()
, Files.db.createIndex({ 'count': 1 })
, Bans.db.createIndex({ 'ip': 1 , 'board': 1 })
, Bans.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 }) //custom expiry, i.e. it will expire when current date > than this date
, Captchas.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 300 }) //captchas valid for 5 minutes
, Ratelimits.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 60 }) //per minute captcha ratelimit

@ -8,11 +8,13 @@ module.exports = async (req, res, next) => {
if (res.locals.permLevel > 1) {//global staff or admin bypass
const bans = await Bans.find(res.locals.ip, res.locals.board ? res.locals.board._id : null);
if (bans && bans.length > 0) {
const globalBans = bans.filter(ban => { return board === null });
const globalBans = bans.filter(ban => { return ban.board === null });
if (globalBans.length > 0 || (res.locals.permLevel >= 4 && globalBans.length !== bans.length)) {
//board staff bypass bans on their own board, but not global bans
const allowAppeal = bans.filter(ban => ban.allowAppeal === true && ban.appeal === null).length > 0;
return res.status(403).render('ban', {
bans: bans
bans: bans,
allowAppeal
});
}
}

@ -0,0 +1,9 @@
'use strict';
const { Bans } = require(__dirname+'/../../db/');
module.exports = async (req, res, next) => {
return Bans.appeal(res.locals.ip, req.body.checkedbans, req.body.message).then(r => r.modifiedCount);
}

@ -8,6 +8,7 @@ module.exports = async (req, res, next) => {
const banExpiry = new Date(req.body.ban_duration ? banDate.getTime() + req.body.ban_duration : 8640000000000000); //perm if none or malformed input
const banReason = req.body.ban_reason || 'No reason specified';
const banBoard = req.body.global_ban ? null : req.params.board;
const allowAppeal = req.body.no_appeal ? false : true;
const bans = res.locals.posts.map(post => {
return {
@ -17,7 +18,9 @@ module.exports = async (req, res, next) => {
'post': req.body.preserve_post ? post : null,
'issuer': req.session.user.username,
'date': banDate,
'expireAt': banExpiry
'expireAt': banExpiry,
allowAppeal,
'appeal': null
}
});

@ -45,6 +45,11 @@ module.exports = async (posts, unlinkOnly) => {
}));
return {
message:`Deleted ${fileNames.length} file(s) from server`,
//NOTE: only deletes from selected posts. other posts with same image will 404
action:'$set',
query: {
'files': []
}
};
}

@ -1,11 +1,11 @@
'use strict';
const { buildCreate } = require(__dirname+'/../../helpers/build.js');
//const { buildCreate } = require(__dirname+'/../../helpers/build.js');
module.exports = async (req, res, next) => {
res.render('create', {
csrf: req.csrfToken(),
csrf: req.csrfToken(), //is csrf necessary when a captcha is required?
});
}

@ -38,6 +38,9 @@ details.toggle-label
label
input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster
label
input.post-check(type='checkbox', name='no_appeal' value='Non-appealable')
| Non-appealable Ban
label
input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban

@ -20,6 +20,9 @@ details.toggle-label
label
input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster
label
input.post-check(type='checkbox', name='no_appeal' value='Non-appealable')
| Non-appealable Ban
label
input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban

@ -31,6 +31,9 @@ details.toggle-label
label
input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster
label
input.post-check(type='checkbox', name='no_appeal' value='Non-appealable')
| Non-appealable Ban
label
input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban

@ -1,8 +1,8 @@
include ./post.pug
mixin ban(ban, modpage)
mixin ban(ban, banpage)
.ban
if modpage
if !banpage || (ban.appeal == null && ban.allowAppeal === true)
input.post-check(type='checkbox', name='checkedbans[]' value=ban._id)
span
| Banned
@ -18,5 +18,9 @@ mixin ban(ban, modpage)
if ban.post
span Banned for the following post:
section.thread
+post(ban.post, false)
+post(ban.post, false, false, false, true)
if ban.appeal != null
div Submitted appeal:
textarea(disabled='true') #{ban.appeal}
if !ban.allowAppeal
div This ban was issued as non-appealable.

@ -1,4 +1,4 @@
mixin post(post, truncate, manage=false, globalmanage=false)
mixin post(post, truncate, manage=false, globalmanage=false, ban=false)
.anchor(id=post.postId)
article(class=`post-container ${post.thread ? '' : 'op'}`)
- const postURL = `/${post.board}/thread/${post.thread || post.postId}.html`;
@ -6,7 +6,7 @@ mixin post(post, truncate, manage=false, globalmanage=false)
label
if globalmanage
input.post-check(type='checkbox', name='globalcheckedposts[]' value=post._id)
else
else if !ban
input.post-check(type='checkbox', name='checkedposts[]' value=post.postId)
|
if !post.thread
@ -70,15 +70,15 @@ mixin post(post, truncate, manage=false, globalmanage=false)
}
pre.post-message !{truncatedMessage}
if truncatedMessage !== post.message
blockquote.muted Message too long. #[a(href=`${postURL}#${post.postId}`) View the full text]
else
p Message too long. #[a(href=`${postURL}#${post.postId}`) View the full text]
else
pre.post-message !{post.message}
if !post.message && post.files.length === 0
blockquote.muted Post file(s) unlinked
p Post file(s) unlinked
if post.banmessage
blockquote.banmessage USER BANNED FOR THIS POST (#{post.banmessage || 'No reason specified'})
p.banmessage USER BANNED FOR THIS POST (#{post.banmessage || 'No reason specified'})
if post.omittedposts || post.omittedimages
blockquote.muted #{post.omittedposts} post(s)#{post.omittedimages > 0 ? ' and '+post.omittedimages+' image(s)' : ''} omitted. #[a(href=`${postURL}#${post.postId}`) View the full thread]
p #{post.omittedposts} post(s)#{post.omittedimages > 0 ? ' and '+post.omittedimages+' image(s)' : ''} omitted. #[a(href=`${postURL}#${post.postId}`) View the full thread]
if post.previewbacklinks && post.previewbacklinks.length > 0
.replies Replies:
each backlink in post.previewbacklinks

@ -6,11 +6,28 @@ block head
block content
h1.board-title Banned!
.table-container.flex-center.mv-10
table.table-body
tr.table-head
th Bans currently in place against your IP:
for ban in bans
tr.table-row
td
+ban(ban)
form.form-post(action=`/forms/appeal`, enctype='application/x-www-form-urlencoded', method='POST')
.table-container.flex-center.mv-10
table
tr
th Bans currently in place against your IP:
if bans.length > 0
for ban in bans
tr
td
+ban(ban, true)
if allowAppeal === true
tr
td
h4.no-m-p Appeal bans:
section.form-wrapper.flexleft.mt-10
input(type='hidden' name='_csrf' value=csrf)
section.row
.label Message
textarea(name='message' required)
section.row
.label Captcha
span.col
iframe.captcha(src='/captcha.html' width=200 height=110 scrolling='no')
input(type='text', name='captcha', autocomplete='off' placeholder='captcha text' pattern=".{6}" required title='6 characters')
input(type='submit', value='submit')

@ -5,4 +5,4 @@ html
body(style='margin:0;padding:0;background:white;')
img(src='/captcha', style='width:200px;height:80px;margin:0 auto;')
form(action='/forms/newcaptcha', method='POST')
input(style='width: 100%;border-width: 1px 0 0 0;', type='submit', value='New Captcha')
input(style='border-width: 1px 1px 0 0;', type='submit', value='🗘')

@ -58,7 +58,7 @@ block content
hr(size=1)
include ../includes/actionfooter_globalmanage.pug
hr(size=1)
h4.no-m-p Global Bans:
h4.no-m-p Global Bans & Appeals:
form(action=`/forms/global/unban` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)
if bans.length === 0
@ -67,7 +67,7 @@ block content
else
for ban in bans
section.thread
+ban(ban, true)
+ban(ban)
hr(size=1)
section.action-wrapper
input(type='submit', value='unban')

@ -189,7 +189,7 @@ block content
hr(size=1)
include ../includes/actionfooter_manage.pug
hr(size=1)
h4.no-m-p Bans:
h4.no-m-p Bans & Appeals:
form(action=`/forms/board/${board._id}/unban` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)
if bans.length === 0
@ -198,7 +198,7 @@ block content
else
for ban in bans
section.thread
+ban(ban, true)
+ban(ban)
hr(size=1)
section.action-wrapper
input(type='submit', value='unban')

Loading…
Cancel
Save