Django AJAX/JQuery tutorial: JavaScript rapid-fire protection updated to Underscore.js (original code in this post)

The JavaScript in the Django AJAX/JQuery tutorial protects against rapid-fire button clicks (direct link to the JS source for the like-buttons in part two in search-box in part three). It originally did this protection in a manual fashion, and has now been updated to use Underscore.jsdebounce function. The benefit of debounce is that it eliminates all the “protection” sections in the manual version, making the code cleaner and easier to understand.

What I thought was a minor protection against a real DDOS attack, is in fact nothing of the sort. These kinds of attack directly target the server, they don’t go through the client (browser) at all. Therefore client-side protection is pointless. It is, however, a reasonable protection against users doing things as fast as they can.

Here’s the original, manual code for reference:

color_ajax_like.js

//THIS FILE MUST BE IMPORTED BEFORE THE "main" FILE.

/**
    The number of milliseconds to ignore clicks on the *same* like
    button, after a button *that was not ignored* was clicked. Used by
    <link to processLike>

    Equal to <code>500</code>.

    The disabling and re-enabling is logged to the console.
 */
var MILLS_TO_IGNORE_LIKES = 500;
/**
    Executes a like click. Triggered by clicks on the various yes/no
    links.

    The disabling and re-enabling is logged to the console.

    See <link to MILLS_TO_IGNORE_LIKES>
 */
var processLike = function()  {

    //In this scope, "this" is the button just clicked on.
    //The "this" in processLikeInner is *not* the button just clicked on.
    var $button_just_clicked_on = $(this);

    //The value of the "data-color_id" attribute.
    var color_id = $button_just_clicked_on.data('color_id');

    var processLikeInner = function(data, textStatus_ignored, jqXHR_ignored)  {
        //alert("sf data='" + data + "', textStatus_ignored='" + textStatus_ignored + "', jqXHR_ignored='" + jqXHR_ignored + "', color_id='" + color_id + "'");
        $('#toggle_color_like_cell_' + color_id).html(data);

        //Rapid-fire click prevention. Don't process requests too close together.

        console.log('Like disabled for: ' + MILLS_TO_IGNORE_LIKES);

        setTimeout(function() {
            $button_just_clicked_on.one('click', processLike);
            console.log('Like re-enabled for color_id ' + color_id + ' ');
        }, MILLS_TO_IGNORE_LIKES);
    }

    var config = {
        url: LIKE_URL_PRE_ID + color_id + '/',
        dataType: 'html',
        success: processLikeInner
    };
    $.ajax(config);
};

color_ajax_search.js

//THIS FILE MUST BE IMPORTED BEFORE THE "main" FILE.

/**
    The number of milliseconds to ignore key-presses in the search box,
    after a key *that was not ignored* was pressed. Used by
    <link to processSearch>

    Equal to <code>100</code>.

    The disabling and re-enabling is logged to the console.
 */
var MILLS_TO_IGNORE_SEARCH = 100;
/**
    Executes a search for colors containing a substring. Triggered by
    key-presses in the search box, as long as there are MIN_SEARCH_CHARS
    or more characters in it (which are lowercased, and trimmed before
    submission).

    See <link to MILLS_TO_IGNORE_SEARCH>.
 */
var processSearch = function()  {
    //The key-press listener is no longer attached

    //Get and trim the search text.
    var searchText = $('#color_search_text').val().trim().toLowerCase();

    if(searchText.length < MIN_SEARCH_CHARS)  {
        //Too short. Ignore the submission, and erase any current results.
        $('#color_search_results').html("");

        //No need to prevent the next request. The server/database haven't
        //been hit.
        activateSearchListenerOnce();

    }  else  {
        //There are at least two characters. Execute the search.
        var config = {
            /*
                Using GET allows you to directly call the search page in
                the browser:

                http://the.url/search/?color_search_text=bl

                Also, GET-s do not require the csrf_token
             */
            type: "GET",
            url: SUBMIT_URL,
            data: {
                'color_search_text' : searchText,
            },
            dataType: 'html',
            success: function (data, textStatus_ignored, jqXHR_ignored)  {
                //alert("data='" + data + "', textStatus_ignored='" + textStatus_ignored + "', jqXHR_ignored='" + jqXHR_ignored + "'");
                $('#color_search_results').html(data);
            }
        };
        $.ajax(config);

        //Attack prevention. Don't process requests too close together.

        console.log('Search disabled for: ' + MILLS_TO_IGNORE_SEARCH);
        setTimeout(function() {
            activateSearchListenerOnce();
            console.log('Search re-enabled');
        }, MILLS_TO_IGNORE_SEARCH);
    }
};

color_ajax_main.js

//THIS MUST BE IMPORTED AS THE VERY LAST THING BEFORE THE CLOSE </body>
//tag.

/**
    Attach the search-textbox listener. Used by the main function and
   color_ajax_search.js/processSearch.
 */
var activateSearchListenerOnce = function()  {
     $('#color_search_text').one('keyup', processSearch);
}
/**
   The Ajax "main" function. Attaches the listeners to the elements on
   page load.
 */
$(document).ready(function()  {
    activateSearchListenerOnce();
    /*
        There are many buttons having the class

            td__toggle_color_like_button

        This attaches a listener to *every one*. Calling this again
        would attach a *second* listener to every button, meaning each
        click would be processed twice.

        When a button is clicked, the listener for that *single button*
        is disabled. It's re-enabled in processLikeInner with

            $button_just_clicked_on.one('click', processLike);
     */
    $('.td__toggle_color_like_button').one('click', processLike);
});
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s