From 84971cd274633c54d132b57e4bd0b69dc2ec6f89 Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 9 Feb 2020 20:26:55 +1100 Subject: [PATCH] start work on blockbypass --- configs/main.js.example | 8 ++++++ controllers/forms.js | 24 +++++++++------- controllers/pages.js | 3 +- db/bypass.js | 35 +++++++++++++++++++++++ db/index.js | 1 + gulp/res/js/modal.js | 15 ++++++---- gulpfile.js | 13 +++++---- helpers/checks/blockbypass.js | 52 +++++++++++++++++++++++++++++++++++ helpers/checks/dnsbl.js | 5 ++-- helpers/tasks.js | 4 +++ models/forms/blockbypass.js | 22 +++++++++++++++ models/pages/blockbypass.js | 16 +++++++++++ models/pages/index.js | 1 + views/mixins/modal.pug | 10 +++++-- views/pages/bypass.pug | 15 ++++++++++ 15 files changed, 197 insertions(+), 27 deletions(-) create mode 100644 db/bypass.js create mode 100644 helpers/checks/blockbypass.js create mode 100644 models/forms/blockbypass.js create mode 100644 models/pages/blockbypass.js create mode 100644 views/pages/bypass.pug diff --git a/configs/main.js.example b/configs/main.js.example index 41af29b7..245e834c 100644 --- a/configs/main.js.example +++ b/configs/main.js.example @@ -50,6 +50,14 @@ module.exports = { cacheTime: 3600 //in seconds, idk whats a good value }, + //block bypasses + blockBypass: { + enabled: false, + expireAfterUses: 40, //however many (attempted) posts per block bypass captcha + expireAfterTime: 86400000, //expiry in ms regardless if the limit was reached, default 1 day + bypassDnsbl: false, + }, + //cache templates in memory. disable only if editing templates and doing dev work cacheTemplates: true, diff --git a/controllers/forms.js b/controllers/forms.js index fe1cecee..7fb8c7cf 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -15,6 +15,7 @@ const express = require('express') , csrf = require(__dirname+'/../helpers/checks/csrfmiddleware.js') , sessionRefresh = require(__dirname+'/../helpers/sessionrefresh.js') , dnsblCheck = require(__dirname+'/../helpers/checks/dnsbl.js') + , blockBypassCheck = require(__dirname+'/../helpers/checks/blockbypass.js') , dynamicResponse = require(__dirname+'/../helpers/dynamic.js') , uploadLimitFunction = (req, res, next) => { return dynamicResponse(req, res, 413, 'message', { @@ -71,16 +72,21 @@ const express = require('express') , globalSettingsController = require(__dirname+'/forms/globalsettings.js') , createBoardController = require(__dirname+'/forms/create.js') , makePostController = require(__dirname+'/forms/makepost.js') - , newcaptcha = require(__dirname+'/../models/forms/newcaptcha.js') + , newCaptcha = require(__dirname+'/../models/forms/newcaptcha.js') + , blockBypass = require(__dirname+'/../models/forms/blockbypass.js') //make new post -router.post('/board/:board/post', dnsblCheck, sessionRefresh, Boards.exists, calcPerms, banCheck, postFiles, paramConverter, verifyCaptcha, numFiles, makePostController); -//router.post('/board/:board/modpost', dnsblCheck, sessionRefresh, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), postFiles, paramConverter, csrf, numFiles, makePostController); //mod post has token instead of captcha +router.post('/board/:board/post', sessionRefresh, Boards.exists, calcPerms, banCheck, postFiles, + paramConverter, verifyCaptcha, numFiles, blockBypassCheck, dnsblCheck, makePostController); +/*router.post('/board/:board/modpost', sessionRefresh, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), postFiles, + paramConverter, csrf, numFiles, blockBypassCheck, dnsblCheck, makePostController); */ //mod post has token instead of captcha //post actions router.post('/board/:board/actions', sessionRefresh, Boards.exists, calcPerms, banCheck, paramConverter, verifyCaptcha, actionController); //public, with captcha router.post('/board/:board/modactions', sessionRefresh, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), paramConverter, actionController); //board manage page router.post('/global/actions', sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(1), paramConverter, globalActionController); //global manage page +//appeal ban +router.post('/appeal', sessionRefresh, paramConverter, verifyCaptcha, appealController); //board management forms router.post('/board/:board/transfer', sessionRefresh, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(2), paramConverter, transferController); @@ -97,19 +103,17 @@ router.post('/global/deletenews', sessionRefresh, csrf, calcPerms, isLoggedIn, h router.post('/global/editaccounts', sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), paramConverter, editAccountsController); //account editing router.post('/global/settings', sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), paramConverter, globalSettingsController); //global settings +//create board +router.post('/create', sessionRefresh, isLoggedIn, verifyCaptcha, calcPerms, hasPerms(4), createBoardController); //accounts router.post('/login', loginController); router.post('/register', verifyCaptcha, registerController); router.post('/changepassword', verifyCaptcha, changePasswordController); -//appeal ban -router.post('/appeal', sessionRefresh, paramConverter, verifyCaptcha, appealController); - -//create board -router.post('/create', sessionRefresh, isLoggedIn, verifyCaptcha, calcPerms, hasPerms(4), createBoardController); - //removes captcha cookie, for refreshing for noscript users -router.post('/newcaptcha', newcaptcha); +router.post('/newcaptcha', newCaptcha); +//solve captcha for block bypass +router.post('/blockbypass', verifyCaptcha, blockBypass); module.exports = router; diff --git a/controllers/pages.js b/controllers/pages.js index 4e980cc2..437c4446 100644 --- a/controllers/pages.js +++ b/controllers/pages.js @@ -15,7 +15,7 @@ const express = require('express') , { manageReports, manageBanners, manageSettings, manageBans } = require(__dirname+'/../models/pages/manage/') , { globalManageSettings, globalManageReports, globalManageBans, globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs } = require(__dirname+'/../models/pages/globalmanage/') - , { changePassword, home, register, login, logout, create, + , { changePassword, blockBypass, home, register, login, logout, create, board, catalog, banners, randombanner, news, captchaPage, captcha, thread, modlog, modloglist, account, boardlist } = require(__dirname+'/../models/pages/'); @@ -61,6 +61,7 @@ router.get('/globalmanage/settings.html', sessionRefresh, isLoggedIn, calcPerms, //captcha router.get('/captcha', captcha); //get captcha image and cookie router.get('/captcha.html', captchaPage); //iframed for noscript users +router.get('/bypass.html', blockBypass); //block bypass page //accounts router.get('/account.html', sessionRefresh, isLoggedIn, account); //page showing boards you are mod/owner of, links to password rese, logout, etc diff --git a/db/bypass.js b/db/bypass.js new file mode 100644 index 00000000..8d83f0b0 --- /dev/null +++ b/db/bypass.js @@ -0,0 +1,35 @@ +'use strict'; + +const Mongo = require(__dirname+'/db.js') + , { blockBypass } = require(__dirname+'/../configs/main.js') + , db = Mongo.client.db('jschan').collection('bypass'); + +module.exports = { + + db, + + checkBypass: (id) => { + return db.findOneAndUpdate({ + '_id': id, + 'uses': { + '$lte': blockBypass.expireAfterUses + } + }, { + '$inc': { + 'uses': 1, + } + }).then(r => r.value); + }, + + getBypass: () => { + return db.insertOne({ + 'uses': 0, + 'expireAt': new Date(Date.now() + blockBypass.expireAfterTime) + }).then(r => { return r.insertedId }); + }, + + deleteAll: () => { + return db.deleteMany({}); + }, + +} diff --git a/db/index.js b/db/index.js index e42ddb3f..a240d9d5 100644 --- a/db/index.js +++ b/db/index.js @@ -13,5 +13,6 @@ module.exports = { News: require(__dirname+'/news.js'), Ratelimits: require(__dirname+'/ratelimits.js'), Modlogs: require(__dirname+'/modlogs.js'), + Bypass: require(__dirname+'/bypass.js'), } diff --git a/gulp/res/js/modal.js b/gulp/res/js/modal.js index dec165a5..5282c303 100644 --- a/gulp/res/js/modal.js +++ b/gulp/res/js/modal.js @@ -4,9 +4,9 @@ var pug_has_own_property=Object.prototype.hasOwnProperty; var pug_match_html=/["&<>]/; function pug_style(r){if(!r)return"";if("object"==typeof r){var t="";for(var e in r)pug_has_own_property.call(r,e)&&(t=t+e+":"+r[e]+";");return t}return r+""}function modal(locals) {var pug_html = "", pug_mixins = {}, pug_interp;;var locals_for_with = (locals || {});(function (modal) {pug_mixins["modal"] = pug_interp = function(data){ var block = (this && this.block), attributes = (this && this.attributes) || {}; -pug_html = pug_html + "\u003Cdiv" + (" class=\"modal-bg\""+pug_attr("style", pug_style(data.hidden?'display:none':''), true, false)) + "\u003E\u003C\u002Fdiv\u003E\u003Cdiv" + (" class=\"modal\""+pug_attr("style", pug_style(data.hidden?'display:none':''), true, false)) + "\u003E\u003Cdiv class=\"row\"\u003E\u003Cp class=\"bold\"\u003E" + (pug_escape(null == (pug_interp = data.title) ? "" : pug_interp)) + "\u003C\u002Fp\u003E\u003Ca class=\"close postform-style\" id=\"modalclose\"\u003EX\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E"; +pug_html = pug_html + "\u003Cdiv" + (" class=\"modal-bg\""+pug_attr("style", pug_style(data.hidden?'display:none':''), true, false)) + "\u003E\u003C\u002Fdiv\u003E\u003Cdiv" + (" class=\"modal\""+pug_attr("style", pug_style(data.hidden?'display:none':''), true, false)) + "\u003E\u003Cdiv class=\"row\"\u003E\u003Cp class=\"bold\"\u003E" + (pug_escape(null == (pug_interp = data.title) ? "" : pug_interp)) + "\u003C\u002Fp\u003E\u003Ca class=\"close postform-style\" id=\"modalclose\"\u003EX\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E"; if (data.message || data.messages || data.error || data.errors) { -pug_html = pug_html + "\u003Cul class=\"nomarks\"\u003E"; +pug_html = pug_html + "\u003Cdiv class=\"row\"\u003E\u003Cul class=\"nomarks\"\u003E"; if (data.message) { pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = data.message) ? "" : pug_interp)) + "\u003C\u002Fli\u003E"; } @@ -53,11 +53,14 @@ pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = error }).call(this); } -pug_html = pug_html + "\u003C\u002Ful\u003E"; +pug_html = pug_html + "\u003C\u002Ful\u003E\u003C\u002Fdiv\u003E"; +if (data.link) { +pug_html = pug_html + "\u003Cdiv class=\"row\"\u003E\u003Ca" + (pug_attr("href", data.link, true, false)+" target=\"_blank\"") + "\u003E" + (pug_escape(null == (pug_interp = data.link) ? "" : pug_interp)) + "\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E"; +} } else if (data.settings) { -pug_html = pug_html + "\u003Cdiv class=\"form-wrapper flexleft mt-10\"\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ETheme\u003C\u002Fdiv\u003E\u003Cselect id=\"theme-setting\"\u003E\u003Coption value=\"default\"\u003Edefault\u003C\u002Foption\u003E"; +pug_html = pug_html + "\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"form-wrapper flexleft mt-10\"\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ETheme\u003C\u002Fdiv\u003E\u003Cselect id=\"theme-setting\"\u003E\u003Coption value=\"default\"\u003Edefault\u003C\u002Foption\u003E"; // iterate data.settings.themes ;(function(){ var $$obj = data.settings.themes; @@ -95,8 +98,8 @@ pug_html = pug_html + "\u003Coption" + (pug_attr("value", theme, true, false)) + } }).call(this); -pug_html = pug_html + "\u003C\u002Fselect\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ELive posts\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"live-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ENotifications\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"notification-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EScroll to new posts\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"scroll-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ELocal time\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"localtime-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003E24h time\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"24hour-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EShow relative time\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"relative-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EHide Thumbnails\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"hideimages-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ERecursive Post Hide\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"hiderecursive-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EVideo\u002FAudio Volume\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"volume-setting\" type=\"range\" min=\"0\" max=\"100\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ELoop audio\u002Fvideo\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"loop-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EUnlimit expand height\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"heightlimit-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ECrisp image rendering\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"crispimages-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EPost Password\u003C\u002Fdiv\u003E\u003Cinput id=\"postpassword-setting\" type=\"password\" name=\"postpassword\"\u002F\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ECustom CSS\u003C\u002Fdiv\u003E\u003Ctextarea id=\"customcss-setting\"\u003E\u003C\u002Ftextarea\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E"; +pug_html = pug_html + "\u003C\u002Fselect\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ELive posts\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"live-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ENotifications\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"notification-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EScroll to new posts\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"scroll-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ELocal time\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"localtime-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003E24h time\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"24hour-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EShow relative time\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"relative-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EHide Thumbnails\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"hideimages-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ERecursive Post Hide\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"hiderecursive-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EVideo\u002FAudio Volume\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"volume-setting\" type=\"range\" min=\"0\" max=\"100\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ELoop audio\u002Fvideo\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"loop-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EUnlimit expand height\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"heightlimit-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ECrisp image rendering\u003C\u002Fdiv\u003E\u003Clabel class=\"postform-style ph-5\"\u003E\u003Cinput id=\"crispimages-setting\" type=\"checkbox\"\u002F\u003E\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003EPost Password\u003C\u002Fdiv\u003E\u003Cinput id=\"postpassword-setting\" type=\"password\" name=\"postpassword\"\u002F\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cdiv class=\"label\"\u003ECustom CSS\u003C\u002Fdiv\u003E\u003Ctextarea id=\"customcss-setting\"\u003E\u003C\u002Ftextarea\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E"; } -pug_html = pug_html + "\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E"; +pug_html = pug_html + "\u003C\u002Fdiv\u003E"; }; pug_mixins["modal"](modal);}.call(this,"modal" in locals_for_with?locals_for_with.modal:typeof modal!=="undefined"?modal:undefined));;return pug_html;} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 385203d4..607a0a3d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -50,8 +50,9 @@ async function wipe() { await db.createCollection('poststats'); await db.createCollection('ratelimit'); await db.createCollection('webring'); + await db.createCollection('bypass'); - const { Webring, Boards, Posts, Captchas, Ratelimits, Accounts, Files, Stats, Modlogs, Bans } = require(__dirname+'/db/'); + const { Webring, Boards, Posts, Captchas, Ratelimits, Accounts, Files, Stats, Modlogs, Bans, Bypass } = require(__dirname+'/db/'); //wipe db shit await Promise.all([ @@ -65,7 +66,8 @@ async function wipe() { Bans.deleteAll(), Files.deleteAll(), Stats.deleteAll(), - Modlogs.deleteAll() + Modlogs.deleteAll(), + Bypass.deleteAll(), ]); //add indexes - should profiled and changed at some point if necessary @@ -84,9 +86,10 @@ async function wipe() { await Modlogs.db.createIndex({ 'board': 1 }) await Files.db.createIndex({ 'count': 1 }) await Bans.db.createIndex({ 'ip': 1 , 'board': 1 }) - await Bans.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 }) //custom expiry, i.e. it will expire when current date > than this date - await Captchas.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 300 }) //captchas valid for 5 minutes - await Ratelimits.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 60 }) //per minute captcha ratelimit + await Bans.db.createIndex({ 'expireAt': 1 }, { expireAfterSeconds: 0 }) //custom expiry, i.e. it will expire when current date > than this date + await Bypass.db.createIndex({ 'expireAt': 1 }, { expireAfterSeconds: 0 }) + await Captchas.db.createIndex({ 'expireAt': 1 }, { expireAfterSeconds: 300 }) //captchas valid for 5 minutes + await Ratelimits.db.createIndex({ 'expireAt': 1 }, { expireAfterSeconds: 60 }) //per minute captcha ratelimit await Posts.db.createIndex({ 'postId': 1,'board': 1,}) await Posts.db.createIndex({ 'board': 1, 'thread': 1, 'bumped': -1 }) await Posts.db.createIndex({ 'board': 1, 'reports.0': 1 }, { 'partialFilterExpression': { 'reports.0': { '$exists': true } } }) diff --git a/helpers/checks/blockbypass.js b/helpers/checks/blockbypass.js new file mode 100644 index 00000000..4372c83c --- /dev/null +++ b/helpers/checks/blockbypass.js @@ -0,0 +1,52 @@ +'use strict'; + +const { Bypass } = require(__dirname+'/../../db/') + , { ObjectId } = require(__dirname+'/../../db/db.js') + , { blockBypass } = require(__dirname+'/../../configs/main.js') + , dynamicResponse = require(__dirname+'/../dynamic.js'); + +module.exports = async (req, res, next) => { + + if (!blockBypass.enabled) { + return next(); + } + + //check if blockbypass exists and right length + const bypassId = req.cookies.bypassid; + if (!bypassId || bypassId.length !== 24) { + return dynamicResponse(req, res, 403, 'message', { + 'title': 'Forbidden', + 'message': 'Missing or invalid block bypass', + 'redirect': '/bypass.html', + 'link': '/bypass.html', + }); + } + + //try to get bypass from db and make sure uses < maxUses + let bypass; + try { + const bypassMongoId = ObjectId(bypassId); + bypass = await Bypass.checkBypass(bypassMongoId); + } catch (err) { + return next(err); + } + + if (!bypass) { + return dynamicResponse(req, res, 403, 'message', { + 'title': 'Forbidden', + 'message': 'Invalid or expired block bypass', + 'redirect': '/bypass.html', + 'link': '/bypass.html', + }); + } else if (bypass.uses >= blockBypass.expireAfterUses) { + return dynamicResponse(req, res, 403, 'message', { + 'title': 'Forbidden', + 'message': 'Block bypass exceeded max uses', + 'redirect': '/bypass.html', + 'link': '/bypass.html', + }); + } + + return next(); + +} diff --git a/helpers/checks/dnsbl.js b/helpers/checks/dnsbl.js index 9fa5c987..9d09fc73 100644 --- a/helpers/checks/dnsbl.js +++ b/helpers/checks/dnsbl.js @@ -2,12 +2,13 @@ const cache = require(__dirname+'/../../redis.js') , dynamicResponse = require(__dirname+'/../dynamic.js') - , { dnsbl } = require(__dirname+'/../../configs/main.js') + , { dnsbl, blockBypass } = require(__dirname+'/../../configs/main.js') , { batch } = require('dnsbl'); module.exports = async (req, res, next) => { - if (dnsbl.enabled && dnsbl.blacklists.length > 0) { + if (dnsbl.enabled && dnsbl.blacklists.length > 0 //if dnsbl enabled and has more than 0 blacklists + && (!res.locals.bypass || !blockBypass.bypassDnsbl)) { //and there is no valid block bypass, or they do not bypass dnsbl const ip = req.headers['x-real-ip'] || req.connection.remoteAddress; let isBlacklisted = await cache.get(`blacklisted:${ip}`); if (isBlacklisted === null) { //not cached diff --git a/helpers/tasks.js b/helpers/tasks.js index 61690695..2b3b7851 100644 --- a/helpers/tasks.js +++ b/helpers/tasks.js @@ -226,6 +226,10 @@ module.exports = { return render('register.html', 'register.pug'); }, + buildBypass: () => { + return render('bypass.html', 'bypass.pug'); + }, + buildCreate: () => { return render('create.html', 'create.pug'); }, diff --git a/models/forms/blockbypass.js b/models/forms/blockbypass.js new file mode 100644 index 00000000..e0840006 --- /dev/null +++ b/models/forms/blockbypass.js @@ -0,0 +1,22 @@ +'use strict'; + +const { Bypass } = require(__dirname+'/../../db/') + , { secureCookies, blockBypass } = require(__dirname+'/../../configs/main.js') + , production = process.env.NODE_ENV === 'production'; + +module.exports = async (req, res, next) => { + + const bypassId = await Bypass.getBypass(); + + return res + .cookie('bypassid', bypassId.toString(), { + 'maxAge': blockBypass.expireAfterTime, + 'secure': production && secureCookies, + 'sameSite': 'strict' + }) + .render('message', { + 'title': 'Success', + 'message': 'Completed block bypass, you may go back and make your post.', + }); + +} diff --git a/models/pages/blockbypass.js b/models/pages/blockbypass.js new file mode 100644 index 00000000..ed456d8b --- /dev/null +++ b/models/pages/blockbypass.js @@ -0,0 +1,16 @@ +'use strict'; + +const { buildBypass } = require(__dirname+'/../../helpers/tasks.js'); + +module.exports = async (req, res, next) => { + + let html; + try { + html = await buildBypass(); + } catch (err) { + return next(err); + } + + return res.send(html); + +} diff --git a/models/pages/index.js b/models/pages/index.js index 01894e14..eb7ee1d3 100644 --- a/models/pages/index.js +++ b/models/pages/index.js @@ -2,6 +2,7 @@ module.exports = { changePassword: require(__dirname+'/changepassword.js'), + blockBypass: require(__dirname+'/blockbypass.js'), register: require(__dirname+'/register.js'), account: require(__dirname+'/account.js'), home: require(__dirname+'/home.js'), diff --git a/views/mixins/modal.pug b/views/mixins/modal.pug index bdc06bdc..c558247d 100644 --- a/views/mixins/modal.pug +++ b/views/mixins/modal.pug @@ -4,8 +4,8 @@ mixin modal(data) .row p.bold #{data.title} a.close.postform-style#modalclose X - .row - if data.message || data.messages || data.error || data.errors + if data.message || data.messages || data.error || data.errors + .row ul.nomarks if data.message li #{data.message} @@ -17,7 +17,11 @@ mixin modal(data) if data.errors each error in data.errors li #{error} - else if data.settings + if data.link + .row + a(href=data.link target='_blank') #{data.link} + else if data.settings + .row .form-wrapper.flexleft.mt-10 .row .label Theme diff --git a/views/pages/bypass.pug b/views/pages/bypass.pug new file mode 100644 index 00000000..c856b8ba --- /dev/null +++ b/views/pages/bypass.pug @@ -0,0 +1,15 @@ +extends ../layout.pug + +block head + script(src='/js/all.js') + title Block Bypass + +block content + h1.board-title Block Bypass + .form-wrapper.flex-center.mv-10 + form.form-post(action='/forms/blockbypass' method='POST') + .row + .label Captcha + span.col + include ../includes/captcha.pug + input(type='submit', value='Submit')