Interested in a ServiceNow event built for developers? Registration for now[dev]26 is officially open!

Requirement to add a google reCAPTCHA custom variable on public facing record producers

georgehornsey
Tera Contributor

I have a requirement to add a google reCAPTCHA widget that can be called via a custom variable which is added to the record producer.

 

So far I have managed to get the public widget created and loading a google reCAPTCHA but it is only working on authenticated forms.

 

I have provided screenshots for context of where I have got to...

1) Custom variable calling the widget

georgehornsey_0-1791557788601.png

2) REST Message for the siteverify

georgehornsey_1-1791557854002.png

3) script include to verify reCAPTCHA tokens

var GoogleRecaptcha = Class.create();
GoogleRecaptcha.prototype = {
    initialize: function(sitekey, secret) {
        this._gcsitekey = sitekey || gs.getProperty('google.captcha.site_key');
        this._gcsecret = secret || gs.getProperty('google.captcha.secret');
    },

    verify: function(token){        
        try {
            var r = new sn_ws.RESTMessageV2('Google Recaptcha', 'Default');
            r.setStringParameterNoEscape('token', token+""); //forcing the token to be a string
            r.setStringParameterNoEscape('secret', this._gcsecret);
            var response = r.execute();
            var responseBody = response.getBody();
            var httpStatus = response.getStatusCode();
            var responseJson = JSON.parse(responseBody);
            return responseJson && responseJson.success;
        }
        catch(ex) {
            var message = ex.message;
            gs.error("GoogleRecaptcha :: " + message);
            return false;
        }      
    },
   
    getSiteKey : function(){
        return this._gcsitekey;
    },

    type: 'GoogleRecaptcha'
};
 
4) widget code
body html:
<!-- BE SURE TO INCLUDE THE GoogleRecaptcha ANGULAR PROVIDER -->
<div>
<form ng-submit="c.submitV2Request()">
<div id="recaptcha_container"></div>
<br>
<input class="btn btn-primary" type="submit" value="Submit reCAPTCHA">
</form>
</div>

server script:
(function() {
    // You can either put the SITEKEY & SECRET in system properties or pass them in
    var gcSiteKey = gs.getProperty('google.captcha.site_key', ''); //sys property name : google.recaptcha.sitekey
    var gcSecret = gs.getProperty('google.captcha.secret', ''); //sys property name : google.recaptcha.secret
    var googleRecaptcha = new GoogleRecaptcha(gcSiteKey, gcSecret);

    if(input && input.token){  
        //check if the request was captcha verified
        if(googleRecaptcha.verify(input.token)){
            //do your regular processing
            data.success = "V2 SERVERSIDE VERIFICATION SUCCESSFUL!";
        }
        else{
            //return error
            data.error = "V2 SERVERSIDE VERIFICATION FAILED!!";
        }
    }
    else if(!input){        
        data.sitekey = googleRecaptcha.getSiteKey();
    }  
})();


Client controller:

function($scope, spUtil, GoogleRecaptcha) {
    var c = this;  

    /* V2 Checkbox Support */
    var recaptchaV2WidgetId = null, recaptchaV2Result = null;

    function getV2Result(result){
        recaptchaV2Result = result;
    }

    function resetV2Recaptcha(){
        recaptchaV2Result = null;
    }

    c.submitV2Request = function(){
        if(recaptchaV2WidgetId === null && recaptchaV2WidgetId === false){
            spUtil.addErrorMessage("Recaptcha not setup!")        
        }
        else if(recaptchaV2Result === null){
            spUtil.addErrorMessage("Recaptcha not solved!")        
        }
        else{
            //looks like we are good to go
            c.server.get({v2: true, token: recaptchaV2Result}).then(function(r){
                //reset the captcha
                GoogleRecaptcha.reset(recaptchaV2WidgetId);
                //check the response
                if(r.data.success){
                    spUtil.addInfoMessage(r.data.success);
                }
                else if(r.data.error){
                    spUtil.addErrorMessage(r.data.error);
                }
            });
        }
    };

    // We will call this from the link function so that the element has been rendered on page
    // For more info about render function visit https://developers.google.com/recaptcha/docs/display#render_param
    $scope.renderCaptcha = function(){
        GoogleRecaptcha.initializeV2({key: c.data.sitekey}).then(function(){
            recaptchaV2WidgetId = GoogleRecaptcha.render('recaptcha_container', {
                'callback' : getV2Result,
                'expired-callback' : resetV2Recaptcha
            });        
        });
    }
    /* End V2 Checkbox Support */
}

