bq/corbel-js

View on GitHub
src/jwt.js

Summary

Maintainability
B
4 hrs
Test Coverage
/* jshint camelcase:false */
//@exclude
'use strict';
//@endexclude

(function() {

    var jwt = corbel.jwt = {

        EXPIRATION: 3500,
        ALGORITHM: 'HS256',
        TYP: 'JWT',
        VERSION: '1.0.0',

        /**
         * JWT-HmacSHA256 generator
         * http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
         * @param  {Object}                                 claims Specific claims to include in the JWT (iss, aud, exp, scope, ...)
         * @param  {String} secret                          String with the client assigned secret
         * @param  {Object} [alg='corbel.jwt.ALGORITHM']    Object with the algorithm type
         * @return {String} jwt                             JWT string
         */
        generate: function(claims, secret, alg) {
            claims = claims || {};
            claims.exp = claims.exp || jwt._generateExp();

            if (!claims.iss) {
                throw new Error('jwt:undefined:iss');
            }
            if (!claims.aud) {
                throw new Error('jwt:undefined:aud');
            }

            return jwt._generate(claims, secret, alg);
        },

        _generate: function(claims, secret, alg){
            alg = alg || jwt.ALGORITHM;

            // Ensure claims specific order
            var claimsKeys = [
                'iss',
                'aud',
                'exp',
                'scope',
                'prn',
                'version',
                'refresh_token',
                'request_domain',

                'basic_auth.username',
                'basic_auth.password',

                'device_id'
            ];

            var finalClaims = {};
            claimsKeys.forEach(function(key) {
                if (claims[key]) {
                    finalClaims[key] = claims[key];
                }
            });

            corbel.utils.extend(finalClaims, claims);

            if (Array.isArray(finalClaims.scope)) {
                finalClaims.scope = finalClaims.scope.join(' ');
            }

            var bAlg = corbel.cryptography.rstr2b64(corbel.cryptography.str2rstr_utf8(JSON.stringify({
                    typ: jwt.TYP,
                    alg: alg
                }))),
                bClaims = corbel.cryptography.rstr2b64(corbel.cryptography.str2rstr_utf8(JSON.stringify(finalClaims))),
                segment = bAlg + '.' + bClaims,
                assertion = corbel.cryptography.b64tob64u(corbel.cryptography.b64_hmac_sha256(secret, segment));

            return segment + '.' + assertion;
        },

        _generateExp: function() {
            return Math.round((new Date().getTime() / 1000)) + jwt.EXPIRATION;
        },

        decode: function(assertion) {
            var serialize;
            if (corbel.Config.isNode || !root.atob) {
                // node environment
                serialize = require('base-64').decode;
            } else {
                serialize = root.atob;
            }
            var decoded = assertion.split('.');

            try {
                decoded[0] = JSON.parse(serialize(decoded[0]));
            } catch (e) {
                decoded[0] = false;
            }

            try {
                decoded[1] = JSON.parse(serialize(decoded[1]));
            } catch (e) {
                decoded[1] = false;
            }

            if (!decoded[0] && !decoded[1]) {
                throw new Error('corbel:jwt:decode:invalid_assertion');
            }

            decoded[0] = decoded[0] || {};
            decoded[1] = decoded[1] || {};

            Object.keys(decoded[1]).forEach(function(key) {
                decoded[0][key] = decoded[1][key];
            });

            return decoded[0];
        }

    };

    return jwt;

})();