First version of grid v2

indiachan-spamvector
Thomas Lynch 2 years ago
parent 6e80af2eec
commit c969814f54
  1. 2
      controllers/forms/globalsettings.js
  2. 36
      gulpfile.js
  3. 3
      lib/captcha/captcha.js
  4. 147
      lib/captcha/generators/grid2.js
  5. 2
      models/pages/captcha.js
  6. 1
      views/includes/captcha.pug
  7. 4
      views/pages/globalmanagesettings.pug

@ -79,7 +79,7 @@ module.exports = {
{ result: lengthBody(req.body.ip_header, 0, 100), expected: false, error: 'IP header length must not exceed 100 characters' },
{ result: lengthBody(req.body.meta_site_name, 0, 100), expected: false, error: 'Meta site name must not exceed 100 characters' },
{ result: lengthBody(req.body.meta_url, 0, 100), expected: false, error: 'Meta url must not exceed 100 characters' },
{ result: inArrayBody(req.body.captcha_options_type, ['grid', 'text', 'google', 'hcaptcha']), expected: true, error: 'Invalid captcha options type' },
{ result: inArrayBody(req.body.captcha_options_type, ['grid', 'grid2', 'text', 'google', 'hcaptcha']), expected: true, error: 'Invalid captcha options type' },
{ result: numberBody(req.body.captcha_options_generate_limit, 1), expected: true, error: 'Captcha options generate limit must be a number > 0' },
{ result: numberBody(req.body.captcha_options_grid_size, 2, 6), expected: true, error: 'Captcha options grid size must be a number from 2-6' },
{ result: numberBody(req.body.captcha_options_grid_image_size, 50, 500), expected: true, error: 'Captcha options grid image size must be a number from 50-500' },

@ -154,6 +154,9 @@ async function ips() {
async function wipe() {
const db = Mongo.db;
const defaultConfig = require(__dirname+'/configs/template.js.example');
await Mongo.setConfig(defaultConfig);
const collectionNames = ['accounts', 'bans', 'custompages', 'boards', 'captcha', 'files',
'modlog','news', 'posts', 'poststats', 'ratelimit', 'bypass', 'roles'];
for (const name of collectionNames) {
@ -283,17 +286,28 @@ async function wipe() {
async function css() {
try {
//a little more configurable
let bypassHeight = (config.get.captchaOptions.type === 'google' || config.get.captchaOptions.type === 'hcaptcha')
? 500
: config.get.captchaOptions.type === 'grid'
? 330
: 235;
let captchaHeight = config.get.captchaOptions.type === 'text' ? 80
: config.get.captchaOptions.type === 'grid' ? config.get.captchaOptions.grid.imageSize+30
: 200; //google/hcaptcha doesnt need this set
let captchaWidth = config.get.captchaOptions.type === 'text' ? 210
: config.get.captchaOptions.type === 'grid' ? config.get.captchaOptions.grid.imageSize+30
: 200; //google/hcaptcha doesnt need this set
let bypassHeight
, captchaHeight
, captchaWidth;
switch (config.get.captchaOptions.type) {
case 'google':
case 'hcaptcha':
bypassHeight = 500;
captchaHeight = 200;
captchaWidth = 200;
break;
case 'grid':
case 'grid2':
bypassHeight = 330;
captchaHeight = config.get.captchaOptions.grid.imageSize+30;
captchaWidth = config.get.captchaOptions.grid.imageSize+30;
break;
case 'text':
bypassHeight = 235;
captchaHeight = 80;
captchaWidth = 210;
break;
}
const cssLocals = `:root {
--attachment-img: url('/file/attachment.png');
--spoiler-img: url('/file/spoiler.png');

@ -31,7 +31,8 @@ module.exports = async (captchaInput, captchaId) => {
captchaInput = Array.isArray(captchaInput) ? captchaInput : [captchaInput];
switch (captchaOptions.type) {
case 'grid': { //grid captcha
case 'grid':
case 'grid2': { //grid captcha
const gridCaptchaMongoId = ObjectId(captchaId);
const normalisedAnswer = new Array(captchaOptions.grid.size**2).fill(false);
captchaInput.forEach(num => {

@ -0,0 +1,147 @@
'use strict';
const gm = require('@fatchan/gm').subClass({ imageMagick: true })
, { promisify } = require('util')
, { Captchas } = require(__dirname+'/../../../db/')
, config = require(__dirname+'/../../misc/config.js')
, uploadDirectory = require(__dirname+'/../../file/uploaddirectory.js')
, getDistorts = require(__dirname+'/../getdistorts.js')
, randomRange = promisify(require('crypto').randomInt)
, randomBytes = promisify(require('crypto').randomBytes)
, padding = 30 //pad edge of image to account for character size + distortion
, nArrows = ['↑', '↟', '↥', '↾', '↿', '⇑', '⇡']
, eArrows = ['➸', '→', '➳', '➵', '→', '↛', '↠', '↣', '↦', '↪', '↬', '↱', '↳', '⇉', '⇏', '⇒', '⇛', '⇝', '⇢']
, wArrows = [ '←', '↚', '↞', '↜', '↢', '↩', '↤', '↫', '↰', '↲', '↵', '⇇', '⇍', '⇐', '⇚', '⇜', '⇠']
, sArrows = ['↓', '↡', '↧', '↴', '⇂', '⇃', '⇊', '⇓', '⇣']
, allArrows = [...nArrows, ...eArrows, ...wArrows, ...sArrows]
, randomBool = async (p) => { return ((await randomBytes(1))[0] > p); }
, randomOf = async (arr) => { return arr[(await randomRange(0, arr.length))]; };
//TODO: last two could belong in lib/misc/(random?)
module.exports = async () => {
const { captchaOptions } = config.get;
const { size, trues, falses, imageSize, noise, edge } = captchaOptions.grid;
const width = imageSize+padding; //TODO: these will never be different, right?
const height = imageSize+padding;
const charMatrix = new Array(size).fill(false)
.map(() => new Array(size).fill(false));
const answerMatrix = new Array(size).fill(false)
.map(() => new Array(size).fill(false));
//put the icon arrows should point at
const correctRow = await randomRange(0, size);
const correctCol = await randomRange(0, size);
charMatrix[correctRow][correctCol] = await randomOf(trues);
//put correct and incorrect arrows in the row/column
const numArray = [...new Array(size).keys()];
const perpendicularRows = numArray.filter(x => x !== correctRow);
for (let row of perpendicularRows) {
/*TODO: necessary to memoize these "inverse" sets of arrows? or maybe instead of even doing a 50/50
random, it should just pick a random from allArrows then set the isCorrect based on if its in the correct set?*/
let arrows;
const isCorrect = await randomBool(127);
if (row < correctRow) {
arrows = isCorrect ? sArrows : [...nArrows, ...eArrows, ...wArrows];
} else if (row > correctRow) {
arrows = isCorrect ? nArrows : [...sArrows, ...eArrows, ...wArrows];
}
charMatrix[row][correctCol] = await randomOf(arrows);
answerMatrix[row][correctCol] = isCorrect;
}
const perpendicularCols = numArray.filter(x => x !== correctCol);
for (let col of perpendicularCols) {
let arrows;
const isCorrect = await randomBool(127);
if (col < correctCol) {
arrows = isCorrect ? eArrows : [...sArrows, ...nArrows, ...wArrows];
charMatrix[correctRow][col] = await randomOf(arrows);
} else if (col > correctCol) {
arrows = isCorrect ? wArrows : [...sArrows, ...nArrows, ...eArrows];
charMatrix[correctRow][col] = await randomOf(arrows);
}
charMatrix[correctRow][col] = await randomOf(arrows);
answerMatrix[correctRow][col] = isCorrect;
}
//TODO: diagonals? need more arrows
//this sucks
if (!answerMatrix.flat().some(x => x === true)) {
if ((await randomBool(127)) === true) {
const randomRow = await randomOf(perpendicularRows);
const arrows = randomRow < correctRow ? sArrows : nArrows;
charMatrix[randomRow][correctCol] = await randomOf(arrows);
answerMatrix[randomRow][correctCol] = true;
} else {
const randomCol = await randomOf(perpendicularCols);
const arrows = randomCol < correctCol ? eArrows : wArrows;
charMatrix[correctRow][randomCol] = await randomOf(arrows);
answerMatrix[correctRow][randomCol] = true;
}
}
//fill the rest with junk arrows/falses
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
if (charMatrix[row][col] === false) {
charMatrix[row][col] = (await randomBool(80))
? (await randomOf(allArrows))
: (await randomOf(falses));
}
}
}
const captcha = gm(width, height, '#ffffff')
.fill('#000000')
.font(__dirname+'/../font.ttf');
const spaceSize = (width-padding)/size;
for (let row = 0; row < size; row++) {
let cxOffset = Math.floor(spaceSize * 1.5);
for (let col = 0; col < size; col++) {
const cyOffset = captchaOptions.grid.iconYOffset;
captcha.fontSize((await randomRange(20, 30)));
captcha.drawText(
(spaceSize * col) + cyOffset,
(spaceSize * row) + cxOffset,
charMatrix[row][col]
);
}
}
//console.log(charMatrix, answerMatrix);
//insert the captcha to db and get id
const captchaId = await Captchas.insertOne(answerMatrix.flat()).then(r => r.insertedId);
//create an array of distortions and apply to the image, if distortion is enabled
const { distortion, numDistorts } = captchaOptions;
if (distortion > 0) {
const distorts = await getDistorts(width, height, numDistorts, distortion);
captcha.distort(distorts, 'Shepards');
}
//add optional edge effect
if (edge > 0) {
captcha.edge(edge);
}
//add optional noise effect
if (noise > 0) {
captcha.noise(noise);
}
return new Promise((resolve, reject) => {
captcha
.write(`${uploadDirectory}/captcha/${captchaId}.jpg`, (err) => {
if (err) {
return reject(err);
}
return resolve({ captchaId });
});
});
};

@ -7,7 +7,7 @@ const { Captchas, Ratelimits } = require(__dirname+'/../../db/')
module.exports = async (req, res, next) => {
const { secureCookies, rateLimitCost, captchaOptions } = config.get;
if (!['grid', 'text'].includes(captchaOptions.type)) {
if (!['text', 'grid', 'grid2'].includes(captchaOptions.type)) {
return next(); //only grid and text captcha continue
}

@ -11,6 +11,7 @@ case captchaOptions.type
.jsonly.captcha(style='display:none;')
input.captchafield(type='text' name='captcha' autocomplete='off' placeholder='Captcha text' pattern='.{6}' required title='6 characters')
when 'grid'
when 'grid2'
span.text-center #{captchaOptions.grid.question}
.catalog
noscript.no-m-p

@ -164,14 +164,14 @@ block content
select(name='captcha_options_type')
option(value='text', selected=settings.captchaOptions.type === 'text') Text
option(value='grid', selected=settings.captchaOptions.type === 'grid') Grid v1
option(value='grid', selected=settings.captchaOptions.type === 'grid2' disabled=true) Grid v2 (Coming soon)
option(value='grid2', selected=settings.captchaOptions.type === 'grid2') Grid v2
option(value='google', selected=settings.captchaOptions.type === 'google') Google
option(value='hcaptcha', selected=settings.captchaOptions.type === 'hcaptcha') Hcaptcha
.row
.label Generate Limit
input(type='number' name='captcha_options_generate_limit' value=settings.captchaOptions.generateLimit)
.row
h4.mv-5 Grid v1 Captcha Options
h4.mv-5 Grid Captcha Options
.row
.label Image Size
input(type='number' name='captcha_options_grid_image_size' value=settings.captchaOptions.grid.imageSize)

Loading…
Cancel
Save