RegistrationRecaptcha.GoogleRecaptcha.js
define('RegistrationRecaptcha.GoogleRecaptcha', [
'jQuery',
'underscore'
], function GoogleRecaptcha(
jQuery,
_
) {
'use strict';
let environmentGlobal;
let containers = ['Recaptcha'];
let recaptcha = {
loadedPromise: jQuery.Deferred(),
initialized: false,
getModuleConfig: function getModuleConfig(environment) {
return environmentGlobal.getConfig('recaptcha');
},
loadCaptcha: function loadCaptcha(options) {
environmentGlobal = options.environment;
if (options.containers) {
containers = options.containers;
}
let url = this.getModuleConfig().apiUrl + '?onload=_loadCaptcha&render=explicit';
window._loadCaptcha = function _loadCaptcha() {
_.defer(function() {
_.each(containers, function (container) {
grecaptcha.render(container, {
sitekey: recaptcha.getModuleConfig().sitekey,
theme: recaptcha.getModuleConfig().theme
});
});
});
};
if (SC.ENVIRONMENT.jsEnvironment === 'browser') {
let promise = jQuery.ajax({
url: url,
dataType: 'script',
cache: true,
preventDefault: true
}).done(function () {
});
} else {
this.loadedPromise.rejectWith('Google ReCaptcha is a Browser Script only');
}
this.initialized = true;
return this.loadedPromise;
},
getResponse: function getResponse(container) {
return jQuery('#'+ container).find('.g-recaptcha-response').val();
},
resetCaptcha: function resetCaptcha() {
grecaptcha.reset();
}
};
return recaptcha;
});
RegistrationRecaptcha.js
define('RegistrationRecaptcha', [
'RegistrationRecaptcha.View',
'underscore'
], function RegistrationRecaptcha(
GoogleRecaptchaView,
_
) {
'use strict';
return {
mountToApp: function mountToApp(application) {
this.application = application;
let layout = application.getComponent('Layout');
this.environment = application.getComponent('Environment');
this.loginRegister = application.getComponent('LoginRegisterPage');
if (layout && this.loginRegister) {
layout.on('afterShowContent', function() {
this.renderElements(application);
}.bind(this));
}
},
renderElements: function renderElements() {
_.defer(function() {
var containarry = ['RecaptchaRegister']
if (jQuery('.login-register-checkout-as-guest-submit').length > 0) {
containarry.push('RecaptchaGuest')
}
let registerGuestRecaptcha = new GoogleRecaptchaView({
application: this.application,
environment: this.environment,
loginRegister: this.loginRegister,
container: 'RecaptchaGuest',
isGuest: true,
parent: this
});
let registerRecaptcha = new GoogleRecaptchaView({
application: this.application,
environment: this.environment,
loginRegister: this.loginRegister,
container: 'RecaptchaRegister',
containers: containarry,
triggerCaptcha: true,
parent: this
});
setTimeout(function() {
jQuery('.login-register-checkout-as-guest-submit').closest('.login-register-checkout-as-guest-control-group').before(
registerGuestRecaptcha.render().$el.html()
);
jQuery('.login-register-register-form-submit').closest('.login-register-register-form-controls-group').before(
registerRecaptcha.render().$el.html()
);
}, 2000);
}.bind(this));
}
};
});
RegistrationRecaptcha.View.js
define('RegistrationRecaptcha.View', [
'Backbone',
'RegistrationRecaptcha.GoogleRecaptcha',
'RegistrationRecaptcha.Model',
'registration_recaptcha.tpl',
'underscore'
], function RegistrationRecaptchaView(
Backbone,
GoogleRecaptcha,
Model,
template,
_
) {
'use strict';
return Backbone.View.extend({
template: template,
initialize: function initialize() {
this.captchaModel = new Model();
let self = this;
this.on('afterViewRender', function () {
if (this.options.triggerCaptcha) {
_.defer(function() {
let containerLength = 0;
_.each(self.options.containers, function (container) {
containerLength += jQuery('#' + container).length;
});
// dirty fix for recaptcha not loading containers
if (containerLength !== self.options.containers.length) {
setTimeout(function() {
self.options.parent.renderElements();
}, 2000);
return;
}
GoogleRecaptcha.loadCaptcha({
environment: self.options.environment,
container: self.options.container,
containers: self.options.containers
});
});
}
}.bind(this));
if (this.options.isGuest) {
jQuery('form.login-register-checkout-as-guest-form').on('submit', function (event) {
event.preventDefault();
let form = this;
let promise = self.validateCaptcha(GoogleRecaptcha.getResponse(self.options.container));
promise.done(function (data) {
if (data.success) {
self.resetMessage({
container: self.options.container
});
jQuery(form).unbind('submit').submit();
return true;
} else {
self.showMessage({
container: self.options.container,
type: 'error',
message: _('Invalid Captcha!').translate()
});
GoogleRecaptcha.resetCaptcha();
return false;
}
});
return false;
});
} else {
this.options.loginRegister.cancelableOn('beforeRegister', function (formFields) {
if (!self.options.isGuest) {
let promise = self.validateCaptcha(GoogleRecaptcha.getResponse(self.options.container));
promise.done(function (data) {
if (data.success) {
self.resetMessage({
container: self.options.container
});
return new jQuery.Deferred().resolve();
} else {
self.showMessage({
container: self.options.container,
type: 'error',
message: _('Invalid Captcha!').translate()
});
GoogleRecaptcha.resetCaptcha();
return new jQuery.Deferred().reject();
}
});
}
});
// fallback once containers are reloaded
jQuery('form.login-register-register-form').on('submit', function (event) {
event.preventDefault();
let form = this;
let promise = self.validateCaptcha(GoogleRecaptcha.getResponse(self.options.container));
promise.done(function (data) {
if (data.success) {
self.resetMessage({
container: self.options.container
});
jQuery(form).unbind('submit').submit();
return true;
} else {
self.showMessage({
container: self.options.container,
type: 'error',
message: _('Invalid Captcha!').translate()
});
GoogleRecaptcha.resetCaptcha();
return false;
}
});
return false;
});
}
},
validateCaptcha: function validateCaptcha(response) {
// @todo this needs to be removed once on production
let config = this.options.environment.getSiteSetting('siteid') + '|' +
(SC.ENVIRONMENT.currentHostString !== '386782-sb2.secure.netsuite.com' ? SC.ENVIRONMENT.currentHostString
: 'dev-valleyvet.production.netsuitestaging.com');
let promise = this.captchaModel.save({
config: config,
response: response
});
return promise;
},
showMessage: function showMessage(options) {
let $selector = jQuery('#' + options.container).parent().find('.recaptcha-message');
$selector
.removeClass('success').removeClass('error')
.addClass(options.type).html(options.message);
},
resetMessage: function resetMessage(options) {
let $selector = jQuery('#' + options.container).parent().find('.recaptcha-message');
$selector.removeClass('success').removeClass('error').html('');
},
getContext: function getContext() {
return {
container: this.options.container
};
}
});
});
RegistrationRecaptcha.Model.js
/**
* @NApiVersion 2.x
* RegistrationRecaptcha.Model
*/
define(['N/search', 'N/https'], function (search, https) {
'use strict';
function validateCaptcha(options) {
var siteConfiguration = getSiteConfiguration(options.config);
if (!siteConfiguration) {
return;
}
var configuration = JSON.parse(siteConfiguration.configuration);
var recaptchaError = {
status: 400,
code: 'ERR_RECAPTCHA_INVALID',
message: 'ReCaptcha is invalid'
};
var response = https.post({
url: configuration.recaptcha.verifyUrl,
body: {
secret: configuration.recaptcha.serverkey,
response: options.response
}
});
var result = JSON.parse(response.body);
if (result.success !== true) {
return recaptchaError;
}
return result;
}
function getSiteConfiguration(config) {
var configuration = [];
var filters = [];
var columns = [];
try {
filters.push(
search.createFilter({
name: 'isinactive',
operator: search.Operator.IS,
values: false
})
);
filters.push(
search.createFilter({
name: 'custrecord_ns_scc_key',
operator: search.Operator.ANYOF,
values: config
})
);
columns.push(
search.createColumn({
name: 'custrecord_ns_scc_value'
})
);
var selectionSearch = search.create({
type: 'customrecord_ns_sc_configuration',
columns: columns,
filters: filters
});
selectionSearch.run().each(function(item) {
configuration.push({
internalid: item.id,
configuration: item.getValue({ name: 'custrecord_ns_scc_value' })
});
return true;
});
} catch (e) {
log.error({
title: 'getSiteSettings:' + e.name,
details: e.message
});
}
return configuration.length > 0 ? configuration.pop() : null;
}
return {
validateCaptcha: validateCaptcha
};
});
RegistrationRecaptcha.Service.ss
/**
* @NApiVersion 2.x
* @NModuleScope Public
*/
define(['./RegistrationRecaptcha.Model'], function (Model) {
'use strict';
var methodNotAllowedError = {
status: 405,
code: 'ERR_METHOD_NOT_ALLOWED',
message: 'Sorry, you are not allowed to perform this action.'
};
var internalError = {
status: 500,
code: 'INTERNAL_ERR',
message: 'Sorry, there was an internal error, please try again later.'
};
return {
service: function service(context) {
var response = {};
switch (context.request.method) {
case 'POST':
var requestBody = JSON.parse(context.request.body);
response = Model.validateCaptcha({
response: requestBody.response || null,
config: requestBody.config || null
});
break;
default:
response = methodNotAllowedError;
}
context.response.write(JSON.stringify(response));
}
};
});
Template
<div class="login-register-register-form-controls-group recaptcha-container">
<div>
<div id="{{container}}"></div>
<br />
<div><small class="recaptcha-message"></small></div>
</div>
</div>