Link:
function link(scope, element, attrs, controller) {
    scope.renderCaptcha();
}

5) Angular Provider
function GoogleRecaptcha($window, $q, $document, $interval, spUtil) {
    var key = null;
    var onLoadFunctionName = 'onGoogleRecaptchaScriptLoaded';
    var defered = $q.defer();
    var initializing = false;
    var initialized = false;

    function writeScript () {
        var tag = document.createElement('script');
        tag.src=url + '?render=' + key + '&onload=' + onLoadFunctionName + '&render=explicit';
        $document.find('body')[0].appendChild(tag);
    }

    function writeScriptV2 () {
        var tag = document.createElement('script');
        tag.src=url + '?onload=' + onLoadFunctionName + '&render=explicit';
        $document.find('body')[0].appendChild(tag);
    }

    function isExecuteAvailable () {
        return angular.isFunction(($window.grecaptcha || {}).execute);
    }

    function scriptExists () {
        return $window.document.querySelector('script[src^="' + url + '"]');
    }

    function execute (params) {
        if (isInitializing()) {
            return defered
                .promise
                .then(function () {
                return execute(params);
            });
        }
        return $window.grecaptcha.execute(key, params);
    }

    function isInitialized () {
        return !!initialized;
    }

    function isInitializing () {
        return !!initializing;
    }

    function reset (id) {
        return $window.grecaptcha.reset(id);
    }

    function extractParametersForRender(obj, params){
        var result = {};
        obj = obj || {};
        params = params || [];
        for(var i in params){
            var param = params[i];          
            if(obj[param]){
                //only set size if it is set to compact otherwise let it be default
                if(param == 'size' && obj[param] === 'compact'){
                    result[param] = obj[param];
                }
                else{
                    result[param] = obj[param];
                }
            }
        }
        return result;
    }

    function render(element_id, options){
        if(initialized && $window.grecaptcha){          
            var opts = extractParametersForRender(options, ['callback', 'theme', 'size', 'tabindex', 'expired-callback','error-callback']);
            opts.sitekey = key;

            return $window.grecaptcha.render(element_id, opts);
        }
        else{
            spUtil.addErrorMessage("GoogleRecaptcha not initialized!");
            return false;
        }
    }

    function initialize (params) {
        setTimeout(function () {
            try {
                if (!params || !params.key) {
                    throw new Error('Missing required public key for reCaptcha.');
                }

                key = params.key + '';

                if (isExecuteAvailable()) {
                    initialized = true;
                    initializing = false;
                    defered.resolve();
                } else if (scriptExists()) {
                    initializing = true;
                    var wait = $interval(function () {
                        if (isExecuteAvailable()) {
                            $interval.cancel(wait);

                            initialized = true;
                            initializing = false;
                            defered.resolve();
                        }
                    }, 50);
                } else {
                    initializing = true;
                    $window[onLoadFunctionName] = function () {
                        initialized = true;
                        initializing = false;
                        defered.resolve();
                    };

                    writeScript();
                }


            } catch (e) {
                defered.reject(e);
            }
        });

        return defered.promise;
    }

    function initializeV2 (params) {
        setTimeout(function () {
            try {
                if (!params || !params.key) {
                    throw new Error('Missing required public key for reCaptcha.');
                }

                key = params.key + '';

                if (isExecuteAvailable()) {
                    initialized = true;
                    initializing = false;
                    defered.resolve();
                } else if (scriptExists()) {
                    initializing = true;
                    var wait = $interval(function () {
                        if (isExecuteAvailable()) {
                            $interval.cancel(wait);

                            initialized = true;
                            initializing = false;
                            defered.resolve();
                        }
                    }, 50);
                } else {
                    initializing = true;
                    $window[onLoadFunctionName] = function () {
                        initialized = true;
                        initializing = false;
                        defered.resolve();
                    };

                    writeScriptV2();
                }


            } catch (e) {
                defered.reject(e);
            }
        });

        return defered.promise;
    }

    return {
        initialize: initialize,
        initializeV2 : initializeV2,
        execute: execute,
        render : render,
        reset: reset,
        isInitialized: isInitialized,
        isInitializing: isInitializing
    };
}


0 REPLIES 0