jschan - Anonymous imageboard software. Classic look, modern features and feel. Works without JavaScript and supports Tor, I2P, Lokinet, etc.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

126 lines
3.6 KiB

'use strict';
const { Captchas } = require(__dirname+'/../../db/')
, { ObjectId } = require(__dirname+'/../../db/db.js')
, config = require(__dirname+'/../misc/config.js')
, { yandex, hcaptcha, google } = require(__dirname+'/../../configs/secrets.js')
, FormData = require('form-data')
, fetch = require('node-fetch')
, { timingSafeEqual } = require('crypto');
module.exports = async (req, captchaInput, captchaId) => {
if (process.env.NO_CAPTCHA) {
return true;
}
const { captchaOptions, ipHeader } = config.get;
//check if captcha field in form is valid
if (!captchaInput
|| (captchaInput.length !== 6 && !captchaOptions.type === 'text')) {
throw 'Incorrect captcha answer';
}
//make sure they have captcha cookie and its 24 chars
if (!['google', 'yandex', 'hcaptcha'].includes(captchaOptions.type)
&& (!captchaId || captchaId.length !== 24)) {
throw 'Captcha expired';
}
captchaInput = Array.isArray(captchaInput) ? captchaInput : [captchaInput];
switch (captchaOptions.type) {
case 'grid':
case 'grid2': { //grid captcha
const gridCaptchaMongoId = ObjectId(captchaId);
const normalisedAnswer = new Array(captchaOptions.grid.size**2).fill(false);
captchaInput.forEach(num => {
normalisedAnswer[+num] = true;
});
let gridCaptcha = await Captchas.findOneAndDelete(gridCaptchaMongoId, normalisedAnswer);
if (!gridCaptcha || !gridCaptcha.value
|| !timingSafeEqual(
Buffer.from(gridCaptcha.value.answer.join(',')),
Buffer.from(normalisedAnswer.join(','))
)
) {
throw 'Incorrect captcha answer';
}
break;
}
case 'text': { //text captcha
const textCaptchaMongoId = ObjectId(captchaId);
let textCaptcha = await Captchas.findOneAndDelete(textCaptchaMongoId, captchaInput[0]);
if (!textCaptcha || !textCaptcha.value
|| !timingSafeEqual(
Buffer.from(textCaptcha.value.answer),
Buffer.from(captchaInput[0])
)
) {
throw 'Incorrect captcha answer';
}
break;
}
case 'google': { //google captcha
let recaptchaResponse;
try {
const form = new FormData();
form.append('secret', google.secretKey);
form.append('response', captchaInput[0]);
recaptchaResponse = await fetch('https://www.google.com/recaptcha/api/siteverify', {
method: 'POST',
body: form,
}).then(res => res.json());
} catch (e) {
console.error(e);
throw 'Captcha error occurred';
}
if (!recaptchaResponse || !recaptchaResponse.success) {
throw 'Incorrect captcha answer';
}
break;
}
case 'hcaptcha': {
let hcaptchaResponse;
try {
const form = new FormData();
form.append('secret', hcaptcha.secretKey);
form.append('sitekey', hcaptcha.siteKey);
form.append('response', captchaInput[0]);
hcaptchaResponse = await fetch('https://hcaptcha.com/siteverify', {
method: 'POST',
body: form,
}).then(res => res.json());
} catch (e) {
console.error(e);
throw 'Captcha error occurred';
}
if (!hcaptchaResponse || !hcaptchaResponse.success) {
throw 'Incorrect captcha answer';
}
break;
}
case 'yandex': {
let yandexResponse;
try {
const userIp = req.headers[ipHeader] || req.connection.remoteAddress;
yandexResponse = await fetch(
`https://smartcaptcha.yandexcloud.net/validate?secret=${encodeURIComponent(yandex.secretKey)}&token=${encodeURIComponent(captchaInput[0])}&ip=${userIp}`)
.then(res => res.json());
} catch (e) {
console.error(e);
throw 'Captcha error occurred';
}
if (!yandexResponse || yandexResponse.success !== 'ok') {
throw 'Incorrect captcha answer';
}
break;
}
default:
throw 'Captcha config error';
}
return true;
};