small refactor, make captcha check separate. should be eaasier to add different captchas now

fix conditions for when to render bypass vs message page on failed captchas
use crypto timingsafeequal for comparing input to answer
merge-requests/208/head
Thomas Lynch 4 years ago
parent 6d7c8d5989
commit 28fdb8af81
  1. 45
      helpers/captcha/captchaverify.js
  2. 31
      helpers/checks/captcha.js
  3. 36
      helpers/checks/torprebypass.js
  4. 4
      helpers/dynamic.js
  5. 2
      models/forms/blockbypass.js

@ -1,7 +1,8 @@
'use strict';
const { Captchas, Ratelimits } = require(__dirname+'/../../db/')
const { Ratelimits } = require(__dirname+'/../../db/')
, { ObjectId } = require(__dirname+'/../../db/db.js')
, checkCaptcha = require(__dirname+'/../checks/captcha.js')
, remove = require('fs-extra').remove
, dynamicResponse = require(__dirname+'/../dynamic.js')
, deleteTempFiles = require(__dirname+'/../files/deletetempfiles.js')
@ -23,43 +24,17 @@ module.exports = async (req, res, next) => {
}
}
//check if captcha field in form is valid
const input = req.body.captcha;
if (!input || input.length !== 6) {
deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Incorrect captcha answer',
'redirect': req.headers.referer,
});
}
//make sure they have captcha cookie and its 24 chars
const captchaId = req.cookies.captchaid;
if (!captchaId || captchaId.length !== 24) {
deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Captcha expired',
'redirect': req.headers.referer,
});
}
// try to get the captcha from the DB
let captcha;
try {
const captchaMongoId = ObjectId(captchaId);
captcha = await Captchas.findOneAndDelete(captchaMongoId, input);
await checkCaptcha(req.body.captcha, req.cookies.captchaid);
} catch (err) {
return next(err);
}
//check that it exists and matches captcha in DB
if (!captcha || !captcha.value || captcha.value.text !== input) {
deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 403, 'message', {
if (err instanceof Error) {
return next(err);
}
const page = (req.body.minimal || req.path === '/blockbypass' ? 'bypass' : 'message');
return dynamicResponse(req, res, 403, page, {
'title': 'Forbidden',
'message': 'Incorrect captcha answer',
'message': err,
'redirect': req.headers.referer,
});
}
@ -69,7 +44,7 @@ module.exports = async (req, res, next) => {
res.clearCookie('captchaid');
await Promise.all([
!res.locals.tor && Ratelimits.resetQuota(res.locals.ip.single, 'captcha'),
remove(`${uploadDirectory}/captcha/${captchaId}.jpg`)
remove(`${uploadDirectory}/captcha/${req.cookies.captchaid}.jpg`)
]);
return next();

@ -0,0 +1,31 @@
'use strict';
const { Captchas } = require(__dirname+'/../../db/')
, { ObjectId } = require(__dirname+'/../../db/db.js')
, { timingSafeEqual } = require('crypto')
module.exports = async (captchaInput, captchaId) => {
//check if captcha field in form is valid
if (!captchaInput || captchaInput.length !== 6) {
throw 'Incorrect captcha answer';
}
//make sure they have captcha cookie and its 24 chars
if (!captchaId || captchaId.length !== 24) {
throw 'Captcha expired';
}
// try to get the captcha from the DB
const captchaMongoId = ObjectId(captchaId);
let captcha = await Captchas.findOneAndDelete(captchaMongoId, captchaInput);
//check that it exists and matches captcha in DB
if (!captcha || !captcha.value
|| !timingSafeEqual(Buffer.from(captcha.value.text), Buffer.from(captchaInput))) {
throw 'Incorrect captcha answer';
}
return true;
}

@ -1,8 +1,9 @@
'use strict';
const { Bypass, Captchas } = require(__dirname+'/../../db/')
const { Bypass } = require(__dirname+'/../../db/')
, { ObjectId } = require(__dirname+'/../../db/db.js')
, { secureCookies, blockBypass } = require(__dirname+'/../../configs/main.js')
, checkCaptcha = require(__dirname+'/../checks/captcha.js')
, remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../files/uploadDirectory.js')
, dynamicResponse = require(__dirname+'/../dynamic.js')
@ -16,40 +17,28 @@ module.exports = async (req, res, next) => {
return next();
}
//for captcha in existing form (NOTE: wont work for multipart forms yet)
const input = req.body.captcha;
if (input && input.length !== 6) {
deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Incorrect captcha answer',
'redirect': req.headers.referer,
});
}
const captchaId = req.cookies.captchaid;
let bypassId = req.signedCookies.bypassid;
if (input && !bypassId) {
// try to get the captcha from the DB
let captcha;
try {
const captchaMongoId = ObjectId(captchaId);
captcha = await Captchas.findOneAndDelete(captchaMongoId, input);
await checkCaptcha(input, captchaId);
} catch (err) {
deleteTempFiles(req).catch(e => console.error);
return next(err);
}
if (captcha && captcha.value && captcha.value.text === input) {
res.locals.solvedCaptcha = true;
res.clearCookie('captchaid');
remove(`${uploadDirectory}/captcha/${captchaId}.jpg`).catch(e => { console.error(e) });
} else {
deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 403, 'message', {
if (err instanceof Error) {
return next(err);
}
const page = (req.body.minimal || req.path === '/blockbypass' ? 'bypass' : 'message');
return dynamicResponse(req, res, 403, page, {
'title': 'Forbidden',
'message': 'Incorrect captcha answer',
'message': err,
'redirect': req.headers.referer,
});
}
res.locals.solvedCaptcha = true;
res.clearCookie('captchaid');
remove(`${uploadDirectory}/captcha/${captchaId}.jpg`).catch(e => { console.error(e) });
}
if (res.locals.solvedCaptcha) {
@ -70,6 +59,7 @@ module.exports = async (req, res, next) => {
//check if blockbypass exists and right length
if (!bypassId || bypassId.length !== 24) {
res.clearCookie('bypassid');
deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',

@ -1,11 +1,11 @@
'use strict';
module.exports = (req, res, code, page, data) => {
res.status(code);
if (req.body.minimal) {
data.minimal = true;
}
res.status(code);
if (req.headers['x-using-xhr'] != null && !req.body.minimal) {
if (req.headers['x-using-xhr'] != null) {
//if sending header with js, and not a bypass_minimal page, show modal
return res.json(data);
} else {

@ -19,7 +19,7 @@ module.exports = async (req, res, next) => {
})
return dynamicResponse(req, res, 200, 'message', {
'minimal': req.body.minimal, //todo: make use x- header for ajax once implm.
'minimal': req.body.minimal,
'title': 'Success',
'message': 'Completed block bypass, you may go back and make your post.',
});

Loading…
Cancel
Save