csrf.js 4.85 KB
/*
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2015 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */

/* globals Promise */

/**
 * @module screens/player/shared/csrf
 */
define('screens/player/shared/csrf', ['underscore'], function(_) {
    'use strict';

    var TOKEN_SERVLET = 'libs/granite/csrf/token.json';

    /**
     * @typedef {Object} CSRFSupportOptions
     *
     * @property {number} [expirationTime=30000] Token expiration time in ms
     * @property {String} [serverURL='http://localhost:4502] URL of CQ server
     */
    var DEFAULTS = {
        expirationTime: 30000,
        serverURL: 'http://localhost:4502'
    };

    /**
     * Creates a new CSRF support instance.
     *
     * @classdesc CSRF Support.
     * @class CSRFSupport
     *
     * @param {CSRFSupportOptions} [options] An object of configurable options.
     */
    var CSRFSupport = function(options) {
        this.options = _.defaults({}, options, DEFAULTS);
        this._token = null;
        this._timestamp = 0;

        var url = this.options.serverURL || '';
        if (url.charAt(url.length - 1) !== '/') {
            url += '/';
        }
        url += TOKEN_SERVLET;
        this._servletURL = url;
    };

    /**
     * Name of the request header that must contain the token
     * @type {string}
     */
    CSRFSupport.HEADER_NAME = 'CSRF-Token';


    CSRFSupport.prototype = /** @lends CSRFSupport.prototype */ {

        /**
         * Retrieves the csrf token from the configured server.
         * @returns {Promise} a promise that resolves to the token.
         */
        getToken: function() {
            var self = this;

            if (self._token && !self.isExpired()) {
                return Promise.resolve(self._token);
            }

            // guard against multiple request - don't know if this works
            if (!self._promise) {
                self._promise = new Promise(function(resolve, reject) {
                    var xhr = new XMLHttpRequest();
                    xhr.onload = function() {
                        try {
                            var data = JSON.parse(xhr.responseText);
                            self._token = data.token;
                            self._timestamp = _.now();
                            resolve(self._token);
                        } catch (e) {
                            reject(e);
                        }
                    };
                    xhr.onerror = xhr.onabort = reject;
                    xhr.open('GET', self._servletURL, true);
                    xhr.send();
                }).then(function(token) {
                    self._promise = null;
                    return token;
                }).catch(function(e) {
                    console.error('error retrieving token', e);
                    self._promise = null;
                });
            }
            return self._promise;
        },

        /**
         * Checks if the cached csrf token is expired.
         * @returns {boolean} `true` if the token is expired
         */
        isExpired: function() {
            return _.now() > this._timestamp + this.options.expirationTime;
        },

        /**
         * prepares the xhr request with the correct csrf token header.
         *
         * @param {XMLHttpRequest} xhr the xhr request to prepare
         * @returns {Promise} A promise that resolves with the given xhr
         */
        prepareXHR: function(xhr) {
            return this.getToken().then(function(token) {
                xhr.setRequestHeader(CSRFSupport.HEADER_NAME, token);
                return xhr;
            });
        },

        /**
         * Executes a jQuery ajax request after adding the required request headers
         *
         * @param {jQuery} $ jQuery
         * @param {Object} options Ajax params
         * @returns {Promise} A promise that resolves with the jquery ajax object.
         */
        $ajax: function($, options) {
            // if granite.csrf is present, don't fetch the token again.
            if (window.Granite && window.Granite.csrf) {
                return Promise.resolve($.ajax(options));
            }

            return this.getToken().then(function(token) {
                options.headers = options.headers || {};
                options.headers[CSRFSupport.HEADER_NAME] = token;
                return $.ajax(options);
            });
        }

    };

    return CSRFSupport;
});