separate trigger action for when tph vs pph is exceeded

option for lock reset and captcha reset, to pick what you want the lock mode and captcha mod to go back to at the end of the hour
also fix avuln in boardsettings where pph trigger/mode settings were not range checked
jschan
Thomas Lynch 4 years ago
parent 029af471fc
commit fed92d6621
  1. 11
      configs/main.js.example
  2. 25
      controllers/forms/boardsettings.js
  3. 14
      db/boards.js
  4. 3
      helpers/paramconverter.js
  5. 18
      helpers/tasks.js
  6. 1
      migrations/index.js
  7. 25
      migrations/migration-0.0.16.js
  8. 10
      models/forms/changeboardsettings.js
  9. 69
      models/forms/makepost.js
  10. 2
      package.json
  11. 42
      views/pages/managesettings.pug

@ -316,10 +316,13 @@ module.exports = {
unlistedLocal: false, //board hidden from on-site board list and frontpage
unlistedWebring: false, //board hidden from webring
captchaMode: 0, //0=disabled, 1=for threads, 2=for all posts
tphTrigger: 0, //numebr of threads in an hour before trigger action is activated
pphTrigger: 0, //number of posts in an hour before ^
triggerAction: 0, //0=nothing, 1=captcha enable for threads, 2=captcha enable for all posts, 3=lock board
resetTrigger: false, //reset captcha/lock settings back to original at the end of hour
tphTrigger: 10, //numebr of threads in an hour before trigger action is activated
pphTrigger: 50, //number of posts in an hour before ^
//0=none, 1=captcha enable for threads, 2=captcha enable for all posts, 3=lock board
tphTriggerAction: 1,
pphTriggerAction: 2,
captchaReset: 0,
lockReset: 0,
forceAnon: false, //disable name and subject, only allow sage email
sageOnlyEmail: false, //only allow sage email
early404: true, //delete threads beyond the first 1/3 of pages with less than 5 replies

@ -111,12 +111,6 @@ module.exports = async (req, res, next) => {
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 > 4)) {
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');
}
@ -130,6 +124,25 @@ module.exports = async (req, res, next) => {
errors.push('Invalid code theme');
}
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 > 4)) {
errors.push('Invalid tph trigger action');
}
if (typeof req.body.pph_trigger === 'number' && (req.body.pph_trigger < 0 || req.body.pph_trigger > 10000)) {
errors.push('Invalid pph trigger threshold');
}
if (typeof req.body.pph_trigger_action === 'number' && (req.body.pph_trigger_action < 0 || req.body.pph_trigger_action > 4)) {
errors.push('Invalid pph trigger action');
}
if (typeof req.body.lock_reset === 'number' && (req.body.lock_reset < 0 || req.body.lock_reset > 2)) {
errors.push('Invalid trigger reset lock');
}
if (typeof req.body.captcha_reset === 'number' && (req.body.captcha_reset < 0 || req.body.captcha_reset > 2)) {
errors.push('Invalid trigger reset captcha');
}
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',

@ -291,15 +291,11 @@ module.exports = {
}, {
'$project': {
'_id': 1,
'lockMode': {
'new': '$settings.lockMode',
'old': '$preTriggerMode.lockMode'
},
'captchaMode': {
'new': '$settings.captchaMode',
'old': '$preTriggerMode.captchaMode'
},
'threadLimit': '$settings.threadLimit'
'lockMode': '$settings.lockMode',
'lockReset': '$settings.lockReset',
'captchaMode': '$settings.captchaMode',
'captchaReset': '$settings.captchaReset',
'threadLimit': '$settings.threadLimit',
}
}
]).toArray();

