Browse Source

reCAPTCHA 2

pull/126/head
Lal'C Mellk Mal 2 years ago
parent
commit
24109d4385
5 changed files with 126 additions and 88 deletions
  1. +1
    -1
      package.json
  2. +103
    -61
      report/client.js
  3. +2
    -2
      report/config.js.example
  4. +19
    -23
      report/server.js
  5. +1
    -1
      server/server.js

+ 1
- 1
package.json View File

@@ -22,7 +22,7 @@
"formidable": "1.0.17",
"minimist": "1.2.0",
"nan": "2.5.0",
"recaptcha": "1.2.1",
"recaptcha2": "^1.3.2",
"redis": "2.6.3",
"request": "2.79",
"sockjs": "0.3.18",


+ 103
- 61
report/client.js View File

@@ -1,11 +1,11 @@
(function () {

var pubKey = reportConfig.RECAPTCHA_PUBLIC_KEY;
var captchaTimeout = 5 * 60 * 1000;
var siteKey = reportConfig.RECAPTCHA_SITE_KEY;
var REPORTS = {};
var PANEL;
var CAPTCHA_CTR = 0;

if (pubKey)
if (siteKey)
menuOptions.push('Report');

var Report = Backbone.Model.extend({
@@ -15,39 +15,63 @@ var Report = Backbone.Model.extend({
},

request_new: function () {
var self = this;

Recaptcha.create(pubKey, 'captcha', {
theme: 'clean',
callback: function () {
self.set('status', 'ready');
}
this.set({
status: 'setup',
error: ''
});

if (this.get('timeout'))
clearTimeout(this.get('timeout'));

this.set('timeout', setTimeout(function () {
self.set('timeout', 0);
self.request_new();
}, captchaTimeout));
if (this.get('captchaId')) {
this.reset();
return;
}
// inject a new div into captchaHolder
let id = 'captcha_' + CAPTCHA_CTR++;
this.trigger('createDiv', id);
// render a new captcha on it
let params = {
sitekey: siteKey,
theme: 'dark',
callback: response => {
this.set({
status: 'ready',
error: '',
response: response
});
},
'expired-callback': () => {
this.set({
status: 'error',
error: 'reCAPTCHA expired.'
});
},
'error-callback': () => {
this.set({
status: 'error',
error: 'reCAPTCHA error.'
});
}
};
setTimeout(() => {
debugger;
window.grecaptcha.render(id, params);
}, 10);
},

did_report: function () {
delete REPORTS[this.id];
if (this.get('timeout')) {
clearTimeout(this.get('timeout'));
this.set('timeout', 0);
}

var self = this;
setTimeout(function () {
self.trigger('destroy');
}, 1500);
setTimeout(() => this.trigger('destroy'), 1500);

if (this.get('hideAfter'))
this.get('post').set('hide', true);
},

reset: function () {
let captchaId = this.get('captchaId');
if (captchaId) {
grecaptcha.reset(captchaId);
this.set('captchaId', null);
}
},
});

var ReportPanel = Backbone.View.extend({
@@ -62,7 +86,10 @@ var ReportPanel = Backbone.View.extend({
},

initialize: function () {
this.$captcha = $('<div id="captcha"/>');
this.$captchaHolder = $('<div>', {
id: 'captcha',
css: {'min-width': 304, 'min-height': 78}
});
this.$message = $('<div class="message"/>');
this.$submit = $('<input>', {type: 'submit', val: 'Report'});
var $hideAfter = $('<input>', {
@@ -80,7 +107,7 @@ var ReportPanel = Backbone.View.extend({
.append($('<a/>', {href: '#'+num, text: '>>'+num}))
.append('<a class="close" href="#">x</a>')
.append(this.$message)
.append(this.$captcha)
.append(this.$captchaHolder)
.append(this.$submit)
.append(' ', $hideLabel);

@@ -93,6 +120,7 @@ var ReportPanel = Backbone.View.extend({
this.listenTo(this.model, {
'change:error': this.error_changed,
'change:status': this.status_changed,
createDiv: this.create_div,
destroy: this.remove,
});
},
@@ -103,13 +131,23 @@ var ReportPanel = Backbone.View.extend({
return this;
},

submit: function () {
if (this.model.get('status') != 'ready')
return false;
send([REPORT_POST, this.model.id, Recaptcha.get_challenge(),
Recaptcha.get_response()]);
this.model.set('status', 'reporting');
return false;
create_div: function (id) {
this.$captchaHolder.empty().append($('<div/>', {id: id}));
},

submit: function (event) {
event.preventDefault();
let status = this.model.get('status');
if (status == 'ready' && this.model.get('response')) {
send([REPORT_POST, this.model.id, this.model.get('response')]);
this.model.set({
status: 'reporting',
response: null
});
}
else if (status == 'error') {
this.model.request_new();
}
},

error_changed: function () {
@@ -118,12 +156,15 @@ var ReportPanel = Backbone.View.extend({

status_changed: function () {
var status = this.model.get('status');
let submit = 'Report';
if (status == 'reporting')
submit = 'Reporting...';
if (status == 'error')
submit = 'Another.';
this.$submit
.prop('disabled', status != 'ready')
.prop('disabled', status != 'ready' && status != 'error')
.toggle(status != 'done')
.val(status=='reporting' ? 'Reporting...' : 'Report');
this.$captcha.toggle(
_.contains(['ready', 'reporting', 'error'], status));
.val(submit);
if (status == 'done')
this.$('label').remove();

@@ -137,38 +178,32 @@ var ReportPanel = Backbone.View.extend({
else if (status == 'ready' && this.model.get('error'))
msg = 'E';
this.$message.text(msg=='E' ? this.model.get('error') : msg);
this.$message
.toggle(!!msg)
.toggleClass('error', msg == 'E');
this.$message.toggleClass('error', msg == 'E');

// not strictly view logic, but only relevant when visible
if (status == 'ready')
this.focus();
else if (status == 'done')
if (status == 'done')
this.model.did_report();
else if (status == 'error')
this.model.request_new();
},

hide_after_changed: function (e) {
this.model.set('hideAfter', e.target.checked);
},

focus: function () {
Recaptcha.focus_response_field();
},

remove: function () {
this.model.reset();

Backbone.View.prototype.remove.call(this);
if (PANEL == this) {
PANEL = null;
Recaptcha.destroy();
}
return false;
},
});

var ajaxJs = 'https://www.google.com/recaptcha/api/js/recaptcha_ajax.js';
var ajaxJs = 'https://www.google.com/recaptcha/api.js?onload=on_init_captcha&render=explicit';

var CAPTCHA_LOADED = false;
window.on_init_captcha = () => { CAPTCHA_LOADED = true; };

menuHandlers.Report = function (post) {
var num = post.id;
@@ -185,14 +220,21 @@ menuHandlers.Report = function (post) {
}
PANEL = new ReportPanel({model: model});
PANEL.render().$el.appendTo('body');
$.getScript(ajaxJs, function () {
if (window.Recaptcha)
model.request_new();
else
model.set({
status: 'error',
error: "Couldn't load reCATPCHA.",
});
if (CAPTCHA_LOADED) {
model.request_new();
return;
}
$.getScript(ajaxJs, () => {
// why is `grecaptcha` not immediately available?
setTimeout(() => {
if (CAPTCHA_LOADED)
model.request_new();
else
model.set({
status: 'error',
error: "Couldn't load reCATPCHA.",
});
}, 10);
});
};



+ 2
- 2
report/config.js.example View File

@@ -12,6 +12,6 @@ module.exports = {
},
},

RECAPTCHA_PUBLIC_KEY: '',
RECAPTCHA_PRIVATE_KEY: '',
RECAPTCHA_SITE_KEY: '',
RECAPTCHA_SECRET_KEY: '',
};

+ 19
- 23
report/server.js View File

@@ -6,18 +6,20 @@ var caps = require('../server/caps'),
msgcheck = require('../server/msgcheck'),
nodemailer = require('nodemailer'),
okyaku = require('../server/okyaku'),
recaptcha = require('recaptcha'),
Recaptcha2 = require('recaptcha2'),
smtpTransport = require('nodemailer-smtp-transport'),
winston = require('winston');

var SMTP = nodemailer.createTransport(smtpTransport(config.SMTP));

const ERRORS = {
'invalid-site-private-key': "Sorry, the server isn't set up with reCAPTCHA properly.",
'invalid-request-cookie': "Something went wrong with our reCAPTCHA token. Please try again.",
'incorrect-captcha-sol': "Incorrect.",
'captcha-timeout': "Sorry, you took too long. Please try again.",
};
var VALIDATOR;
if (!!config.RECAPTCHA_SITE_KEY) {
VALIDATOR = new Recaptcha2({
siteKey: config.RECAPTCHA_SITE_KEY,
secretKey: config.RECAPTCHA_SECRET_KEY,
});
exports.enabled = true;
}

var safe = common.safe;

@@ -135,7 +137,7 @@ function maybe_mnemonic(ip) {
}

okyaku.dispatcher[common.REPORT_POST] = function (msg, client) {
if (!msgcheck.check(['id', 'string', 'string'], msg))
if (!msgcheck.check(['id', 'string'], msg))
return false;

var num = msg[0];
@@ -143,24 +145,13 @@ okyaku.dispatcher[common.REPORT_POST] = function (msg, client) {
if (!op || !caps.can_access_thread(client.ident, op))
return reply_error("Post does not exist.");

var data = {
remoteip: client.ident.ip,
challenge: msg[1],
response: msg[2].trim(),
};
if (!data.challenge || !data.response)
const response = msg[1];
if (!response)
return reply_error("Pretty please?");
if (data.challenge.length > 10000 || data.response.length > 10000)
if (response.length > 10000)
return reply_error("tl;dr");

var checker = new recaptcha.Recaptcha(config.RECAPTCHA_PUBLIC_KEY,
config.RECAPTCHA_PRIVATE_KEY, data);
checker.verify(function (ok, err) {
if (!ok) {
reply_error(ERRORS[err] || err);
return;
}

VALIDATOR.validate(response, client.ident.ip).then(function () {
var op = db.OPs[num];
if (!op)
return reply_error("Post does not exist.");
@@ -172,6 +163,11 @@ okyaku.dispatcher[common.REPORT_POST] = function (msg, client) {
// success!
client.send([op, common.REPORT_POST, num]);
});
}, function (err) {
let readable = VALIDATOR.translateErrors(err);
if (Array.isArray(readable))
readable = readable.join('; ');
reply_error(readable);
});
return true;



+ 1
- 1
server/server.js View File

@@ -33,7 +33,7 @@ if (config.CURFEW_BOARDS)
require('../curfew/server');
try {
var reportConfig = require('../report/config');
if (reportConfig.RECAPTCHA_PUBLIC_KEY)
if (reportConfig.RECAPTCHA_SITE_KEY)
require('../report/server');
} catch (e) {}



Loading…
Cancel
Save