diff --git a/controllers/forms.js b/controllers/forms.js index 4b8dbdc9..75614499 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -20,7 +20,7 @@ const express = require('express') , useSession = require(__dirname+'/../helpers/usesession.js') , sessionRefresh = require(__dirname+'/../helpers/sessionrefresh.js') , dnsblCheck = require(__dirname+'/../helpers/checks/dnsbl.js') - , blockBypassCheck = require(__dirname+'/../helpers/checks/blockbypass.js') + , blockBypass = require(__dirname+'/../helpers/checks/blockbypass.js') , fileMiddlewares = require(__dirname+'/../helpers/filemiddlewares.js') //controllers , { deleteBoardController, editBansController, appealController, globalActionController, @@ -30,14 +30,14 @@ const express = require('express') resignController, deleteAccountController, loginController, registerController, changePasswordController, deleteAccountsController, editAccountController, globalSettingsController, createBoardController, makePostController, addStaffController, deleteStaffController, editStaffController, editCustomPageController, editPostController, - editRoleController, newCaptcha, blockBypass, logout, deleteSessionsController } = require(__dirname+'/forms/index.js'); + editRoleController, newCaptchaForm, blockBypassForm, logoutForm, deleteSessionsController } = require(__dirname+'/forms/index.js'); //make new post router.post('/board/:board/post', geoAndTor, fileMiddlewares.postsEarly, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, fileMiddlewares.posts, -makePostController.paramConverter, verifyCaptcha, numFiles, blockBypassCheck, dnsblCheck, imageHashes, makePostController.controller); +makePostController.paramConverter, verifyCaptcha, numFiles, blockBypass.middleware, dnsblCheck, imageHashes, makePostController.controller); router.post('/board/:board/modpost', geoAndTor, fileMiddlewares.postsEarly, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, isLoggedIn, -hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), fileMiddlewares.posts, makePostController.paramConverter, csrf, numFiles, blockBypassCheck, dnsblCheck, makePostController.controller); //mod post has token instead of captcha +hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), fileMiddlewares.posts, makePostController.paramConverter, csrf, numFiles, blockBypass.middleware, dnsblCheck, makePostController.controller); //mod post has token instead of captcha //post actions router.post('/board/:board/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, actionController.paramConverter, verifyCaptcha, actionController.controller); //public, with captcha @@ -114,7 +114,7 @@ router.post('/create', geoAndTor, torPreBypassCheck, processIp, useSession, sess //accounts router.post('/login', useSession, loginController.paramConverter, loginController.controller); -router.post('/logout', useSession, logout); +router.post('/logout', useSession, logoutForm); router.post('/register', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, verifyCaptcha, calcPerms, registerController.paramConverter, registerController.controller); router.post('/changepassword', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, verifyCaptcha, changePasswordController.paramConverter, changePasswordController.controller); router.post('/resign', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, resignController.paramConverter, resignController.controller); @@ -122,9 +122,9 @@ router.post('/deleteaccount', useSession, sessionRefresh, csrf, calcPerms, isLog router.post('/deletesessions', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, deleteSessionsController.paramConverter, deleteSessionsController.controller); //removes captcha cookie, for refreshing for noscript users -router.post('/newcaptcha', newCaptcha); +router.post('/newcaptcha', newCaptchaForm); //solve captcha for block bypass -router.post('/blockbypass', geoAndTor, processIp, verifyCaptcha, blockBypass); +router.post('/blockbypass', geoAndTor, processIp, verifyCaptcha, blockBypassForm); module.exports = router; diff --git a/controllers/forms/index.js b/controllers/forms/index.js index acc60ce9..7a03f9bc 100644 --- a/controllers/forms/index.js +++ b/controllers/forms/index.js @@ -39,8 +39,8 @@ module.exports = { editRoleController: require(__dirname+'/editrole.js'), //these dont have a "real" controller - newCaptcha: require(__dirname+'/../../models/forms/newcaptcha.js'), - blockBypass: require(__dirname+'/../../models/forms/blockbypass.js'), - logout: require(__dirname+'/../../models/forms/logout.js'), + newCaptchaForm: require(__dirname+'/../../models/forms/newcaptcha.js'), + blockBypassForm: require(__dirname+'/../../models/forms/blockbypass.js'), + logoutForm: require(__dirname+'/../../models/forms/logout.js'), }; diff --git a/helpers/checks/blockbypass.js b/helpers/checks/blockbypass.js index b3d538f8..3295290e 100644 --- a/helpers/checks/blockbypass.js +++ b/helpers/checks/blockbypass.js @@ -7,73 +7,81 @@ const { Bypass } = require(__dirname+'/../../db/') , dynamicResponse = require(__dirname+'/../dynamic.js') , production = process.env.NODE_ENV === 'production'; -module.exports = async (req, res, next) => { +module.exports = { - const { secureCookies, blockBypass } = config.get; - if (res.locals.preFetchedBypassId //if they already have a bypass - || (!blockBypass.enabled //or if block bypass isnt enabled - && (!blockBypass.forceAnonymizers //and we dont force it for anonymizer - || !res.locals.anonymizer))) { //or they arent on an anonymizer - return next(); - } + check: async (req, res, next) => { + const { secureCookies, blockBypass } = config.get; + + //check if blockbypass exists and right length + const bypassId = req.signedCookies.bypassid; + if (!res.locals.solvedCaptcha && (!bypassId || bypassId.length !== 24)) { + deleteTempFiles(req).catch(e => console.error); + return dynamicResponse(req, res, 403, 'message', { + 'title': 'Forbidden', + 'message': 'Please complete a block bypass to continue', + 'frame': '/bypass_minimal.html', + 'link': { + 'href': '/bypass.html', + 'text': 'Get block bypass', + }, + }); + } + + //try to get bypass from db and make sure uses < maxUses + let bypass; + if (bypassId && bypassId.length === 24) { + try { + const bypassMongoId = ObjectId(bypassId); + bypass = await Bypass.checkBypass(bypassMongoId, res.locals.anonymizer); + res.locals.blockBypass = true; + } catch (err) { + return next(err); + } + } + + if (bypass //if they have a valid bypass + && (bypass.uses < blockBypass.expireAfterUses //and its not overused + || (res.locals.anonymizer + && !blockBypass.forceAnonymizers))) { //OR its not forced for anonymizers + return next(); + } + + if (res.locals.solvedCaptcha) { + //they dont have a valid bypass, but just solved board captcha, so give them a new one + const newBypass = await Bypass.getBypass(res.locals.anonymizer); + const newBypassId = newBypass.insertedId; + res.locals.blockBypass = true; + res.cookie('bypassid', newBypassId.toString(), { + 'maxAge': blockBypass.expireAfterTime, + 'secure': production && secureCookies && (req.headers['x-forwarded-proto'] === 'https'), + 'sameSite': 'strict', + 'signed': true + }); + return next(); + } - //check if blockbypass exists and right length - const bypassId = req.signedCookies.bypassid; - if (!res.locals.solvedCaptcha && (!bypassId || bypassId.length !== 24)) { deleteTempFiles(req).catch(e => console.error); return dynamicResponse(req, res, 403, 'message', { 'title': 'Forbidden', - 'message': 'Please complete a block bypass to continue', + 'message': 'Block bypass expired or exceeded max uses', 'frame': '/bypass_minimal.html', 'link': { 'href': '/bypass.html', 'text': 'Get block bypass', }, }); - } - - //try to get bypass from db and make sure uses < maxUses - let bypass; - if (bypassId && bypassId.length === 24) { - try { - const bypassMongoId = ObjectId(bypassId); - bypass = await Bypass.checkBypass(bypassMongoId, res.locals.anonymizer); - res.locals.blockBypass = true; - } catch (err) { - return next(err); - } - } - if (bypass //if they have a valid bypass - && (bypass.uses < blockBypass.expireAfterUses //and its not overused - || (res.locals.anonymizer - && !blockBypass.forceAnonymizers))) { //OR its not forced for anonymizers - return next(); - } + }, - if (res.locals.solvedCaptcha) { - //they dont have a valid bypass, but just solved board captcha, so give them a new one - const newBypass = await Bypass.getBypass(res.locals.anonymizer); - const newBypassId = newBypass.insertedId; - res.locals.blockBypass = true; - res.cookie('bypassid', newBypassId.toString(), { - 'maxAge': blockBypass.expireAfterTime, - 'secure': production && secureCookies && (req.headers['x-forwarded-proto'] === 'https'), - 'sameSite': 'strict', - 'signed': true - }); - return next(); - } - - deleteTempFiles(req).catch(e => console.error); - return dynamicResponse(req, res, 403, 'message', { - 'title': 'Forbidden', - 'message': 'Block bypass expired or exceeded max uses', - 'frame': '/bypass_minimal.html', - 'link': { - 'href': '/bypass.html', - 'text': 'Get block bypass', - }, - }); + middleware: async (req, res, next) => { + const { blockBypass } = config.get; + if (res.locals.preFetchedBypassId //if they already have a bypass + || (!blockBypass.enabled //or if block bypass isnt enabled + && (!blockBypass.forceAnonymizers //and we dont force it for anonymizer + || !res.locals.anonymizer))) { //or they arent on an anonymizer + return next(); + } + return module.exports.check(req, res, next); + }, } diff --git a/helpers/checks/dnsbl.js b/helpers/checks/dnsbl.js index 503464e2..33f7fb15 100644 --- a/helpers/checks/dnsbl.js +++ b/helpers/checks/dnsbl.js @@ -1,6 +1,7 @@ 'use strict'; const cache = require(__dirname+'/../../redis.js') + , { check: blockBypassCheck } = require(__dirname+'/blockbypass.js') , dynamicResponse = require(__dirname+'/../dynamic.js') , deleteTempFiles = require(__dirname+'/../files/deletetempfiles.js') , config = require(__dirname+'/../../config.js') @@ -9,9 +10,16 @@ const cache = require(__dirname+'/../../redis.js') module.exports = async (req, res, next) => { const { ipHeader, dnsbl, blockBypass } = config.get; + if (dnsbl.enabled && dnsbl.blacklists.length > 0 //if dnsbl enabled and has more than 0 blacklists - && !res.locals.anonymizer //anonymizers cant be dnsbl'd - && (!res.locals.blockBypass || !blockBypass.bypassDnsbl)) { //and there is no valid block bypass, or they do not bypass dnsbl + && !res.locals.anonymizer) { //anonymizers cant be dnsbl'd + if (blockBypass.bypassDnsbl) { + if (!res.locals.blockBypass) { + return blockBypassCheck(req, res, next); + } + return next(); //already solved + } + //otherwise, bad block bypass or dnsbl cant be bypassed const ip = req.headers[ipHeader] || req.connection.remoteAddress; let isBlacklisted = await cache.get(`blacklisted:${ip}`); if (isBlacklisted === null) { //not cached @@ -23,7 +31,7 @@ module.exports = async (req, res, next) => { deleteTempFiles(req).catch(e => console.error); return dynamicResponse(req, res, 403, 'message', { 'title': 'Forbidden', - 'message': `Your request was blocked because your IP address is listed on a blacklist.${blockBypass.bypassDnsbl ? ' You can solve a "block bypass" to temporarily circumvent blacklisting.' : ''}`, + 'message': `Your request was blocked because your IP address is listed on a blacklist.`, 'redirect': req.headers.referer || '/', 'link': blockBypass.bypassDnsbl ? { text: 'Solve block bypass', href: '/bypass.html' } : null, });