From fed92d6621bbcbdddd77f36e4c5ef1a48cfa99e0 Mon Sep 17 00:00:00 2001 From: Thomas Lynch Date: Sat, 7 Nov 2020 07:22:23 +0000 Subject: [PATCH] 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 --- configs/main.js.example | 11 +++-- controllers/forms/boardsettings.js | 25 ++++++++--- db/boards.js | 14 +++--- helpers/paramconverter.js | 3 +- helpers/tasks.js | 18 +++++--- migrations/index.js | 1 + migrations/migration-0.0.16.js | 25 +++++++++++ models/forms/changeboardsettings.js | 10 ++--- models/forms/makepost.js | 69 +++++++++++++++-------------- package.json | 2 +- views/pages/managesettings.pug | 42 ++++++++++++------ 11 files changed, 139 insertions(+), 81 deletions(-) create mode 100644 migrations/migration-0.0.16.js diff --git a/configs/main.js.example b/configs/main.js.example index 225db252..b8d43514 100644 --- a/configs/main.js.example +++ b/configs/main.js.example @@ -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 diff --git a/controllers/forms/boardsettings.js b/controllers/forms/boardsettings.js index a92e403a..3c9b0f3b 100644 --- a/controllers/forms/boardsettings.js +++ b/controllers/forms/boardsettings.js @@ -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', diff --git a/db/boards.js b/db/boards.js index 0505182f..eaa0ca51 100644 --- a/db/boards.js +++ b/db/boards.js @@ -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(); diff --git a/helpers/paramconverter.js b/helpers/paramconverter.js index 0980bcdb..2ec08b88 100644 --- a/helpers/paramconverter.js +++ b/helpers/paramconverter.js @@ -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 = /^(?[\d]+y)?(?[\d]+mo)?(?[\d]+w)?(?[\d]+d)?(?[\d]+h)?(?[\d]+m)?(?[\d]+s)?$/ , timeUtils = require(__dirname+'/timeutils.js') diff --git a/helpers/tasks.js b/helpers/tasks.js index ef150fa9..b2d03825 100644 --- a/helpers/tasks.js +++ b/helpers/tasks.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 () => { diff --git a/migrations/index.js b/migrations/index.js index 5f41c8cb..4e052b4e 100644 --- a/migrations/index.js +++ b/migrations/index.js @@ -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 } diff --git a/migrations/migration-0.0.16.js b/migrations/migration-0.0.16.js new file mode 100644 index 00000000..e61b00ba --- /dev/null +++ b/migrations/migration-0.0.16.js @@ -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:*'); +}; diff --git a/models/forms/changeboardsettings.js b/models/forms/changeboardsettings.js index cac5b3ce..a7e57b74 100644 --- a/models/forms/changeboardsettings.js +++ b/models/forms/changeboardsettings.js @@ -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 - } } }); diff --git a/models/forms/makepost.js b/models/forms/makepost.js index ad5805de..b2cbdfb6 100644 --- a/models/forms/makepost.js +++ b/models/forms/makepost.js @@ -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); } } diff --git a/package.json b/package.json index 5a87e774..4ed1c71e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jschan", "version": "0.0.1", - "migrateVersion": "0.0.15", + "migrateVersion": "0.0.16", "description": "", "main": "server.js", "dependencies": { diff --git a/views/pages/managesettings.pug b/views/pages/managesettings.pug index f246bf39..0904c2ec 100644 --- a/views/pages/managesettings.pug +++ b/views/pages/managesettings.pug @@ -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