@ -5,7 +5,8 @@ const { ObjectId } = require(__dirname+'/../db/db.js')
'checkedreports', 'checkedbans', 'checkedbanners', 'checkedaccounts', 'countries']) //only these should be arrays, since express bodyparser can output arrays
, trimFields = ['tags', 'uri', 'moderators', 'filters', 'announcement', 'description', 'message',
'name', 'subject', 'email', 'postpassword', 'password', 'default_name', 'report_reason', 'ban_reason', 'log_message', 'custom_css'] //trim if we dont want filed with whitespace
, numberFields = ['filter_mode', 'lock_mode', 'message_r9k_mode', 'file_r9k_mode', 'captcha_mode', 'tph_trigger', 'pph_trigger', 'trigger_action', 'bump_limit', 'reply_limit', 'move_to_thread',, 'postId',
, numberFields = ['lock_reset', 'captcha_reset', 'filter_mode', 'lock_mode', 'message_r9k_mode', 'file_r9k_mode', 'captcha_mode',
'tph_trigger', 'pph_trigger', 'pph_trigger_action', 'tph_trigger_action', 'bump_limit', 'reply_limit', 'move_to_thread', 'postId',
'max_files', 'thread_limit', 'thread', 'max_thread_message_length', 'max_reply_message_length', 'min_thread_message_length', 'min_reply_message_length', 'auth_level'] //convert these to numbers before they hit our routes
, banDurationRegex = /^(?<YEAR>[\d]+y)?(?<MONTH>[\d]+mo)?(?<WEEK>[\d]+w)?(?<DAY>[\d]+d)?(?<HOUR>[\d]+h)?(?<MINUTE>[\d]+m)?(?<SECOND>[\d]+s)?$/
, timeUtils = require(__dirname+'/timeutils.js')

@ -236,6 +236,7 @@ module.exports = {
if (triggeredBoards.length === 0) {
return; //no label is no triggers
}
//console.log(triggeredBoards);
await cache.del('triggered');
const triggerModes = await Boards.triggerModes(triggeredBoards);
const bulkWrites = triggerModes.map(p => {
@ -246,22 +247,26 @@ module.exports = {
},
'update': {
'$set': {
'settings.lockMode': p.lockMode.old,
'settings.captchaMode': p.captchaMode.old
/* reset=0 is "no change", the options go from 0-2, and get reset to 0 or 1,
so if >0, we subtract 1 otherwise no change */
'settings.lockMode': (p.lockReset > 0 ? p.lockReset-1 : p.lockMode),
'settings.captchaMode': (p.captchaReset > 0 ? p.captchaReset-1 : p.captchaMode),
}
}
}
}
})
});
//console.log(bulkWrites);
await Boards.db.bulkWrite(bulkWrites);
const promises = [];
triggerModes.forEach(async (p) => {
await cache.del(`board:${p._id}`);
if (p.captchaMode.old < p.captchaMode.new) {
if (p.captchaMode.old === 2) {
//console.log(p, p.captchaReset > 0 && p.captchaReset-1 < p.captchaMode);
if (p.captchaReset > 0 && p.captchaReset-1 < p.captchaMode) {
if (p.captchaReset-1 <= 1) {
promises.push(remove(`${uploadDirectory}/html/${p._id}/thread/`));
}
if (p.captchaMode.old === 0) {
if (p.captchaReset-1 === 0) {
buildQueue.push({
'task': 'buildBoardMultiple',
'options': {
@ -282,7 +287,6 @@ module.exports = {
await Promise.all(promises);
const end = process.hrtime(start);
debugLogs && console.log(timeDiffString(label, end));
},
buildChangePassword: async () => {

@ -16,4 +16,5 @@ module.exports = {
'0.0.13': require(__dirname+'/migration-0.0.13.js'), //add r9k mode (files)
'0.0.14': require(__dirname+'/migration-0.0.14.js'), //add option for disable .onion file posts to board settings
'0.0.15': require(__dirname+'/migration-0.0.15.js'), //messages r9k option
'0.0.16': require(__dirname+'/migration-0.0.16.js'), //separate tph/pph triggers
}

@ -0,0 +1,25 @@
'use strict';
module.exports = async(db, redis) => {
console.log('Allow tph/pph separate triggers and resets');
await db.collection('boards').updateMany({}, {
'$rename': {
'settings.triggerAction' : 'settings.pphTriggerAction',
}
});
await db.collection('boards').updateMany({}, {
'$unset': {
'settings.resetTrigger' : '',
'preTriggerMode': '',
}
});
await db.collection('boards').updateMany({}, {
'$set': {
'settings.tphTriggerAction' : 0,
'settings.captchaReset' : 0,
'settings.lockReset' : 0,
}
});
console.log('Cleared boards cache');
await redis.deletePattern('board:*');
};

@ -93,11 +93,13 @@ module.exports = async (req, res, next) => {
'forceReplyFile': booleanSetting(req.body.force_reply_file),
'forceThreadSubject': booleanSetting(req.body.force_thread_subject),
'disableReplySubject': booleanSetting(req.body.disable_reply_subject),
'resetTrigger': booleanSetting(req.body.reset_trigger),
'captchaMode': numberSetting(req.body.captcha_mode, oldSettings.captchaMode),
'tphTrigger': numberSetting(req.body.tph_trigger, oldSettings.tphTrigger),
'tphTriggerAction': numberSetting(req.body.tph_trigger_action, oldSettings.tphTriggerAction),
'pphTrigger': numberSetting(req.body.pph_trigger, oldSettings.pphTrigger),
'triggerAction': numberSetting(req.body.trigger_action, oldSettings.triggerAction),
'pphTriggerAction': numberSetting(req.body.pph_trigger_action, oldSettings.pphTriggerAction),
'captchaReset': numberSetting(req.body.captcha_reset, oldSettings.captchaReset),
'lockReset': numberSetting(req.body.lock_reset, oldSettings.lockReset),
'threadLimit': numberSetting(req.body.thread_limit, oldSettings.threadLimit),
'replyLimit': numberSetting(req.body.reply_limit, oldSettings.replyLimit),
'bumpLimit': numberSetting(req.body.bump_limit, oldSettings.bumpLimit),
@ -134,10 +136,6 @@ module.exports = async (req, res, next) => {
await Boards.updateOne(req.params.board, {
'$set': {
'settings': newSettings,
'preTriggerMode': {
'lockMode': newSettings.lockMode,
'captchaMode': newSettings.captchaMode
}
}
});

@ -47,9 +47,9 @@ module.exports = async (req, res, next) => {
let redirect = `/${req.params.board}/`
let salt = null;
let thread = null;
const { filterBanDuration, filterMode, filters, blockedCountries, resetTrigger,
const { filterBanDuration, filterMode, filters, blockedCountries, threadLimit, ids, userPostSpoiler,
lockReset, captchaReset, pphTrigger, tphTrigger, tphTriggerAction, pphTriggerAction,
maxFiles, sageOnlyEmail, forceAnon, replyLimit, disableReplySubject,
threadLimit, ids, userPostSpoiler, pphTrigger, tphTrigger, triggerAction,
captchaMode, lockMode, allowedFileTypes, flags, fileR9KMode, messageR9KMode } = res.locals.board.settings;
if (res.locals.permLevel >= 4
&& res.locals.country
@ -463,41 +463,42 @@ module.exports = async (req, res, next) => {
const postId = await Posts.insertOne(res.locals.board, data, thread);
let enableCaptcha = false;
if (triggerAction > 0 //if trigger is enabled
&& (tphTrigger > 0 || pphTrigger > 0) //and has a threshold > 0
&& ((triggerAction < 3 && captchaMode < triggerAction) //and its triggering captcha and captcha isnt on
|| (triggerAction === 3 && lockMode < 1) //or triggering locking and board isnt locked
|| (triggerAction === 4 && lockMode < 2))) {
//read stats to check number threads in past hour
const hourPosts = await Stats.getHourPosts(res.locals.board._id);
if (hourPosts //if stats exist for this hour and its above either trigger
&& (tphTrigger > 0 && hourPosts.tph >= tphTrigger)
|| (pphTrigger > 0 && hourPosts.pph > pphTrigger)) {
//update in memory for other stuff done e.g. rebuilds
const update = {
'$set': {
'preTriggerMode': {
lockMode,
captchaMode
let enableCaptcha = false; //make this returned from some function, refactor and move the next section to another file
const pphTriggerActive = (pphTriggerAction > 0 && pphTrigger > 0);
const tphTriggerActive = (tphTriggerAction > 0 && tphTrigger > 0);
if (pphTriggerAction || tphTriggerActive) { //if a trigger is enabled
const triggerUpdate = {
'$set': {},
};
//and a setting needs to be updated
const pphTriggerUpdate = (pphTriggerAction < 3 && captchaMode < pphTriggerAction)
|| (pphTriggerAction === 3 && lockMode < 1)
|| (pphTriggerAction === 4 && lockMode < 2);
const tphTriggerUpdate = (tphTriggerAction < 3 && captchaMode < tphTriggerAction)
|| (tphTriggerAction === 3 && lockMode < 1)
|| (tphTriggerAction === 4 && lockMode < 2);
if (tphTriggerUpdate || pphTriggerUpdate) {
const hourPosts = await Stats.getHourPosts(res.locals.board._id);
const calcTriggerMode = (update, trigger, triggerAction, stat) => { //todo: move this somewhere else
if (trigger > 0 && stat >= trigger) {
//update in memory for other stuff done e.g. rebuilds
if (triggerAction < 3) {
res.locals.board.settings.captchaMode = triggerAction;
update['$set']['settings.captchaMode'] = triggerAction;
enableCaptcha = true; //todo make this also returned after moving/refactoring this
} else {
res.locals.board.settings.lockMode = triggerAction-2;
update['$set']['settings.lockMode'] = triggerAction-2;
}
return true;
}
};
if (triggerAction < 3) {
res.locals.board.settings.captchaMode = triggerAction;
update['$set']['settings.captchaMode'] = triggerAction;
enableCaptcha = true;
} else if (triggerAction === 3) {
res.locals.board.settings.lockMode = 1;
update['$set']['settings.lockMode'] = 1;
} else if (triggerAction === 4) {
res.locals.board.settings.lockMode = 2;
update['$set']['settings.lockMode'] = 2;
return false;
}
//set it in the db
await Boards.updateOne(res.locals.board._id, update);
if (resetTrigger) {
//mark the board as being triggered so we can return it to old mode after on schedule
const updatedPphTrigger = pphTriggerUpdate && calcTriggerMode(triggerUpdate, pphTrigger, pphTriggerAction, hourPosts.pph);
const updatedTphTrigger = tphTriggerUpdate && calcTriggerMode(triggerUpdate, tphTrigger, tphTriggerAction, hourPosts.tph);
if (updatedPphTrigger || updatedTphTrigger) {
//set it in the db
await Boards.updateOne(res.locals.board._id, triggerUpdate);
await cache.sadd('triggered', res.locals.board._id);
}
}

@ -1,7 +1,7 @@
{
"name": "jschan",
"version": "0.0.1",
"migrateVersion": "0.0.15",
"migrateVersion": "0.0.16",
"description": "",
"main": "server.js",
"dependencies": {

@ -206,24 +206,40 @@ block content
option(value='0', selected=board.settings.captchaMode === 0) No Captcha
option(value='1', selected=board.settings.captchaMode === 1) Captcha for new thread
option(value='2', selected=board.settings.captchaMode === 2) Captcha for all posts
.row
.label TPH Trigger Threshold
input(type='number', name='tph_trigger', value=board.settings.tphTrigger)
.row
.label PPH Trigger Threshold
input(type='number', name='pph_trigger', value=board.settings.pphTrigger)
.row
.label TPH/PPH Trigger Action
select(name='trigger_action')
option(value='0', selected=board.settings.triggerAction === 0) Do nothing
option(value='1', selected=board.settings.triggerAction === 1) Enable captcha for new thread
option(value='2', selected=board.settings.triggerAction === 2) Enable captcha for all posts
option(value='3', selected=board.settings.triggerAction === 3) Lock thread creation
option(value='4', selected=board.settings.triggerAction === 4) Lock board
.label PPH Trigger Action
select(name='pph_trigger_action')
option(value='0', selected=board.settings.pphTriggerAction === 0) Do nothing
option(value='1', selected=board.settings.pphTriggerAction === 1) Enable captcha for new thread
option(value='2', selected=board.settings.pphTriggerAction === 2) Enable captcha for all posts
option(value='3', selected=board.settings.pphTriggerAction === 3) Lock thread creation
option(value='4', selected=board.settings.pphTriggerAction === 4) Lock board
.row
.label Auto Reset Trigger
label.postform-style.ph-5
input(type='checkbox', name='reset_trigger', value='true' checked=board.settings.resetTrigger)
.label TPH Trigger Threshold
input(type='number', name='tph_trigger', value=board.settings.tphTrigger)
.row
.label TPH Trigger Action
select(name='tph_trigger_action')
option(value='0', selected=board.settings.tphTriggerAction === 0) Do nothing
option(value='1', selected=board.settings.tphTriggerAction === 1) Enable captcha for new thread
option(value='2', selected=board.settings.tphTriggerAction === 2) Enable captcha for all posts
option(value='3', selected=board.settings.tphTriggerAction === 3) Lock thread creation
option(value='4', selected=board.settings.tphTriggerAction === 4) Lock board
.row
.label Trigger Reset Lock Mode
select(name='lock_reset')
option(value='0', selected=board.settings.lockReset === 0) No change
option(value='1', selected=board.settings.lockReset === 1) Unlock board
option(value='2', selected=board.settings.lockReset === 2) Lock thread creation
.row
.label Trigger Reset Captcha Mode
select(name='captcha_reset')
option(value='0', selected=board.settings.captchaReset === 0) No change
option(value='1', selected=board.settings.captchaReset === 1) Captcha disbaled
option(value='2', selected=board.settings.captchaReset === 2) Captcha for new thread
.row
.label Early 404
label.postform-style.ph-5

Loading…
Cancel
Save