From bd428342937127142034f68ad0137fd72067edbb Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Tue, 20 Nov 2012 08:08:25 -0300 Subject: [PATCH 01/26] support secure cookies, apply changes from other forks that seemed to be useful, updated tests and remove legacy stuff --- .gitignore | 1 + deps/nodeunit | 1 - lib/cookie-sessions.js | 31 +++++++---- package.json | 6 +++ test.js | 22 -------- test/test-cookie-sessions.js | 99 +++++++++++++++++++++++++++++++++++- 6 files changed, 124 insertions(+), 36 deletions(-) create mode 100644 .gitignore delete mode 160000 deps/nodeunit delete mode 100755 test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/deps/nodeunit b/deps/nodeunit deleted file mode 160000 index 0e1afee..0000000 --- a/deps/nodeunit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e1afee3a47a29c63d296de7dabc687122a301bd diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index 477a388..e1dbf15 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -12,12 +12,12 @@ var exports = module.exports = function(settings){ var s = extend(default_settings, settings); if(!s.secret) throw new Error('No secret set in cookie-session settings'); - if(typeof s.path !== 'string' || s.path.indexOf('/') != 0) + if(typeof s.path !== 'string' || s.path.indexOf('/') !== 0) throw new Error('invalid cookie path, must start with "/"'); return function(req, res, next){ // if the request is not under the specified path, do nothing. - if (url.parse(req.url).pathname.indexOf(s.path) != 0) { + if (url.parse(req.url).pathname.indexOf(s.path) !== 0) { next(); return; } @@ -57,7 +57,9 @@ var exports = module.exports = function(settings){ + '; expires=' + exports.expires(s.timeout) + '; path=' + s.path + '; HttpOnly'; } + if ( s.domain ) cookiestr +='; Domain=' + s.domain; + if ( s.secure ) cookiestr +='; Secure'; if (cookiestr !== undefined) { if(Array.isArray(headers)) headers.push(['Set-Cookie', cookiestr]); @@ -82,9 +84,9 @@ var exports = module.exports = function(settings){ } // call the original writeHead on the request return _writeHead.apply(res, args); - } - next(); + }; + next(); }; }; @@ -104,7 +106,7 @@ function extend(obj) { for (var prop in source) obj[prop] = source[prop]; }); return obj; -}; +} exports.deserialize = function(secret, timeout, str){ // Parses a secure cookie string, returning the object stored within it. @@ -157,10 +159,13 @@ exports.valid = function(secret, timeout, str){ var hmac_sig = exports.hmac_signature( secret, parts.timestamp, parts.data_blob ); - return ( - parts.hmac_signature === hmac_sig && - parts.timestamp + timeout > new Date().getTime() - ); + + var validatedTimeout = true; + if(timeout){ + validatedTimeout = parts.timestamp + timeout > new Date().getTime(); + } + + return (parts.hmac_signature === hmac_sig && validatedTimeout); }; exports.decrypt = function(secret, str){ @@ -210,10 +215,14 @@ exports.readSession = function(key, secret, timeout, req){ if(cookies[key]){ return exports.deserialize(secret, timeout, cookies[key]); } - return undefined; + return {}; }; exports.expires = function(timeout){ - return (new Date(new Date().getTime() + (timeout))).toUTCString(); + if(timeout){ + return (new Date(new Date().getTime() + (timeout))).toUTCString(); + } + + return null; }; diff --git a/package.json b/package.json index daddbdb..523bac9 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,17 @@ , "main": "./index" , "author": "Caolan McMahon" , "version": "0.0.2" +, "scripts": { + "test": "nodeunit test", +} , "repository" : { "type" : "git" , "url" : "http://github.com/caolan/cookie-sessions.git" } , "bugs" : { "url" : "http://github.com/caolan/cookie-sessions/issues" } +, "devDependencies" : { + "nodeunit" : "*" +} , "licenses" : [ { "type" : "MIT" , "url" : "http://github.com/caolan/cookie-sessions/raw/master/LICENSE" diff --git a/test.js b/test.js deleted file mode 100755 index f41d154..0000000 --- a/test.js +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/local/bin/node - -require.paths.push(__dirname); -require.paths.push(__dirname + '/deps'); -require.paths.push(__dirname + '/lib'); - -try { - var testrunner = require('nodeunit').testrunner; -} -catch(e) { - var sys = require('sys'); - sys.puts("Cannot find nodeunit module."); - sys.puts("You can download submodules for this project by doing:"); - sys.puts(""); - sys.puts(" git submodule init"); - sys.puts(" git submodule update"); - sys.puts(""); - process.exit(); -} - -process.chdir(__dirname); -testrunner.run(['test']); diff --git a/test/test-cookie-sessions.js b/test/test-cookie-sessions.js index e05026c..187d6cd 100644 --- a/test/test-cookie-sessions.js +++ b/test/test-cookie-sessions.js @@ -1,4 +1,4 @@ -var sessions = require('cookie-sessions'); +var sessions = require('../lib/cookie-sessions'); exports['split'] = function(test){ @@ -305,7 +305,7 @@ exports['readSession no cookie'] = function(test){ var r = sessions.readSession( 'node_session', 'secret', 12, 'request_obj' ); - test.same(r, undefined, 'return empty session'); + test.same(r, {}, 'return empty session'); // restore copied functions sessions.readCookies = readCookies; @@ -389,6 +389,101 @@ exports['writeHead'] = function(test){ sessions(s)(req, res, next); }; +exports['writeHead secure cookies'] = function(test){ + test.expect(6); + + var s = { + session_key:'_node', + secret: 'secret', + timeout: 86400, + secure: true + }; + var req = {headers: {cookie: "_node="}, url: '/'}; + var res = { + writeHead: function(code, headers){ + test.equals( + headers['Set-Cookie'], + '_node=serialized_session; ' + + 'expires=expiry_date; ' + + 'path=/; HttpOnly; Secure' + ); + test.equals(headers['original'], 'header'); + } + }; + + var serialize = sessions.serialize; + sessions.serialize = function(secret, data){ + test.equals(secret, 'secret', 'serialize called with secret'); + test.same(data, {test:'test'}, 'serialize called with session data'); + return 'serialized_session'; + }; + + var expires = sessions.expires; + sessions.expires = function(timeout){ + test.equals(timeout, s.timeout); + return 'expiry_date'; + }; + + var next = function(){ + test.ok(true, 'chain.next called'); + req.session = {test:'test'}; + res.writeHead(200, {'original':'header'}); + // restore copied functions + sessions.serialize = serialize; + sessions.expires = expires; + test.done(); + }; + sessions(s)(req, res, next); +}; + +exports['writeHead secure cookies with domain'] = function(test){ + test.expect(6); + + var s = { + session_key:'_node', + secret: 'secret', + timeout: 86400, + domain:'.domain.com', + secure: true + }; + var req = {headers: {cookie: "_node="}, url: '/'}; + var res = { + writeHead: function(code, headers){ + test.equals( + headers['Set-Cookie'], + '_node=serialized_session; ' + + 'expires=expiry_date; ' + + 'path=/; HttpOnly; Domain=.domain.com; Secure' + ); + test.equals(headers['original'], 'header'); + } + }; + + var serialize = sessions.serialize; + sessions.serialize = function(secret, data){ + test.equals(secret, 'secret', 'serialize called with secret'); + test.same(data, {test:'test'}, 'serialize called with session data'); + return 'serialized_session'; + }; + + var expires = sessions.expires; + sessions.expires = function(timeout){ + test.equals(timeout, s.timeout); + return 'expiry_date'; + }; + + var next = function(){ + test.ok(true, 'chain.next called'); + req.session = {test:'test'}; + res.writeHead(200, {'original':'header'}); + // restore copied functions + sessions.serialize = serialize; + sessions.expires = expires; + test.done(); + }; + sessions(s)(req, res, next); +}; + exports['writeHead doesnt write cookie if none exists and session is undefined'] = function(test){ test.expect(3); From b5d540507b770f6ecb33d5fc030fb8442132b399 Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Tue, 20 Nov 2012 08:10:35 -0300 Subject: [PATCH 02/26] remove extra semicolon on package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 523bac9..12104e4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ , "author": "Caolan McMahon" , "version": "0.0.2" , "scripts": { - "test": "nodeunit test", + "test": "nodeunit test" } , "repository" : { "type" : "git" From ff1918004bc4a93bbc3645b05ba9dd0a202ae655 Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Tue, 20 Nov 2012 08:38:59 -0300 Subject: [PATCH 03/26] support for session cookies (i.e. not setting expires) --- lib/cookie-sessions.js | 19 +++++++++------ test/test-cookie-sessions.js | 47 +++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index e1dbf15..60e3dd4 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -47,15 +47,20 @@ var exports = module.exports = function(settings){ var cookiestr; if (req.session === undefined) { if ("cookie" in req.headers) { - cookiestr = escape(s.session_key) + '=' - + '; expires=' + exports.expires(0) - + '; path=' + s.path + '; HttpOnly'; + cookiestr = escape(s.session_key) + '='; + if (!s.session_cookie) { + cookiestr += '; expires=' + exports.expires(-30 * 24 * 60 * 60 * 1000); + } + cookiestr += '; path=' + s.path + '; HttpOnly'; + } } else { - cookiestr = escape(s.session_key) + '=' - + escape(exports.serialize(s.secret, req.session)) - + '; expires=' + exports.expires(s.timeout) - + '; path=' + s.path + '; HttpOnly'; + cookiestr = escape(s.session_key) + '=' + escape(exports.serialize(s.secret, req.session)); + if (!s.session_cookie) { + cookiestr += '; expires=' + exports.expires(s.timeout); + } + + cookiestr += '; path=' + s.path + '; HttpOnly'; } if ( s.domain ) cookiestr +='; Domain=' + s.domain; diff --git a/test/test-cookie-sessions.js b/test/test-cookie-sessions.js index 187d6cd..151f3f3 100644 --- a/test/test-cookie-sessions.js +++ b/test/test-cookie-sessions.js @@ -484,6 +484,51 @@ exports['writeHead secure cookies with domain'] = function(test){ sessions(s)(req, res, next); }; +exports['writeHead session cookies'] = function(test){ + test.expect(5); + + var s = { + session_key:'_node', + secret: 'secret', + session_cookie: true + }; + var req = {headers: {cookie: "_node="}, url: '/'}; + var res = { + writeHead: function(code, headers){ + test.equals( + headers['Set-Cookie'], + '_node=serialized_session; ' + + 'path=/; HttpOnly' + ); + test.equals(headers['original'], 'header'); + } + }; + + var serialize = sessions.serialize; + sessions.serialize = function(secret, data){ + test.equals(secret, 'secret', 'serialize called with secret'); + test.same(data, {test:'test'}, 'serialize called with session data'); + return 'serialized_session'; + }; + + var expires = sessions.expires; + sessions.expires = function(timeout){ + test.equals(null, s.timeout); + return 'expiry_date'; + }; + + var next = function(){ + test.ok(true, 'chain.next called'); + req.session = {test:'test'}; + res.writeHead(200, {'original':'header'}); + // restore copied functions + sessions.serialize = serialize; + sessions.expires = expires; + test.done(); + }; + sessions(s)(req, res, next); +}; + exports['writeHead doesnt write cookie if none exists and session is undefined'] = function(test){ test.expect(3); @@ -532,7 +577,7 @@ exports['writeHead writes empty cookie with immediate expiration if session is u var expires = sessions.expires; sessions.expires = function(timeout){ - test.equals(timeout, 0); + test.equals(timeout, -30 * 24 * 60 * 60 * 1000); return 'now'; }; var readSession = sessions.readSession; From 31414ab9fca395d5f2e8e3a35db7d1736beabecc Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Fri, 24 May 2013 10:17:13 -0300 Subject: [PATCH 04/26] dont throw an error if cookie timed out (session or persistent) --- lib/cookie-sessions.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index 60e3dd4..29cc2a5 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -118,7 +118,8 @@ exports.deserialize = function(secret, timeout, str){ // Throws an exception if the secure cookie string does not validate. if(!exports.valid(secret, timeout, str)){ - throw new Error('invalid cookie'); + console.log('cookie invalid (expired or bad mac'); + return {}; } var data = exports.decrypt(secret, exports.split(str).data_blob); return JSON.parse(data); @@ -133,7 +134,8 @@ exports.serialize = function(secret, data){ var hmac_sig = exports.hmac_signature(secret, timestamp, data_enc); var result = hmac_sig + timestamp + data_enc; if(!exports.checkLength(result)){ - throw new Error('data too long to store in a cookie'); + console.error('data too long to store in a cookie'); + return {}; } return result; }; From a192c158c8f1b40e4f248cb3242171869cbb48d9 Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Fri, 24 May 2013 11:24:51 -0300 Subject: [PATCH 05/26] update test to reflect new behavior on invalid cookie and too long cookie --- test/test-cookie-sessions.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/test/test-cookie-sessions.js b/test/test-cookie-sessions.js index 151f3f3..a7fd597 100644 --- a/test/test-cookie-sessions.js +++ b/test/test-cookie-sessions.js @@ -133,12 +133,10 @@ exports['deserialize invalid cookie'] = function(test){ JSON.parse = function(str){ test.ok(false, 'should not attempt to parse invalid cookie'); }; - try { - sessions.deserialize('secret', 123, 'cookiestring'); - } - catch(e){ - test.ok(true, 'throw exception on invalid cookie'); - } + + var result = sessions.deserialize('secret', 123, 'cookiestring'); + + test.same({}, result, 'should return empty cookie') // restore copied functions: sessions.valid = valid; @@ -212,14 +210,10 @@ exports['serialize data over 4096 chars'] = function(test){ sessions.hmac_signature = function(secret, timestamp, data_str){ return 'hmac'; }; - try { - var r = sessions.serialize('secret', {test:'test'}); - } - catch(e){ - test.ok( - true, 'serializing a cookie over 4096 chars throws an exception' - ); - } + + var r = sessions.serialize('secret', {test:'test'}); + + test.same({}, r, 'should return empty cookie on long data'); // restore copied functions: sessions.encrypt = encrypt; From 46a21d4eb518d254371fc085bc84e8f3c3d5535e Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 12:44:28 +0100 Subject: [PATCH 06/26] refactor library and update crypto --- lib/cookie-sessions.js | 349 +++++++++++++++++------------------------ 1 file changed, 142 insertions(+), 207 deletions(-) diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index 29cc2a5..0f255d0 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -1,235 +1,170 @@ -var crypto = require('crypto'); +var magic = require('auth0-magic') var url = require('url'); -var exports = module.exports = function(settings){ - - var default_settings = { - // don't set a default cookie secret, must be explicitly defined - session_key: '_node', - timeout: 1000 * 60 * 60 * 24, // 24 hours - path: '/' - }; - var s = extend(default_settings, settings); - if(!s.secret) throw new Error('No secret set in cookie-session settings'); - - if(typeof s.path !== 'string' || s.path.indexOf('/') !== 0) - throw new Error('invalid cookie path, must start with "/"'); +var MAX_LENGTH = 4096; + +var exports = module.exports = function(settings) { + // don't set a default cookie secret, must be explicitly defined + var defaultSettings = { + sessionKey: '_node', + timeout: 1000 * 60 * 60 * 24, // 24 hours + path: '/' + }; + + var opts = Object.assign({}, defaultSettings, settings) + + if (!opts.secret) { + throw new Error('No secret set in cookie-session settings'); + } + if ((typeof opts.secret === 'string' && opts.secret.length !== 64) || + (Buffer.isBuffer(opts.secret) && opts.secret.length !== 32)) { + throw new Error('Invalid secret length'); + } + + if (typeof opts.path !== 'string' || opts.path.indexOf('/') !== 0) { + throw new Error('Invalid cookie path, must start with "/"'); + } + + return function(req, res, next) { + // if the request is not under the specified path, do nothing. + if (req.path.indexOf(opts.path) !== 0) { + next(); + return; + } - return function(req, res, next){ - // if the request is not under the specified path, do nothing. - if (url.parse(req.url).pathname.indexOf(s.path) !== 0) { - next(); - return; + // Read session data from a request and store it in req.session + exports.readSession(opts.session_key, opts.secret, opts.timeout, req, (err, session) => { + if (err) { + session = null + } + req.session = session; + + var options = { + path: opts.path, + httpOnly: true, + domain: opts.domain, + secure: opts.secure + }; + + // If the session is undefined, no cookie is sent (and the cookie is + // cleared if it is already set). If a session exists, we add a + // Set-Cookie header to all responses with the session data and the + // current timestamp. The cookie needs to be set on every response so + // that the timestamp is up to date, and the session does not expire + // unless the user is inactive. + if (req.session === null) { + if (req.cookies[opts.session_key]) { + if (!opts.sessionCookie) { + options.expires = exports.expires(0); + }; + + res.cookie(opts.session_key, '', options) } - - // Read session data from a request and store it in req.session - req.session = exports.readSession( - s.session_key, s.secret, s.timeout, req); - - // proxy writeHead to add cookie to response - var _writeHead = res.writeHead; - res.writeHead = function(statusCode){ - - var reasonPhrase, headers; - if (typeof arguments[1] === 'string') { - reasonPhrase = arguments[1]; - headers = arguments[2] || {}; - } - else { - headers = arguments[1] || {}; - } - - // Add a Set-Cookie header to all responses with the session data - // and the current timestamp. The cookie needs to be set on every - // response so that the timestamp is up to date, and the session - // does not expire unless the user is inactive. - - var cookiestr; - if (req.session === undefined) { - if ("cookie" in req.headers) { - cookiestr = escape(s.session_key) + '='; - if (!s.session_cookie) { - cookiestr += '; expires=' + exports.expires(-30 * 24 * 60 * 60 * 1000); - } - cookiestr += '; path=' + s.path + '; HttpOnly'; - - } - } else { - cookiestr = escape(s.session_key) + '=' + escape(exports.serialize(s.secret, req.session)); - if (!s.session_cookie) { - cookiestr += '; expires=' + exports.expires(s.timeout); - } - - cookiestr += '; path=' + s.path + '; HttpOnly'; - } - - if ( s.domain ) cookiestr +='; Domain=' + s.domain; - if ( s.secure ) cookiestr +='; Secure'; - - if (cookiestr !== undefined) { - if(Array.isArray(headers)) headers.push(['Set-Cookie', cookiestr]); - else { - // if a Set-Cookie header already exists, convert headers to - // array so we can send multiple Set-Cookie headers. - if(headers['Set-Cookie'] !== undefined){ - headers = exports.headersToArray(headers); - headers.push(['Set-Cookie', cookiestr]); - } - // if no Set-Cookie header exists, leave the headers as an - // object, and add a Set-Cookie property - else { - headers['Set-Cookie'] = cookiestr; - } - } - } - - var args = [statusCode, reasonPhrase, headers]; - if (!args[1]) { - args.splice(1, 1); - } - // call the original writeHead on the request - return _writeHead.apply(res, args); - }; - next(); - }; + return; + } else { + if (!opts.sessionCookie) { + options.expires = exports.expires(opts.timeout); + } + exports.serialize(opts.secret, req.session, (err, cookieData) => { + if (err) { + next(err) + return; + } + res.cookie(opts.session_key, cookieData, options) + next(); + return; + }) + } + }) + }; }; -exports.headersToArray = function(headers){ - if(Array.isArray(headers)) return headers; - return Object.keys(headers).reduce(function(arr, k){ - arr.push([k, headers[k]]); - return arr; - }, []); +exports.split = function(str){ + var arr = str.split('$') + return { + nonce: arr[0], + ciphertext: arr[1] + }; }; -// Extend a given object with all the properties in passed-in object(s). -// From underscore.js (http://documentcloud.github.com/underscore/) -function extend(obj) { - Array.prototype.slice.call(arguments).forEach(function(source) { - for (var prop in source) obj[prop] = source[prop]; - }); - return obj; +exports.valid = function(timestamp, timeout) { + return timestamp + timeout > Date.now(); } -exports.deserialize = function(secret, timeout, str){ - // Parses a secure cookie string, returning the object stored within it. - // Throws an exception if the secure cookie string does not validate. - - if(!exports.valid(secret, timeout, str)){ - console.log('cookie invalid (expired or bad mac'); - return {}; - } - var data = exports.decrypt(secret, exports.split(str).data_blob); - return JSON.parse(data); +exports.expires = function(timeout) { + if (timeout) { + return new Date(Date.now() + timeout); + } + return null; }; -exports.serialize = function(secret, data){ - // Turns a JSON-compatibile object literal into a secure cookie string - - var data_str = JSON.stringify(data); - var data_enc = exports.encrypt(secret, data_str); - var timestamp = (new Date()).getTime(); - var hmac_sig = exports.hmac_signature(secret, timestamp, data_enc); - var result = hmac_sig + timestamp + data_enc; - if(!exports.checkLength(result)){ - console.error('data too long to store in a cookie'); - return {}; +exports.encrypt = function(secret, message, cb) { + magic.encrypt.sync(message, secret, (err, output) => { + if (err) { + return cb(err) } - return result; -}; - -exports.split = function(str){ - // Splits a cookie string into hmac signature, timestamp and data blob. - return { - hmac_signature: str.slice(0,40), - timestamp: parseInt(str.slice(40, 53), 10), - data_blob: str.slice(53) - }; -}; - -exports.hmac_signature = function(secret, timestamp, data){ - // Generates a HMAC for the timestamped data, returning the - // hex digest for the signature. - var hmac = crypto.createHmac('sha1', secret); - hmac.update(timestamp + data); - return hmac.digest('hex'); + cb( + null, + { ciphertext: output.ciphertext.toString('hex'), + nonce: output.nonce.toString('hex') + } + ) + }) }; -exports.valid = function(secret, timeout, str){ - // Tests the validity of a cookie string. Returns true if the HMAC - // signature of the secret, timestamp and data blob matches the HMAC in the - // cookie string, and the cookie's age is less than the timeout value. - - var parts = exports.split(str); - var hmac_sig = exports.hmac_signature( - secret, parts.timestamp, parts.data_blob - ); - - var validatedTimeout = true; - if(timeout){ - validatedTimeout = parts.timestamp + timeout > new Date().getTime(); +exports.decrypt = function(secret, ciphertext, nonce, cb) { + magic.decrypt.sync(secret, ciphertext, nonce, (err, plaintext) => { + if (err) { + return cb(err) } - - return (parts.hmac_signature === hmac_sig && validatedTimeout); -}; - -exports.decrypt = function(secret, str){ - // Decrypt the aes192 encoded str using secret. - var decipher = crypto.createDecipher("aes192", secret); - return decipher.update(str, 'hex', 'utf8') + decipher.final('utf8'); -}; - -exports.encrypt = function(secret, str){ - // Encrypt the str with aes192 using secret. - var cipher = crypto.createCipher("aes192", secret); - return cipher.update(str, 'utf8', 'hex') + cipher.final('hex'); -}; - -exports.checkLength = function(str){ - // Test if a string is within the maximum length allowed for a cookie. - return str.length <= 4096; + cb(null, plaintext.toString('utf-8')) + }); }; -exports.readCookies = function(req){ - // if "cookieDecoder" is in use, then req.cookies - // will already contain the parsed cookies - if (req.cookies) { - return req.cookies; +// Turns a JSON-compatibile object literal into a secure cookie string +exports.serialize = function(secret, data, cb) { + var cookieData = { + d: data, + t: Date.now() + } + var data_str = JSON.stringify(cookieData); + exports.encrypt(secret, data_str, (err, result) => { + if (err) { + return cb(new Error('Failed to encrypt cookie')) } - else { - // Extracts the cookies from a request object. - var cookie = req.headers.cookie; - if(!cookie){ - return {}; - } - var parts = cookie.split(/\s*;\s*/g).map(function(x){ - return x.split('='); - }); - return parts.reduce(function(a, x){ - a[unescape(x[0])] = unescape(x[1]); - return a; - }, {}); + var result = result.nonce + '$' + result.ciphertext; + if (result.length > MAX_LENGTH) { + return cb(new Error('Data too long to store in a cookie')) ; } -}; - -exports.readSession = function(key, secret, timeout, req){ - // Reads the session data stored in the cookie named 'key' if it validates, - // otherwise returns an empty object. + cb(null, result); + }) +} - var cookies = exports.readCookies(req); - if(cookies[key]){ - return exports.deserialize(secret, timeout, cookies[key]); +// Parses a secure cookie string, returning the object stored within it. +exports.deserialize = function(secret, str, timeout, cb) { + var splited = exports.split(str) + exports.decrypt(secret, splited.ciphertext, splited.nonce, (err, cookieData) => { + if (err) { + cb(new Error('Failed to decrypt cookie')) + return; } - return {}; -}; + var jsonData = JSON.parse(cookieData) - -exports.expires = function(timeout){ - if(timeout){ - return (new Date(new Date().getTime() + (timeout))).toUTCString(); + if (!exports.valid(jsonData.t, timeout)) { + return cb(new Error('Cookie expired')) } + cb(null, jsonData.d); + }); +}; - return null; + // Reads the session data stored in the cookie named 'key' if it validates, + // otherwise returns null. +exports.readSession = function(key, secret, timeout, req, cb) { + if (req.cookies && req.cookies[key]) { + exports.deserialize(secret, req.cookies[key], timeout, cb); + } else { + cb(null, null); + } }; From 3ae865beac59147590d583f5128a48757dd48ea8 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 12:52:06 +0100 Subject: [PATCH 07/26] update tests --- test/cookie-sessions.test.js | 131 ++++++ test/test-cookie-sessions.js | 801 ----------------------------------- 2 files changed, 131 insertions(+), 801 deletions(-) create mode 100644 test/cookie-sessions.test.js delete mode 100644 test/test-cookie-sessions.js diff --git a/test/cookie-sessions.test.js b/test/cookie-sessions.test.js new file mode 100644 index 0000000..073a6f9 --- /dev/null +++ b/test/cookie-sessions.test.js @@ -0,0 +1,131 @@ +var sessions = require('../lib/cookie-sessions'); +var assert = require('assert') +var sinon = require('sinon') +var crypto = require('crypto') +var sandbox; + +describe('cookie sessions tests', () => { + var secret = '63e973cd82855e4f5669af6d7fa0ec4be02474871c155ead81a669474adc5e61' + var message = 'the answer to the ultimate question of life, the universe, and everything' + var encr = '8c17200a08910652154d41b561242f4ff4672e4caf2ac71fc29b40cbe8f1451519e333f26f6b87fbda5e29220171dc350d67b537ee7ac02d2e6dd4f25597de27727a70c2e196242026a15daffaaf6bc9230b03b5786879e9bd' + var nonce = '074800982ca4f120b37acaf86c4997769100843d466e5595' + var user_data = { + id: 123, + username: 'foobar', + email: 'foobar@gmail.com', + role: 'user' + } + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('encrypt', () => { + it('should sucessfully return encrypted data', (done) => { + sessions.encrypt(secret, message, (err, result) => { + assert.ok(!err) + done(); + }); + }) + }) + + describe('decrypt', () => { + it('should sucessfully return decrypted data', (done) => { + sessions.decrypt(secret, encr, nonce, (err, plaintext) => { + assert.equal(message, plaintext) + done(); + }); + }) + }) + + describe('serialize', () => { + it('should return a string', (done) => { + sessions.serialize(secret, user_data, (err, result) => { + assert.ok(!err) + var arr = result.split('$') + var nonce = arr[0] + var cipher = arr[1] + assert.ok(nonce) + assert.ok(cipher) + done(); + }); + }) + + it('should return error on long data', (done) => { + var long = ''; + for(var i=0; i<2050; i++){ + long += 'a'; + }; + sandbox.stub(sessions, 'encrypt').yields(null, {ciphertext: long, nonce: long}); + sessions.serialize(secret, user_data, (err, result) => { + assert.ok(err) + assert.equal(err.message, 'Data too long to store in a cookie') + done(); + }); + }) + }) + + describe('deserialize', () => { + it('should return user data in JSON if cookie has not expired', (done) => { + sessions.serialize(secret, user_data, (err, result) => { + sessions.deserialize(secret, result, 1000 * 60 * 60 * 24, (err, data) => { + assert.ok(!err) + assert.deepEqual(data, user_data) + done(); + }); + }); + }) + + it('should return validation error if cookie has expired', (done) => { + var str = 'b5a8af11ce10cd632796df523c9142595b472b686738f6a6$29d28d5d9c63b3daf6d628626b5ce3d045dc506321579af014b7eda3665e84342f65c97f79df314b7991255a41585e325e7a6e7e0218485c27af644cc1517e81801dae85716deeea32fbdccebda1ed59a3b3a1ec689447d5a39d6a37c4541623ef93ffc0ca2fb17cd20e4708a7fab59e3f87c5c5afa277b66bd3' + sessions.deserialize(secret, str, 1000 * 60 * 60 * 24, (err, data) => { + assert.ok(err) + assert.equal(err.message, 'Cookie expired') + done(); + }); + }) + }) + + describe('readSession', () => { + it('session with key node_session read ok', () => { + sandbox.stub(sessions, 'deserialize').yields(null, {username: 'foobar'}); + sessions.readSession('node_session', secret, 12, {cookies: {node_session: 'lala'}} , (err, data) => { + assert.ok(!err) + assert.deepEqual({username: 'foobar'}, data) + }) + }); + }) + + describe('onInit', () => { + it('throw exception if no secret set in server settings', () => { + try { + sessions({}) + } catch(err) { + assert.ok(err) + assert.equal(err.message, 'No secret set in cookie-session settings') + } + }); + it('throw exception for invalid path', () => { + try { + sessions({path: 'foo/bar'}) + } catch(err) { + assert.ok(err) + assert.equal(err.message, 'No secret set in cookie-session settings') + } + }); + it('should do nothing for a request not under the specified path', (done) => { + const session = sessions({path: '/foo/bar', secret: secret }) + req = { path: '/foo/baz' } + res = {} + next = function() { + assert.ok(!req.session) + done() + } + session(req, res, next) + }); + }) +}) diff --git a/test/test-cookie-sessions.js b/test/test-cookie-sessions.js deleted file mode 100644 index a7fd597..0000000 --- a/test/test-cookie-sessions.js +++ /dev/null @@ -1,801 +0,0 @@ -var sessions = require('../lib/cookie-sessions'); - - -exports['split'] = function(test){ - var hmac_sig = 'c82d1eacb4adb15a3250a6df7c8f190b586ab6b9', - timestamp = 1264710287440, - data_blob = 'somedata'; - - var serialized_cookie = hmac_sig + timestamp + data_blob; - test.same( - sessions.split(serialized_cookie), - {hmac_signature: hmac_sig, timestamp: timestamp, data_blob: data_blob}, - 'split correctly seperates sig, timestamp and data blob' - ); - test.done(); -}; - -exports['valid'] = function(test){ - var secret = 'secret'; - current_valid_sig = '5eaaa22480acefd8b18d67bb194573dc1b75d9db', - expired_valid_sig = '9c7ad86913ceeced1f6f249ba52868006c8dfdab', - invalid_sig = '51a2a32485a6e7d8b9810711112513d14b15d16b', - expired_timestamp = 1264700000000, - current_timestamp = 1264710287440, - session_timeout = 54000, - data_blob = 'somedata'; - - var Date_copy = global.Date; - global.Date = function(){this.getTime = function(){return 1264710287000}}; - - test.ok( - sessions.valid( - secret, session_timeout, - current_valid_sig + current_timestamp + data_blob - ) === true, - 'returns true for valid hmac sig within timeout' - ); - test.ok( - sessions.valid( - secret, session_timeout, - expired_valid_sig + expired_timestamp + data_blob - ) === false, - 'returns false for valid hmac sig past timeout' - ); - test.ok( - sessions.valid( - secret, session_timeout, - invalid_sig + current_timestamp + data_blob - ) === false, - 'returns false for invalid hmac sig within timeout' - ); - test.ok( - sessions.valid( - secret, session_timeout, - invalid_sig + expired_timestamp + data_blob - ) === false, - 'returns false for invalid hmac sig past timeout' - ); - - // restore Date - global.Date = Date_copy; - test.done(); -}; - -exports['decrypt'] = function(test){ - var r = sessions.decrypt( - 'secret', '686734eb9e0fff9adea53983210825ef' - ); - test.same(r, 'somedata', 'decrypt sucessfully returns decrypted data'); - test.done(); -}; - -exports['encrypt'] = function(test){ - var r = sessions.encrypt('secret', 'somedata'); - test.same( - r, '686734eb9e0fff9adea53983210825ef', - 'encrypt sucessfully returns encrypted data' - ); - test.done(); -}; - -exports['deserialize valid cookie'] = function(test){ - test.expect(8); - // copy some functions - var valid = sessions.valid; - var decrypt = sessions.decrypt; - var parse = JSON.parse; - - sessions.valid = function(secret, timeout, str){ - test.equals(secret, 'secret', 'valid called with secret'); - test.equals(timeout, 123, 'valid called with timeout'); - test.equals(str, 'cookiestring', 'valid called with cookie string'); - return true; - }; - sessions.split = function(str){ - test.equals(str, 'cookiestring', 'split called with cookie string'); - return {data_blob: 'datastr'}; - }; - sessions.decrypt = function(secret, str){ - test.equals(secret, 'secret', 'decrypt called with secret'); - test.equals(str, 'datastr', 'decrypt called with data string'); - return 'decrypted_data'; - }; - JSON.parse = function(str){ - test.equals( - str, 'decrypted_data', 'JSON.parse called with decrypted data' - ); - return {test:'test'}; - }; - var r = sessions.deserialize('secret', 123, 'cookiestring'); - test.same(r, {test:'test'}, 'deserialize returns parsed json data'); - - // restore copied functions: - sessions.valid = valid; - sessions.decrypt = decrypt; - JSON.parse = parse; - test.done(); -}; - -exports['deserialize invalid cookie'] = function(test){ - test.expect(1); - // copy some functions - var valid = sessions.valid; - var decrypt = sessions.decrypt; - var parse = JSON.parse; - - sessions.valid = function(secret, timeout, str){ - return false; - }; - sessions.decrypt = function(secret, str){ - test.ok(false, 'should not attempt to decrypt invalid cookie'); - }; - JSON.parse = function(str){ - test.ok(false, 'should not attempt to parse invalid cookie'); - }; - - var result = sessions.deserialize('secret', 123, 'cookiestring'); - - test.same({}, result, 'should return empty cookie') - - // restore copied functions: - sessions.valid = valid; - sessions.decrypt = decrypt; - JSON.parse = parse; - test.done(); -}; - -exports['serialize'] = function(test){ - test.expect(7); - // copy some functions - var encrypt = sessions.encrypt; - var hmac_signature = sessions.hmac_signature; - var stringify= JSON.stringify; - var Date_copy = global.Date; - - global.Date = function(){this.getTime = function(){return 1234;}}; - JSON.stringify = function(obj){ - test.same( - obj, {test:'test'}, 'JSON.stringify called with cookie data obj' - ); - return 'data'; - }; - sessions.encrypt = function(secret, str){ - test.equals(secret, 'secret', 'encrypt called with secret'); - test.equals(str, 'data', 'encrypt called with stringified data'); - return 'encrypted_data'; - }; - sessions.hmac_signature = function(secret, timestamp, data_str){ - test.equals(secret, 'secret', 'hmac_signature called with secret'); - test.equals(timestamp, 1234, 'hmac_signature called with timestamp'); - test.equals( - data_str, 'encrypted_data', - 'hmac_signature called with encrypted data string' - ); - return 'hmac'; - }; - var r = sessions.serialize('secret', {test:'test'}); - test.equals( - r, 'hmac1234encrypted_data', 'serialize returns correct string' - ); - - // restore copied functions: - sessions.encrypt = encrypt; - sessions.hmac_signature = hmac_signature; - JSON.stringify = stringify; - global.Date = Date_copy; - test.done(); -}; - -exports['serialize data over 4096 chars'] = function(test){ - test.expect(1); - // copy some functions - var encrypt = sessions.encrypt; - var hmac_signature = sessions.hmac_signature; - var stringify= JSON.stringify; - var Date_copy = global.Date; - - global.Date = function(){this.getTime = function(){return 1234;}}; - JSON.stringify = function(obj){ - return 'data'; - }; - sessions.encrypt = function(secret, str){ - // lets make this too long! - var r = ''; - for(var i=0; i<4089; i++){ - r = r + 'x'; - }; - return r; - }; - sessions.hmac_signature = function(secret, timestamp, data_str){ - return 'hmac'; - }; - - var r = sessions.serialize('secret', {test:'test'}); - - test.same({}, r, 'should return empty cookie on long data'); - - // restore copied functions: - sessions.encrypt = encrypt; - sessions.hmac_signature = hmac_signature; - JSON.stringify = stringify; - global.Date = Date_copy; - test.done(); -}; - -exports['readCookies'] = function(test){ - var req = { - headers: {cookie: "name1=data1; test=\"abcXYZ%20123\""}, - url: '/' - }; - var r = sessions.readCookies(req); - test.same(r, {name1: 'data1', test: '"abcXYZ 123"'}, 'test header read ok'); - test.done(); -}; - -exports['readCookies alternate format'] = function(test){ - var req = { - headers: {cookie: "name1=data1;test=\"abcXYZ%20123\""}, - url: '/' - }; - var r = sessions.readCookies(req); - test.same(r, {name1: 'data1', test: '"abcXYZ 123"'}, 'test header read ok'); - test.done(); -}; - -exports['readCookies no cookie in headers'] = function(test){ - var req = {headers: {}, url: '/'}; - var r = sessions.readCookies(req); - test.same(r, {}, 'returns empty object'); - test.done(); -}; - -exports['readCookies from Connect cookieDecoder'] = function(test){ - var req = {headers: {}, cookies: {'test':'cookie'}, url: '/'}; - test.same(sessions.readCookies(req), {'test': 'cookie'}); - test.done(); -}; - -exports['readSession'] = function(test){ - test.expect(5); - var readCookies = sessions.readCookies; - var deserialize = sessions.deserialize; - - sessions.readCookies = function(r){ - test.equals(r, 'request_obj', 'readCookies called with request object'); - return {'node_session': 'cookie_data'}; - }; - sessions.deserialize = function(secret, timeout, str){ - test.equals(secret, 'secret', 'readCookies called with secret'); - test.equals(timeout, 12, 'readCookies called with timeout'); - test.equals(str, 'cookie_data', 'readCookies called with cookie data'); - return {test: 'test'}; - }; - - var r = sessions.readSession( - 'node_session', 'secret', 12, 'request_obj' - ); - test.same(r, {test: 'test'}, 'session with key node_session read ok'); - - // restore copied functions - sessions.readCookies = readCookies; - sessions.deserialize = deserialize; - test.done(); -}; - -exports['readSession no cookie'] = function(test){ - test.expect(2); - var readCookies = sessions.readCookies; - var deserialize = sessions.deserialize; - - sessions.readCookies = function(r){ - test.equals(r, 'request_obj', 'readCookies called with request object'); - return {}; - }; - sessions.deserialize = function(secret, timeout, str){ - test.ok(false, 'should not call deserialize'); - }; - - var r = sessions.readSession( - 'node_session', 'secret', 12, 'request_obj' - ); - test.same(r, {}, 'return empty session'); - - // restore copied functions - sessions.readCookies = readCookies; - sessions.deserialize = deserialize; - test.done(); -}; - -exports['onRequest'] = function(test){ - test.expect(5); - var readSession = sessions.readSession; - var s = { - session_key:'_node', - secret: 'secret', - timeout: 86400 - }; - var req = {url: '/'}; - - sessions.readSession = function(key, secret, timeout, req){ - test.equals(key, '_node', 'readSession called with session key'); - test.equals(secret, 'secret', 'readSession called with secret'); - test.equals(timeout, 86400, 'readSession called with timeout'); - return 'testsession'; - }; - var next = function(){ - test.ok(true, 'chain.next called'); - test.equals( - req.session, 'testsession', 'req.session equals session data' - ); - }; - sessions(s)(req, 'res', next); - - // restore copied functions - sessions.readSession = readSession; - test.done(); -}; - -exports['writeHead'] = function(test){ - test.expect(6); - - var s = { - session_key:'_node', - secret: 'secret', - timeout: 86400, - domain:'.domain.com' - }; - var req = {headers: {cookie: "_node="}, url: '/'}; - var res = { - writeHead: function(code, headers){ - test.equals( - headers['Set-Cookie'], - '_node=serialized_session; ' + - 'expires=expiry_date; ' + - 'path=/; HttpOnly; Domain=.domain.com' - ); - test.equals(headers['original'], 'header'); - } - }; - - var serialize = sessions.serialize; - sessions.serialize = function(secret, data){ - test.equals(secret, 'secret', 'serialize called with secret'); - test.same(data, {test:'test'}, 'serialize called with session data'); - return 'serialized_session'; - }; - - var expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(timeout, s.timeout); - return 'expiry_date'; - }; - - var next = function(){ - test.ok(true, 'chain.next called'); - req.session = {test:'test'}; - res.writeHead(200, {'original':'header'}); - // restore copied functions - sessions.serialize = serialize; - sessions.expires = expires; - test.done(); - }; - sessions(s)(req, res, next); -}; - -exports['writeHead secure cookies'] = function(test){ - test.expect(6); - - var s = { - session_key:'_node', - secret: 'secret', - timeout: 86400, - secure: true - }; - var req = {headers: {cookie: "_node="}, url: '/'}; - var res = { - writeHead: function(code, headers){ - test.equals( - headers['Set-Cookie'], - '_node=serialized_session; ' + - 'expires=expiry_date; ' + - 'path=/; HttpOnly; Secure' - ); - test.equals(headers['original'], 'header'); - } - }; - - var serialize = sessions.serialize; - sessions.serialize = function(secret, data){ - test.equals(secret, 'secret', 'serialize called with secret'); - test.same(data, {test:'test'}, 'serialize called with session data'); - return 'serialized_session'; - }; - - var expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(timeout, s.timeout); - return 'expiry_date'; - }; - - var next = function(){ - test.ok(true, 'chain.next called'); - req.session = {test:'test'}; - res.writeHead(200, {'original':'header'}); - // restore copied functions - sessions.serialize = serialize; - sessions.expires = expires; - test.done(); - }; - sessions(s)(req, res, next); -}; - -exports['writeHead secure cookies with domain'] = function(test){ - test.expect(6); - - var s = { - session_key:'_node', - secret: 'secret', - timeout: 86400, - domain:'.domain.com', - secure: true - }; - var req = {headers: {cookie: "_node="}, url: '/'}; - var res = { - writeHead: function(code, headers){ - test.equals( - headers['Set-Cookie'], - '_node=serialized_session; ' + - 'expires=expiry_date; ' + - 'path=/; HttpOnly; Domain=.domain.com; Secure' - ); - test.equals(headers['original'], 'header'); - } - }; - - var serialize = sessions.serialize; - sessions.serialize = function(secret, data){ - test.equals(secret, 'secret', 'serialize called with secret'); - test.same(data, {test:'test'}, 'serialize called with session data'); - return 'serialized_session'; - }; - - var expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(timeout, s.timeout); - return 'expiry_date'; - }; - - var next = function(){ - test.ok(true, 'chain.next called'); - req.session = {test:'test'}; - res.writeHead(200, {'original':'header'}); - // restore copied functions - sessions.serialize = serialize; - sessions.expires = expires; - test.done(); - }; - sessions(s)(req, res, next); -}; - -exports['writeHead session cookies'] = function(test){ - test.expect(5); - - var s = { - session_key:'_node', - secret: 'secret', - session_cookie: true - }; - var req = {headers: {cookie: "_node="}, url: '/'}; - var res = { - writeHead: function(code, headers){ - test.equals( - headers['Set-Cookie'], - '_node=serialized_session; ' + - 'path=/; HttpOnly' - ); - test.equals(headers['original'], 'header'); - } - }; - - var serialize = sessions.serialize; - sessions.serialize = function(secret, data){ - test.equals(secret, 'secret', 'serialize called with secret'); - test.same(data, {test:'test'}, 'serialize called with session data'); - return 'serialized_session'; - }; - - var expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(null, s.timeout); - return 'expiry_date'; - }; - - var next = function(){ - test.ok(true, 'chain.next called'); - req.session = {test:'test'}; - res.writeHead(200, {'original':'header'}); - // restore copied functions - sessions.serialize = serialize; - sessions.expires = expires; - test.done(); - }; - sessions(s)(req, res, next); -}; - -exports['writeHead doesnt write cookie if none exists and session is undefined'] = function(test){ - test.expect(3); - - var s = { - session_key:'_node', - secret: 'secret', - timeout: 86400 - }; - var req = {headers: {}, url: '/'}; - var res = { - writeHead: function(code, headers){ - test.ok(!("Set-Cookie" in headers)); - test.equals(headers['original'], 'header'); - } - }; - - var next = function(){ - test.ok(true, 'chain.next called'); - req.session = undefined; - res.writeHead(200, {'original':'header'}); - test.done(); - }; - sessions(s)(req, res, next); -}; - -exports['writeHead writes empty cookie with immediate expiration if session is undefined'] = function(test){ - test.expect(4); - - var s = { - session_key:'_node', - secret: 'secret', - timeout: 86400 - }; - var req = {headers: {cookie: "_node=Blah"}, url: '/'}; - var res = { - writeHead: function(code, headers){ - test.equals( - headers['Set-Cookie'], - '_node=; ' + - 'expires=now; ' + - 'path=/; HttpOnly' - ); - test.equals(headers['original'], 'header'); - } - }; - - var expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(timeout, -30 * 24 * 60 * 60 * 1000); - return 'now'; - }; - var readSession = sessions.readSession; - sessions.readSession = function(key, secret, timeout, req) { - return {"username": "Bob"}; - }; - - var next = function(){ - test.ok(true, 'chain.next called'); - req.session = undefined; - res.writeHead(200, {'original':'header'}); - // restore copied functions - sessions.expires = expires; - sessions.readSession = readSession; - test.done(); - }; - sessions(s)(req, res, next); -}; - -exports['onInit secret set'] = function(test){ - test.expect(0); - var s = {secret: 'secret'}; - try { - sessions({secret: 'secret'}); - } - catch(e){ - test.ok(false, 'do nothing if secret set in server settings'); - } - test.done(); -}; - -exports['onInit no secret set'] = function(test){ - test.expect(1); - try { - sessions({}); - } - catch(e){ - test.ok(true, 'throw exception if no secret set in server settings'); - } - test.done(); -}; - -exports['set multiple cookies'] = function(test){ - test.expect(3); - var _serialize = sessions.serialize; - sessions.serialize = function(){ - return 'session_data'; - }; - - var _expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(timeout, 12345); - return 'expiry_date'; - }; - - var req = {headers: {cookie:''}, url: '/'}; - var res = {writeHead: function(statusCode, headers){ - test.equals(statusCode, 200); - test.same(headers, [ - ['other_header', 'val'], - ['Set-Cookie', 'testcookie=testvalue'], - ['Set-Cookie', '_node=session_data; ' + - 'expires=expiry_date; ' + - 'path=/; HttpOnly'] - ]); - sessions.serialize = _serialize; - sessions.expires = _expires; - test.done(); - }}; - - sessions({secret: 'secret', timeout: 12345})(req, res, function(){ - req.session = {test: 'test'}; - res.writeHead(200, { - 'other_header': 'val', - 'Set-Cookie':'testcookie=testvalue' - }); - }); -}; - -exports['set single cookie'] = function(test){ - test.expect(3); - var _serialize = sessions.serialize; - sessions.serialize = function(){ - return 'session_data'; - }; - - var _expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(timeout, 12345); - return 'expiry_date'; - }; - - var req = {headers: {cookie:''}, url: '/'}; - var res = {writeHead: function(statusCode, headers){ - test.equals(statusCode, 200); - test.same(headers, { - 'other_header': 'val', - 'Set-Cookie': '_node=session_data; ' + - 'expires=expiry_date; ' + - 'path=/; HttpOnly' - }); - sessions.serialize = _serialize; - sessions.expires = _expires; - test.done(); - }}; - sessions({secret: 'secret', timeout: 12345})(req, res, function(){ - req.session = {test: 'test'}; - res.writeHead(200, {'other_header': 'val'}); - }); -}; - -exports['handle headers as array'] = function(test){ - test.expect(3); - var _serialize = sessions.serialize; - sessions.serialize = function(){ - return 'session_data'; - }; - - var _expires = sessions.expires; - sessions.expires = function(timeout){ - test.equals(timeout, 12345); - return 'expiry_date'; - }; - - var req = {headers: {cookie:''}, url: '/'}; - var res = {writeHead: function(statusCode, headers){ - test.equals(statusCode, 200); - test.same(headers, [ - ['header1', 'val1'], - ['header2', 'val2'], - ['Set-Cookie', '_node=session_data; ' + - 'expires=expiry_date; ' + - 'path=/; HttpOnly'] - ]); - sessions.serialize = _serialize; - test.done(); - }}; - sessions({secret: 'secret', timeout: 12345})(req, res, function(){ - req.session = {test: 'test'}; - res.writeHead(200, [['header1', 'val1'],['header2', 'val2']]); - }); -}; - -exports['convert headers to array'] = function(test){ - test.same( - sessions.headersToArray({'key1':'val1','key2':'val2'}), - [['key1','val1'],['key2','val2']] - ); - test.same( - sessions.headersToArray([['key1','val1'],['key2','val2']]), - [['key1','val1'],['key2','val2']] - ); - test.done(); -}; - -exports['send cookies even if there are no headers'] = function (test) { - test.expect(2); - var req = {headers: {cookie:''}, url: '/'}; - var res = { - writeHead: function (code, headers) { - test.equal(code, 200); - test.ok(headers['Set-Cookie']); - test.done(); - } - }; - sessions({secret: 'secret', timeout: 12345})(req, res, function () { - req.session = {test: 'test'}; - res.writeHead(200); - }); -}; - -exports['send cookies when no headers but reason_phrase'] = function (test) { - test.expect(3); - var req = {headers: {cookie:''}, url: '/'}; - var res = { - writeHead: function (code, reason_phrase, headers) { - test.equal(code, 200); - test.equal(reason_phrase, 'reason'); - test.ok(headers['Set-Cookie']); - test.done(); - } - }; - sessions({secret: 'secret', timeout: 12345})(req, res, function () { - req.session = {test: 'test'}; - res.writeHead(200, 'reason'); - }); -}; - -exports['custom path'] = function (test) { - test.expect(2); - var req = {headers: {cookie:''}, url: '/test/path'}; - var res = { - writeHead: function (code, headers) { - test.equal(code, 200); - test.ok(/path=\/test\/path/.test(headers['Set-Cookie'])); - test.done(); - } - }; - sessions({ - secret: 'secret', - timeout: 12345, - path: '/test/path' - })(req, res, function () { - req.session = {test: 'test'}; - res.writeHead(200, {'other_header': 'val'}); - }); -}; - -exports['don\'t set cookie if incorrect path'] = function (test) { - test.expect(2); - var req = {headers: {cookie:''}, url: '/other/path'}; - var res = { - writeHead: function (code, headers) { - test.equal(code, 200); - test.equal(headers['Set-Cookie'], undefined); - test.done(); - } - }; - sessions({ - secret: 'secret', - timeout: 12345, - path: '/test/path' - })(req, res, function () { - req.session = {test: 'test'}; - res.writeHead(200, {'other_header': 'val'}); - }); -}; From 3d18d96ab9df939d9eeee0b270afb0f2a226e2d0 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 12:52:43 +0100 Subject: [PATCH 08/26] Update Readme, add a section with the differences --- README.md | 74 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 670bf98..eafdaa0 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,41 @@ # Cookie-Sessions -Secure cookie-based session middleware for -[Connect](http://github.com/senchalabs/connect). This is a new module and I -wouldn't recommend for production use just yet. +Secure cookie-based session middleware for Express. Session data is stored on the request object in the 'session' property: +```js + var app = require('express'); + var cookieSessions = require('cookie-sessions'); - var connect = require('connect'), - sessions = require('cookie-sessions'); + app.use( + cookieSessions({ + session_key: 'session_data', + secret: process.env.SECRET + }) + ); +``` - Connect.createServer( - sessions({secret: '123abc'}), - function(req, res, next){ - req.session = {'hello':'world'}; - res.writeHead(200, {'Content-Type':'text/plain'}); - res.end('session data updated'); - } - ).listen(8080); +The session data can be any JSON object. It's timestamped, encrypted and +authenticated automatically. The authenticated encryption uses +`xsalsa20poly1305` offered by +[auth0-magic](https://github.com/auth0/magic) via +[magic.encrypt.sync](https://github.com/auth0/magic/#magicencryptsync--magicdecryptsync) +function. The httpOnly cookie flag is set by default. -The session data is JSON.stringified, encrypted and timestamped, then a HMAC -signature is attached to test for tampering. The main function accepts a -number of options: +The main function accepts a number of options: - * secret -- The secret to encrypt the session data with - * timeout -- The amount of time in miliseconds before the cookie expires - (default: 24 hours) - * session_key -- The cookie key name to store the session data in - (default: _node) - * path -- The path to use for the cookie (default: '/') - * domain -- (optional) Define a specific domain/subdomain scope for the cookie + * secret -- The secret to encrypt the session data with. It has to be 32 + bytes long. + * timeout -- The amount of time in miliseconds before the cookie expires + (default: 24 hours) + * sessionKey -- The cookie key name to store the session data in (default: + \_node) + * path -- The path to use for the cookie (default: '/') + * domain -- (optional) Define a specific domain/subdomain scope for the + cookie + * secure -- (optional) Boolean, If true, the secure cookie flag will be set + * sessionCookie -- (optional) Boolean, if true, it's considered a session + cookie and no "expires" is set ## Why store session data in cookies? @@ -47,3 +54,24 @@ number of options: __In summary:__ don't use cookie storage if you keep a lot of data in your sessions! + +## Migrating to version 1.0.0 + +* Any cookie created with 0.0.2 version will be invalidated. +* The secret used to encrypt the sessions data can no longer be of any length. + It MUST be 32 bytes long and passed as either a Buffer object or a hex + encoded string. +* The `options` object has two naming changes: + * `sessionKey` instead of `session_key` + * `sessionCookie` instead of `session_cookie` +* The following exported functions are now async and expect a callback: + * readSession + * serialize + * deserialize + * encrypt + * decrypt +* The following exported functions have been removed: + * readCookies + * checkLength + * headersToArray + * hmac\_signature From fb6f167ecd5ab8b84c802f16a4e0958a441eea86 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 12:53:38 +0100 Subject: [PATCH 09/26] Update package.json --- package-lock.json | 2251 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 47 +- 2 files changed, 2278 insertions(+), 20 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e60a8a3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2251 @@ +{ + "name": "cookie-sessions", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.3.tgz", + "integrity": "sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ==", + "requires": { + "@babel/types": "^7.1.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.3.tgz", + "integrity": "sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w==" + }, + "@babel/template": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", + "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.1.2", + "@babel/types": "^7.1.2" + } + }, + "@babel/traverse": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.4.tgz", + "integrity": "sha512-my9mdrAIGdDiSVBuMjpn/oXYpva0/EZwWL3sm3Wcy/AVWO2eXnsoZruOT9jOGNRXU8KbCIu5zsKnXcAJ6PcV6Q==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.1.3", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.1.3", + "@babel/types": "^7.1.3", + "debug": "^3.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" + } + }, + "@babel/types": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz", + "integrity": "sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + } + }, + "@sinonjs/commons": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.0.2.tgz", + "integrity": "sha512-WR3dlgqJP4QNrLC4iXN/5/2WaLQQ0VijOOkmflqFGVJ6wLEpbSjo7c0ZeGIdtY8Crk7xBBp87sM6+Mkerz7alw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.0.0.tgz", + "integrity": "sha512-vdjoYLDptCgvtJs57ULshak3iJe4NW3sJ3g36xVDGff5AE8P30S6A093EIEPjdi2noGhfuNOEkbxt3J3awFW1w==", + "dev": true, + "requires": { + "@sinonjs/samsam": "2.1.0" + }, + "dependencies": { + "@sinonjs/samsam": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.0.tgz", + "integrity": "sha512-5x2kFgJYupaF1ns/RmharQ90lQkd2ELS8A9X0ymkAAdemYHGtI2KiUHG8nX2WU0T1qgnOU5YMqnBM2V7NUanNw==", + "dev": true, + "requires": { + "array-from": "^2.1.1" + } + } + } + }, + "@sinonjs/samsam": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.2.tgz", + "integrity": "sha512-ZwTHAlC9akprWDinwEPD4kOuwaYZlyMwVJIANsKNC3QVp0AHB04m7RnB4eqeWfgmxw8MGTzS9uMaw93Z3QcZbw==", + "dev": true + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "optional": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "auth0-magic": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/auth0-magic/-/auth0-magic-1.0.3.tgz", + "integrity": "sha512-DfgCBot9uEbnm0f5Oku3VtuR68P8YJUsImfS2n2PYVPEDUjmDr8bZqcGFxhvmBNFqeHsQ/RByvBXgRK2tri8/w==", + "requires": { + "bcrypt": "3.0.0", + "libsodium-wrappers-sumo": "0.7.3" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.0.tgz", + "integrity": "sha512-gjicxsD4e5U3nH0EqiEb5y+fKpsZ7F52wcnmNfu45nxnolWVAYh7NgbdfilY+5x1v6cLspxmzz4hf+ju2pFxhA==", + "requires": { + "nan": "2.10.0", + "node-pre-gyp": "0.10.2" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.3", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true + }, + "needle": { + "version": "2.2.1", + "bundled": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.2", + "bundled": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.3.5", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sax": { + "version": "1.2.4", + "bundled": true + }, + "semver": { + "version": "5.5.0", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "4.4.4", + "bundled": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.3", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "commander": { + "version": "2.15.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "requires": { + "lodash": "^4.17.10" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==" + }, + "istanbul-lib-instrument": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.0.0.tgz", + "integrity": "sha512-eQY9vN9elYjdgN9Iv6NS/00bptm02EBBk70lRMaVjeA6QYocQgenVrSgC28TJurdnZa80AGO3ASdFN+w/njGiQ==", + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.1", + "semver": "^5.5.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + } + } + }, + "jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" + }, + "just-extend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-3.0.0.tgz", + "integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "libsodium-sumo": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.3.tgz", + "integrity": "sha512-zCik0rx9XVNKMLY/t+kI6L/+LGomPx/UZ5iiU2YzCDN2K3GPxY2e0OnjJuX/W3/03fRV7W0btNOOgWpvab2P5g==" + }, + "libsodium-wrappers-sumo": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.3.tgz", + "integrity": "sha512-JNEZ74zu83BiPxXiYCexVOJdLaNpQTig5bC28MsPzBjzUwIDdLSabEfQNbEtODaNcqq3UlEgYa8IGvDF1jrINA==", + "requires": { + "libsodium-sumo": "0.7.3" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lolex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", + "integrity": "sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.10", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "2.10.0", + "resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + }, + "nise": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.6.tgz", + "integrity": "sha512-1GedetLKzmqmgwabuMSqPsT7oumdR77SBpDfNNJhADRIeA3LN/2RVqR4fFqwvzhAqcTef6PPCzQwITE/YQ8S8A==", + "dev": true, + "requires": { + "@sinonjs/formatio": "3.0.0", + "just-extend": "^3.0.0", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "requires": { + "abbrev": "1" + } + }, + "nyc": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.1.0.tgz", + "integrity": "sha512-3GyY6TpQ58z9Frpv4GMExE1SV2tAgYqC7HSy2omEhNiCT3mhT9NyiOvIE8zkbuJVFzmvvNTnE4h/7/wQae7xLg==", + "requires": { + "archy": "^1.0.0", + "arrify": "^1.0.1", + "caching-transform": "^2.0.0", + "convert-source-map": "^1.6.0", + "debug-log": "^1.0.1", + "find-cache-dir": "^2.0.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.1", + "istanbul-lib-hook": "^2.0.1", + "istanbul-lib-instrument": "^3.0.0", + "istanbul-lib-report": "^2.0.2", + "istanbul-lib-source-maps": "^2.0.1", + "istanbul-reports": "^2.0.1", + "make-dir": "^1.3.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.0.0", + "uuid": "^3.3.2", + "yargs": "11.1.0", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true + }, + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "append-transform": { + "version": "1.0.0", + "bundled": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true + }, + "async": { + "version": "1.5.2", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true + }, + "caching-transform": { + "version": "2.0.0", + "bundled": true, + "requires": { + "make-dir": "^1.0.0", + "md5-hex": "^2.0.0", + "package-hash": "^2.0.0", + "write-file-atomic": "^2.0.0" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "convert-source-map": { + "version": "1.6.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "default-require-extensions": { + "version": "2.0.0", + "bundled": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "bundled": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-error": { + "version": "4.1.1", + "bundled": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "find-cache-dir": { + "version": "2.0.0", + "bundled": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "istanbul-lib-coverage": { + "version": "2.0.1", + "bundled": true + }, + "istanbul-lib-hook": { + "version": "2.0.1", + "bundled": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.2", + "bundled": true, + "requires": { + "istanbul-lib-coverage": "^2.0.1", + "make-dir": "^1.3.0", + "supports-color": "^5.4.0" + } + }, + "istanbul-lib-source-maps": { + "version": "2.0.1", + "bundled": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^2.0.1", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true + } + } + }, + "istanbul-reports": { + "version": "2.0.1", + "bundled": true, + "requires": { + "handlebars": "^4.0.11" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash.flattendeep": { + "version": "4.4.0", + "bundled": true + }, + "longest": { + "version": "1.0.1", + "bundled": true + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "md5-hex": { + "version": "2.0.0", + "bundled": true, + "requires": { + "md5-o-matic": "^0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.10", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "ms": { + "version": "2.0.0", + "bundled": true + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "p-limit": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "bundled": true + }, + "package-hash": { + "version": "2.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "lodash.flattendeep": "^4.4.0", + "md5-hex": "^2.0.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true + }, + "path-type": { + "version": "3.0.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "bundled": true + }, + "pkg-dir": { + "version": "3.0.0", + "bundled": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "read-pkg": { + "version": "3.0.0", + "bundled": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "bundled": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "release-zalgo": { + "version": "1.0.0", + "bundled": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "semver": { + "version": "5.5.0", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "optional": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "bundled": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "test-exclude": { + "version": "5.0.0", + "bundled": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + } + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "uuid": { + "version": "3.3.2", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true + }, + "yargs": { + "version": "11.1.0", + "bundled": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "bundled": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true + } + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + }, + "sinon": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.0.0.tgz", + "integrity": "sha512-8OrSYFPZ9xaECfi1ayVMd0ihYCW2OZYgX3rBczrB990gHZMM+aftvhNTJazGz/luS0Us9NWgD5P3KGQ7kYZvGg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "@sinonjs/formatio": "^3.0.0", + "@sinonjs/samsam": "^2.1.2", + "diff": "^3.5.0", + "lodash.get": "^4.4.2", + "lolex": "^3.0.0", + "nise": "^1.4.5", + "supports-color": "^5.5.0", + "type-detect": "^4.0.8" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "http://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/package.json b/package.json index 12104e4..cb13e27 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,29 @@ -{ "name": "cookie-sessions" -, "description": "Secure cookie-based session middleware for Connect" -, "main": "./index" -, "author": "Caolan McMahon" -, "version": "0.0.2" -, "scripts": { - "test": "nodeunit test" -} -, "repository" : - { "type" : "git" - , "url" : "http://github.com/caolan/cookie-sessions.git" - } -, "bugs" : { "url" : "http://github.com/caolan/cookie-sessions/issues" } -, "devDependencies" : { - "nodeunit" : "*" -} -, "licenses" : - [ { "type" : "MIT" - , "url" : "http://github.com/caolan/cookie-sessions/raw/master/LICENSE" +{ + "name": "cookie-sessions", + "description": "Secure cookie-based session middleware for Express", + "main": "./lib/cookie-sessions", + "version": "1.0.0", + "scripts": { + "test": "NODE_PATH=. mocha ./test" + }, + "repository": { + "type": "git", + "url": "http://github.com/auth0/cookie-sessions.git" + }, + "bugs": { + "url": "http://github.com/auth0/cookie-sessions/issues" + }, + "devDependencies": { + "mocha": "^5.2.0", + "sinon": "^7.0.0" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://github.com/caolan/cookie-sessions/raw/master/LICENSE" } - ] + ], + "dependencies": { + "auth0-magic": "1.0.3" + } } From 0e816d458237332dad605c4f11aecae8d911491a Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 12:53:57 +0100 Subject: [PATCH 10/26] Include .editorconfig file --- .editorconfig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..150a05b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = true From bf28440776f9bf076a2400990ba4e29846a49526 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 23:56:34 +0100 Subject: [PATCH 11/26] add httpOnly and secure flag in options but default true --- lib/cookie-sessions.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index 0f255d0..5e13f19 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -8,7 +8,9 @@ var exports = module.exports = function(settings) { var defaultSettings = { sessionKey: '_node', timeout: 1000 * 60 * 60 * 24, // 24 hours - path: '/' + path: '/', + httpOnly: true, + secure: true }; var opts = Object.assign({}, defaultSettings, settings) @@ -43,6 +45,7 @@ var exports = module.exports = function(settings) { path: opts.path, httpOnly: true, domain: opts.domain, + httpOnly: opts.httpOnly, secure: opts.secure }; From fbaa52d22013fee80d5d915302f1bab453487ef2 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 23:57:34 +0100 Subject: [PATCH 12/26] add support for samesite cookie flag --- lib/cookie-sessions.js | 9 +++++++-- test/cookie-sessions.test.js | 12 ++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index 5e13f19..ed14b51 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -1,7 +1,7 @@ var magic = require('auth0-magic') var url = require('url'); -var MAX_LENGTH = 4096; +const MAX_LENGTH = 4096; var exports = module.exports = function(settings) { // don't set a default cookie secret, must be explicitly defined @@ -27,6 +27,10 @@ var exports = module.exports = function(settings) { throw new Error('Invalid cookie path, must start with "/"'); } + if (opts.sameSite && (opts.sameSite !== 'lax' || opts.sameSite !== 'strict')) { + throw new Error('Possible values for sameSite option: "lax" or "strict"'); + } + return function(req, res, next) { // if the request is not under the specified path, do nothing. if (req.path.indexOf(opts.path) !== 0) { @@ -46,7 +50,8 @@ var exports = module.exports = function(settings) { httpOnly: true, domain: opts.domain, httpOnly: opts.httpOnly, - secure: opts.secure + secure: opts.secure, + sameSite: opts.sameSite }; // If the session is undefined, no cookie is sent (and the cookie is diff --git a/test/cookie-sessions.test.js b/test/cookie-sessions.test.js index 073a6f9..135e9e9 100644 --- a/test/cookie-sessions.test.js +++ b/test/cookie-sessions.test.js @@ -101,7 +101,7 @@ describe('cookie sessions tests', () => { }) describe('onInit', () => { - it('throw exception if no secret set in server settings', () => { + it('throw error if no secret set in server settings', () => { try { sessions({}) } catch(err) { @@ -109,7 +109,7 @@ describe('cookie sessions tests', () => { assert.equal(err.message, 'No secret set in cookie-session settings') } }); - it('throw exception for invalid path', () => { + it('throw error for invalid path', () => { try { sessions({path: 'foo/bar'}) } catch(err) { @@ -127,5 +127,13 @@ describe('cookie sessions tests', () => { } session(req, res, next) }); + it('throw error for invalid value in sameSite option', () => { + try { + sessions({secret: secret, sameSite: true}) + } catch(err) { + assert.ok(err) + assert.equal(err.message, 'Possible values for sameSite option: "lax" or "strict"') + } + }); }) }) From 58dd48facf4ef5e72ce80860d679aa7cace81dfc Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 22 Oct 2018 23:57:49 +0100 Subject: [PATCH 13/26] add options in table and mention cookie-parser --- README.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index eafdaa0..2a00301 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ Session data is stored on the request object in the 'session' property: }) ); ``` +The middleware requires that +[cookie-parser](https://www.npmjs.com/package/cookie-parser) middleware is also +used. The session data can be any JSON object. It's timestamped, encrypted and authenticated automatically. The authenticated encryption uses @@ -24,18 +27,17 @@ function. The httpOnly cookie flag is set by default. The main function accepts a number of options: - * secret -- The secret to encrypt the session data with. It has to be 32 - bytes long. - * timeout -- The amount of time in miliseconds before the cookie expires - (default: 24 hours) - * sessionKey -- The cookie key name to store the session data in (default: - \_node) - * path -- The path to use for the cookie (default: '/') - * domain -- (optional) Define a specific domain/subdomain scope for the - cookie - * secure -- (optional) Boolean, If true, the secure cookie flag will be set - * sessionCookie -- (optional) Boolean, if true, it's considered a session - cookie and no "expires" is set +| Option | Required | Description | Default | +|---------------|----------|-------------------------------------------------------------------------------------------------------------------------|----------| +| secret | Yes | The secret to encrypt the session data, which must be 32 bytes long; i.e., a 32-byte buffer or 64-character hex string. | | +| timeout | Yes | The amount of time in milliseconds before the cookie expires. | 24 hours | +| sessionKey | Yes | The cookie key name in which to store the session data. | `\_node` | +| path | Yes | The path to use for the cookie. | `/` | +| domain | No | Define a specific domain/subdomain scope for the cookie. | | +| httpOnly | No | Boolean: if true, the httpOnly cookie flag will be set. | true | +| secure | No | Boolean: if true, the secure cookie flag will be set. | true | +| sameSite | No | If set to "lax" or "strict", the sameSite cookie flag with the corresponding mode will be set. | | +| sessionCookie | No | Boolean: if true, it's considered a session cookie and no "expires" is set. | | ## Why store session data in cookies? From 143ba2e993d8c9c077e85c6feb2754f75d27a711 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Wed, 24 Oct 2018 10:25:57 +0100 Subject: [PATCH 14/26] fix nit in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a00301..f999d7b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Session data is stored on the request object in the 'session' property: app.use( cookieSessions({ - session_key: 'session_data', + sessionKey: 'session_data', secret: process.env.SECRET }) ); From ec8b734d73a0631751dc074bc2c7bf2d9b91d8c2 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Sun, 28 Oct 2018 15:51:54 -0700 Subject: [PATCH 15/26] refactored library, overrode res.writeHeads --- lib/cookie-sessions.js | 233 +++--- package-lock.json | 1621 ++++++++++++++++++++-------------------- package.json | 10 +- 3 files changed, 946 insertions(+), 918 deletions(-) diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index ed14b51..e438bd2 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -1,27 +1,24 @@ -var magic = require('auth0-magic') -var url = require('url'); +var _ = require('lodash') +var crypto = require('crypto') const MAX_LENGTH = 4096; -var exports = module.exports = function(settings) { - // don't set a default cookie secret, must be explicitly defined - var defaultSettings = { - sessionKey: '_node', - timeout: 1000 * 60 * 60 * 24, // 24 hours - path: '/', - httpOnly: true, - secure: true - }; +// don't set a default cookie secret, must be explicitly defined +const DEFAULT_SETTINGS = { + name: '_node', + timeout: 1000 * 60 * 60 * 24, // 24 hours + path: '/', + autoRenew: true, + httpOnly: true, + secure: true +}; - var opts = Object.assign({}, defaultSettings, settings) +var exports = module.exports = function(settings) { + var opts = Object.assign({}, DEFAULT_SETTINGS, settings) if (!opts.secret) { throw new Error('No secret set in cookie-session settings'); } - if ((typeof opts.secret === 'string' && opts.secret.length !== 64) || - (Buffer.isBuffer(opts.secret) && opts.secret.length !== 32)) { - throw new Error('Invalid secret length'); - } if (typeof opts.path !== 'string' || opts.path.indexOf('/') !== 0) { throw new Error('Invalid cookie path, must start with "/"'); @@ -32,147 +29,141 @@ var exports = module.exports = function(settings) { } return function(req, res, next) { + // if the request is not under the specified path, do nothing. if (req.path.indexOf(opts.path) !== 0) { next(); return; } + req.session = {} // Read session data from a request and store it in req.session - exports.readSession(opts.session_key, opts.secret, opts.timeout, req, (err, session) => { - if (err) { - session = null - } - req.session = session; - - var options = { - path: opts.path, - httpOnly: true, - domain: opts.domain, - httpOnly: opts.httpOnly, - secure: opts.secure, - sameSite: opts.sameSite - }; - - // If the session is undefined, no cookie is sent (and the cookie is - // cleared if it is already set). If a session exists, we add a - // Set-Cookie header to all responses with the session data and the - // current timestamp. The cookie needs to be set on every response so - // that the timestamp is up to date, and the session does not expire - // unless the user is inactive. - if (req.session === null) { - if (req.cookies[opts.session_key]) { - if (!opts.sessionCookie) { - options.expires = exports.expires(0); - }; - - res.cookie(opts.session_key, '', options) - } - next(); - return; - } else { - if (!opts.sessionCookie) { - options.expires = exports.expires(opts.timeout); - } - exports.serialize(opts.secret, req.session, (err, cookieData) => { - if (err) { - next(err) - return; + var session = { + data: {} + } + try { + session = exports.deserialize(opts.secret, req.cookies[opts.name], opts.timeout); + req.session = session.data + } catch (err) { + } + var oldSession = _.cloneDeep(req.session) + + var options = _.pick(opts, [ 'path', 'domain', 'httpOnly', 'secure', 'sameSite' ]) + + var _writeHead = res.writeHead; + res.writeHead = function() { + // only set cookie if autoRenew is on or session data changed + if (opts.autoRenew || !_.isEqual(oldSession, req.session)) { + /* + * If the session is empty, no cookie is sent (and the cookie is + * cleared if it is already set). If a session exists, we add a + * Set-Cookie header to all responses with the session data and the + * current timestamp. The cookie needs to be set on every response so + * that the timestamp is up to date, and the session does not expire + * unless the user is inactive. + */ + if (_.isEqual(req.session, {})) { + if (req.cookies[opts.name]) { + res.clearCookie(opts.name, options) + } + } else { + if ((!opts.sessionCookie || !opts.autoRenew) && opts.timeout) { + options.expires = new Date(Date.now() + opts.timeout); } - res.cookie(opts.session_key, cookieData, options) - next(); - return; - }) + try { + cookiestr = exports.serialize(opts.secret, req.session, session.time, opts.autoRenew) + } catch(err) { + cookiestr = '' + } + res.cookie(opts.name, cookiestr, options) + } } - }) + + return _writeHead.apply(res, arguments); + } + next() }; }; exports.split = function(str){ var arr = str.split('$') return { - nonce: arr[0], - ciphertext: arr[1] + ciphertext: arr[0], + iv: arr[1], + authTag: arr[2] }; }; +exports.merge = function(obj) { + return [ obj.ciphertext, obj.iv, obj.authTag ].join('$') +}; exports.valid = function(timestamp, timeout) { return timestamp + timeout > Date.now(); } -exports.expires = function(timeout) { - if (timeout) { - return new Date(Date.now() + timeout); +exports.encrypt = function(secret, message) { + try { + var iv = crypto.randomBytes(16); + var cipher = crypto.createCipheriv('aes-256-gcm', secret, iv); + var ciphertext = cipher.update(message, 'utf8', 'hex'); + ciphertext += cipher.final('hex'); + return { + ciphertext: ciphertext, + iv: iv.toString('hex'), + authTag: cipher.getAuthTag().toString('hex') + }; + } catch(err) { + throw new Error('Failed to encrypt cookie') } - return null; -}; - -exports.encrypt = function(secret, message, cb) { - magic.encrypt.sync(message, secret, (err, output) => { - if (err) { - return cb(err) - } - cb( - null, - { ciphertext: output.ciphertext.toString('hex'), - nonce: output.nonce.toString('hex') - } - ) - }) }; -exports.decrypt = function(secret, ciphertext, nonce, cb) { - magic.decrypt.sync(secret, ciphertext, nonce, (err, plaintext) => { - if (err) { - return cb(err) - } - cb(null, plaintext.toString('utf-8')) - }); +exports.decrypt = function(secret, ciphertext, iv, authTag) { + try { + var decipher = crypto.createDecipheriv('aes-256-gcm', secret , Buffer.from(iv, 'hex')); + decipher.setAuthTag(Buffer.from(authTag, 'hex')); + var plaintext = decipher.update(ciphertext, 'hex', 'utf8'); + plaintext += decipher.final('utf8'); + return plaintext; + } catch(err) { + throw new Error('Failed to decrypt and authenticate cookie') + } }; // Turns a JSON-compatibile object literal into a secure cookie string -exports.serialize = function(secret, data, cb) { +exports.serialize = function(secret, data, sessionTime, autoRenew) { + if (autoRenew || typeof sessionTime === 'undefined') { + sessionTime = Date.now() + } var cookieData = { d: data, - t: Date.now() + t: sessionTime } - var data_str = JSON.stringify(cookieData); - exports.encrypt(secret, data_str, (err, result) => { - if (err) { - return cb(new Error('Failed to encrypt cookie')) - } - var result = result.nonce + '$' + result.ciphertext; - if (result.length > MAX_LENGTH) { - return cb(new Error('Data too long to store in a cookie')) ; - } - cb(null, result); - }) + var dataStr = JSON.stringify(cookieData); + var enc = exports.encrypt(secret, dataStr) + var result = exports.merge(enc) + if (result.length > MAX_LENGTH) { + throw Error('Data too long to store in a cookie') ; + } + return result } // Parses a secure cookie string, returning the object stored within it. -exports.deserialize = function(secret, str, timeout, cb) { - var splited = exports.split(str) - exports.decrypt(secret, splited.ciphertext, splited.nonce, (err, cookieData) => { - if (err) { - cb(new Error('Failed to decrypt cookie')) - return; +exports.deserialize = function(secret, str, timeout) { + if (!str) { + return { + data: {} } - var jsonData = JSON.parse(cookieData) - - if (!exports.valid(jsonData.t, timeout)) { - return cb(new Error('Cookie expired')) - } - cb(null, jsonData.d); - }); -}; + } + var splited = exports.split(str) + cookieData = exports.decrypt(secret, splited.ciphertext, splited.iv, splited.authTag) + var jsonData = JSON.parse(cookieData) - // Reads the session data stored in the cookie named 'key' if it validates, - // otherwise returns null. -exports.readSession = function(key, secret, timeout, req, cb) { - if (req.cookies && req.cookies[key]) { - exports.deserialize(secret, req.cookies[key], timeout, cb); - } else { - cb(null, null); + if (!exports.valid(jsonData.t, timeout)) { + throw new Error('Cookie expired') + } + return { + data: jsonData.d, + time: jsonData.t } }; diff --git a/package-lock.json b/package-lock.json index e60a8a3..cc873bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, "requires": { "@babel/highlight": "^7.0.0" } @@ -16,25 +17,20 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.3.tgz", "integrity": "sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ==", + "dev": true, "requires": { "@babel/types": "^7.1.3", "jsesc": "^2.5.1", "lodash": "^4.17.10", "source-map": "^0.5.0", "trim-right": "^1.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } } }, "@babel/helper-function-name": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", "@babel/template": "^7.1.0", @@ -45,6 +41,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -53,6 +50,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -61,6 +59,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -70,12 +69,14 @@ "@babel/parser": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.3.tgz", - "integrity": "sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w==" + "integrity": "sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w==", + "dev": true }, "@babel/template": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.1.2", @@ -86,6 +87,7 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.4.tgz", "integrity": "sha512-my9mdrAIGdDiSVBuMjpn/oXYpva0/EZwWL3sm3Wcy/AVWO2eXnsoZruOT9jOGNRXU8KbCIu5zsKnXcAJ6PcV6Q==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/generator": "^7.1.3", @@ -102,6 +104,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz", "integrity": "sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==", + "dev": true, "requires": { "esutils": "^2.0.2", "lodash": "^4.17.10", @@ -143,32 +146,30 @@ "integrity": "sha512-ZwTHAlC9akprWDinwEPD4kOuwaYZlyMwVJIANsKNC3QVp0AHB04m7RnB4eqeWfgmxw8MGTzS9uMaw93Z3QcZbw==", "dev": true }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "optional": true + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true }, "array-from": { "version": "2.1.1", @@ -176,452 +177,44 @@ "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "auth0-magic": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/auth0-magic/-/auth0-magic-1.0.3.tgz", - "integrity": "sha512-DfgCBot9uEbnm0f5Oku3VtuR68P8YJUsImfS2n2PYVPEDUjmDr8bZqcGFxhvmBNFqeHsQ/RByvBXgRK2tri8/w==", - "requires": { - "bcrypt": "3.0.0", - "libsodium-wrappers-sumo": "0.7.3" - } + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, - "bcrypt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.0.tgz", - "integrity": "sha512-gjicxsD4e5U3nH0EqiEb5y+fKpsZ7F52wcnmNfu45nxnolWVAYh7NgbdfilY+5x1v6cLspxmzz4hf+ju2pFxhA==", + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.10.2" + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true - }, - "iconv-lite": { - "version": "0.4.23", - "bundled": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minipass": { - "version": "2.3.3", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true - }, - "needle": { - "version": "2.2.1", - "bundled": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.2", - "bundled": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } - }, - "readable-stream": { - "version": "2.3.5", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true - }, - "sax": { - "version": "1.2.4", - "bundled": true - }, - "semver": { - "version": "5.5.0", - "bundled": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.0.3", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true - }, - "tar": { - "version": "4.4.4", - "bundled": true, - "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.3", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "ms": "2.0.0" } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true } } }, @@ -629,6 +222,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -640,10 +234,17 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -654,6 +255,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -661,7 +263,17 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } }, "commander": { "version": "2.15.1", @@ -669,15 +281,69 @@ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "dev": true, + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, "requires": { "ms": "^2.1.1" }, @@ -685,14 +351,28 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true }, "diff": { "version": "3.5.0", @@ -700,42 +380,157 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true }, - "escodegen": { + "etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dev": true, "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + } } }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" + "finalhandler": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true }, "fs.realpath": { "version": "1.0.0", @@ -743,22 +538,11 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "globals": { "version": "11.8.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", - "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==" + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", + "dev": true }, "growl": { "version": "1.10.5", @@ -766,36 +550,11 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "^4.17.10" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "he": { "version": "1.1.1", @@ -803,10 +562,32 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "http-errors": { + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -815,7 +596,14 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true }, "isarray": { "version": "0.0.1", @@ -823,56 +611,17 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==" + "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==", + "dev": true }, "istanbul-lib-instrument": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.0.0.tgz", "integrity": "sha512-eQY9vN9elYjdgN9Iv6NS/00bptm02EBBk70lRMaVjeA6QYocQgenVrSgC28TJurdnZa80AGO3ASdFN+w/njGiQ==", + "dev": true, "requires": { "@babel/generator": "^7.0.0", "@babel/parser": "^7.0.0", @@ -886,28 +635,14 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - } - } + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "jsesc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", + "dev": true }, "just-extend": { "version": "3.0.0", @@ -915,28 +650,6 @@ "integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==", "dev": true }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "libsodium-sumo": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.3.tgz", - "integrity": "sha512-zCik0rx9XVNKMLY/t+kI6L/+LGomPx/UZ5iiU2YzCDN2K3GPxY2e0OnjJuX/W3/03fRV7W0btNOOgWpvab2P5g==" - }, - "libsodium-wrappers-sumo": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.3.tgz", - "integrity": "sha512-JNEZ74zu83BiPxXiYCexVOJdLaNpQTig5bC28MsPzBjzUwIDdLSabEfQNbEtODaNcqq3UlEgYa8IGvDF1jrINA==", - "requires": { - "libsodium-sumo": "0.7.3" - } - }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", @@ -954,23 +667,59 @@ "integrity": "sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ==", "dev": true }, + "media-typer": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "0.0.10", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - }, "mkdirp": { "version": "0.5.1", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" }, @@ -978,7 +727,8 @@ "minimist": { "version": "0.0.8", "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true } } }, @@ -1038,10 +788,11 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "nan": { - "version": "2.10.0", - "resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true }, "nise": { "version": "1.4.6", @@ -1064,18 +815,11 @@ } } }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1" - } - }, "nyc": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.1.0.tgz", "integrity": "sha512-3GyY6TpQ58z9Frpv4GMExE1SV2tAgYqC7HSy2omEhNiCT3mhT9NyiOvIE8zkbuJVFzmvvNTnE4h/7/wQae7xLg==", + "dev": true, "requires": { "archy": "^1.0.0", "arrify": "^1.0.1", @@ -1107,6 +851,7 @@ "align-text": { "version": "0.1.4", "bundled": true, + "dev": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -1115,38 +860,46 @@ }, "amdefine": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "ansi-regex": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "append-transform": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "default-require-extensions": "^2.0.0" } }, "archy": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "arrify": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "async": { "version": "1.5.2", - "bundled": true + "bundled": true, + "dev": true }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1154,11 +907,13 @@ }, "builtin-modules": { "version": "1.1.1", - "bundled": true + "bundled": true, + "dev": true }, "caching-transform": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "make-dir": "^1.0.0", "md5-hex": "^2.0.0", @@ -1169,11 +924,13 @@ "camelcase": { "version": "1.2.1", "bundled": true, + "dev": true, "optional": true }, "center-align": { "version": "0.1.3", "bundled": true, + "dev": true, "optional": true, "requires": { "align-text": "^0.1.3", @@ -1183,6 +940,7 @@ "cliui": { "version": "2.1.0", "bundled": true, + "dev": true, "optional": true, "requires": { "center-align": "^0.1.1", @@ -1193,25 +951,30 @@ "wordwrap": { "version": "0.0.2", "bundled": true, + "dev": true, "optional": true } } }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "commondir": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "dev": true }, "convert-source-map": { "version": "1.6.0", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -1219,6 +982,7 @@ "cross-spawn": { "version": "4.0.2", "bundled": true, + "dev": true, "requires": { "lru-cache": "^4.0.1", "which": "^1.2.9" @@ -1227,21 +991,25 @@ "debug": { "version": "3.1.0", "bundled": true, + "dev": true, "requires": { "ms": "2.0.0" } }, "debug-log": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "decamelize": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "default-require-extensions": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "strip-bom": "^3.0.0" } @@ -1249,17 +1017,20 @@ "error-ex": { "version": "1.3.2", "bundled": true, + "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "es6-error": { "version": "4.1.1", - "bundled": true + "bundled": true, + "dev": true }, "execa": { "version": "0.7.0", "bundled": true, + "dev": true, "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -1273,6 +1044,7 @@ "cross-spawn": { "version": "5.1.0", "bundled": true, + "dev": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -1284,6 +1056,7 @@ "find-cache-dir": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "commondir": "^1.0.1", "make-dir": "^1.0.0", @@ -1293,6 +1066,7 @@ "find-up": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -1300,6 +1074,7 @@ "foreground-child": { "version": "1.5.6", "bundled": true, + "dev": true, "requires": { "cross-spawn": "^4", "signal-exit": "^3.0.0" @@ -1307,19 +1082,23 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "get-caller-file": { "version": "1.0.3", - "bundled": true + "bundled": true, + "dev": true }, "get-stream": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "glob": { "version": "7.1.3", "bundled": true, + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1331,11 +1110,13 @@ }, "graceful-fs": { "version": "4.1.11", - "bundled": true + "bundled": true, + "dev": true }, "handlebars": { "version": "4.0.11", "bundled": true, + "dev": true, "requires": { "async": "^1.4.0", "optimist": "^0.6.1", @@ -1346,6 +1127,7 @@ "source-map": { "version": "0.4.4", "bundled": true, + "dev": true, "requires": { "amdefine": ">=0.0.4" } @@ -1354,19 +1136,23 @@ }, "has-flag": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "hosted-git-info": { "version": "2.7.1", - "bundled": true + "bundled": true, + "dev": true }, "imurmurhash": { "version": "0.1.4", - "bundled": true + "bundled": true, + "dev": true }, "inflight": { "version": "1.0.6", "bundled": true, + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -1374,46 +1160,56 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "dev": true }, "invert-kv": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-arrayish": { "version": "0.2.1", - "bundled": true + "bundled": true, + "dev": true }, "is-buffer": { "version": "1.1.6", - "bundled": true + "bundled": true, + "dev": true }, "is-builtin-module": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "builtin-modules": "^1.0.0" } }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-stream": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "isexe": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "istanbul-lib-coverage": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true }, "istanbul-lib-hook": { "version": "2.0.1", "bundled": true, + "dev": true, "requires": { "append-transform": "^1.0.0" } @@ -1421,6 +1217,7 @@ "istanbul-lib-report": { "version": "2.0.2", "bundled": true, + "dev": true, "requires": { "istanbul-lib-coverage": "^2.0.1", "make-dir": "^1.3.0", @@ -1430,6 +1227,7 @@ "istanbul-lib-source-maps": { "version": "2.0.1", "bundled": true, + "dev": true, "requires": { "debug": "^3.1.0", "istanbul-lib-coverage": "^2.0.1", @@ -1440,24 +1238,28 @@ "dependencies": { "source-map": { "version": "0.6.1", - "bundled": true + "bundled": true, + "dev": true } } }, "istanbul-reports": { "version": "2.0.1", "bundled": true, + "dev": true, "requires": { "handlebars": "^4.0.11" } }, "json-parse-better-errors": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "kind-of": { "version": "3.2.2", "bundled": true, + "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -1465,11 +1267,13 @@ "lazy-cache": { "version": "1.0.4", "bundled": true, + "dev": true, "optional": true }, "lcid": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "invert-kv": "^1.0.0" } @@ -1477,6 +1281,7 @@ "load-json-file": { "version": "4.0.0", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -1487,6 +1292,7 @@ "locate-path": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -1494,15 +1300,18 @@ }, "lodash.flattendeep": { "version": "4.4.0", - "bundled": true + "bundled": true, + "dev": true }, "longest": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "lru-cache": { "version": "4.1.3", "bundled": true, + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -1511,6 +1320,7 @@ "make-dir": { "version": "1.3.0", "bundled": true, + "dev": true, "requires": { "pify": "^3.0.0" } @@ -1518,17 +1328,20 @@ "md5-hex": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "md5-o-matic": "^0.1.1" } }, "md5-o-matic": { "version": "0.1.1", - "bundled": true + "bundled": true, + "dev": true }, "mem": { "version": "1.1.0", "bundled": true, + "dev": true, "requires": { "mimic-fn": "^1.0.0" } @@ -1536,51 +1349,60 @@ "merge-source-map": { "version": "1.1.0", "bundled": true, + "dev": true, "requires": { "source-map": "^0.6.1" }, "dependencies": { "source-map": { "version": "0.6.1", - "bundled": true + "bundled": true, + "dev": true } } }, "mimic-fn": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "minimatch": { "version": "3.0.4", "bundled": true, + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.10", - "bundled": true + "bundled": true, + "dev": true }, "mkdirp": { "version": "0.5.1", "bundled": true, + "dev": true, "requires": { "minimist": "0.0.8" }, "dependencies": { "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "dev": true } } }, "ms": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "normalize-package-data": { "version": "2.4.0", "bundled": true, + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "is-builtin-module": "^1.0.0", @@ -1591,17 +1413,20 @@ "npm-run-path": { "version": "2.0.2", "bundled": true, + "dev": true, "requires": { "path-key": "^2.0.0" } }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "once": { "version": "1.4.0", "bundled": true, + "dev": true, "requires": { "wrappy": "1" } @@ -1609,6 +1434,7 @@ "optimist": { "version": "0.6.1", "bundled": true, + "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" @@ -1616,11 +1442,13 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "os-locale": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "execa": "^0.7.0", "lcid": "^1.0.0", @@ -1629,11 +1457,13 @@ }, "p-finally": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "p-limit": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -1641,17 +1471,20 @@ "p-locate": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "p-limit": "^2.0.0" } }, "p-try": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "package-hash": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.11", "lodash.flattendeep": "^4.4.0", @@ -1662,6 +1495,7 @@ "parse-json": { "version": "4.0.0", "bundled": true, + "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -1669,41 +1503,49 @@ }, "path-exists": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "path-key": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true }, "path-type": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "pify": "^3.0.0" } }, "pify": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "pkg-dir": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "find-up": "^3.0.0" } }, "pseudomap": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "read-pkg": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -1713,6 +1555,7 @@ "read-pkg-up": { "version": "4.0.0", "bundled": true, + "dev": true, "requires": { "find-up": "^3.0.0", "read-pkg": "^3.0.0" @@ -1721,29 +1564,35 @@ "release-zalgo": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "es6-error": "^4.0.1" } }, "repeat-string": { "version": "1.6.1", - "bundled": true + "bundled": true, + "dev": true }, "require-directory": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "require-main-filename": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "resolve-from": { "version": "4.0.0", - "bundled": true + "bundled": true, + "dev": true }, "right-align": { "version": "0.1.3", "bundled": true, + "dev": true, "optional": true, "requires": { "align-text": "^0.1.1" @@ -1752,45 +1601,54 @@ "rimraf": { "version": "2.6.2", "bundled": true, + "dev": true, "requires": { "glob": "^7.0.5" } }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "dev": true }, "semver": { "version": "5.5.0", - "bundled": true + "bundled": true, + "dev": true }, "set-blocking": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "shebang-command": { "version": "1.2.0", "bundled": true, + "dev": true, "requires": { "shebang-regex": "^1.0.0" } }, "shebang-regex": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "signal-exit": { "version": "3.0.2", - "bundled": true + "bundled": true, + "dev": true }, "source-map": { "version": "0.5.7", "bundled": true, + "dev": true, "optional": true }, "spawn-wrap": { "version": "1.4.2", "bundled": true, + "dev": true, "requires": { "foreground-child": "^1.5.6", "mkdirp": "^0.5.0", @@ -1803,6 +1661,7 @@ "spdx-correct": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -1810,11 +1669,13 @@ }, "spdx-exceptions": { "version": "2.1.0", - "bundled": true + "bundled": true, + "dev": true }, "spdx-expression-parse": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -1822,11 +1683,13 @@ }, "spdx-license-ids": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "string-width": { "version": "2.1.1", "bundled": true, + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -1835,21 +1698,25 @@ "strip-ansi": { "version": "4.0.0", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^3.0.0" } }, "strip-bom": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "strip-eof": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "supports-color": { "version": "5.4.0", "bundled": true, + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -1857,6 +1724,7 @@ "test-exclude": { "version": "5.0.0", "bundled": true, + "dev": true, "requires": { "arrify": "^1.0.1", "minimatch": "^3.0.4", @@ -1867,6 +1735,7 @@ "uglify-js": { "version": "2.8.29", "bundled": true, + "dev": true, "optional": true, "requires": { "source-map": "~0.5.1", @@ -1877,6 +1746,7 @@ "yargs": { "version": "3.10.0", "bundled": true, + "dev": true, "optional": true, "requires": { "camelcase": "^1.0.2", @@ -1890,15 +1760,18 @@ "uglify-to-browserify": { "version": "1.0.2", "bundled": true, + "dev": true, "optional": true }, "uuid": { "version": "3.3.2", - "bundled": true + "bundled": true, + "dev": true }, "validate-npm-package-license": { "version": "3.0.3", "bundled": true, + "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -1907,26 +1780,31 @@ "which": { "version": "1.3.1", "bundled": true, + "dev": true, "requires": { "isexe": "^2.0.0" } }, "which-module": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "window-size": { "version": "0.1.0", "bundled": true, + "dev": true, "optional": true }, "wordwrap": { "version": "0.0.3", - "bundled": true + "bundled": true, + "dev": true }, "wrap-ansi": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -1934,11 +1812,13 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1946,6 +1826,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1955,6 +1836,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1963,11 +1845,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "write-file-atomic": { "version": "2.3.0", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -1976,15 +1860,18 @@ }, "y18n": { "version": "3.2.1", - "bundled": true + "bundled": true, + "dev": true }, "yallist": { "version": "2.1.2", - "bundled": true + "bundled": true, + "dev": true }, "yargs": { "version": "11.1.0", "bundled": true, + "dev": true, "requires": { "cliui": "^4.0.0", "decamelize": "^1.1.1", @@ -2003,6 +1890,7 @@ "cliui": { "version": "4.1.0", "bundled": true, + "dev": true, "requires": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0", @@ -2012,6 +1900,7 @@ "find-up": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "locate-path": "^2.0.0" } @@ -2019,6 +1908,7 @@ "locate-path": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -2027,6 +1917,7 @@ "p-limit": { "version": "1.3.0", "bundled": true, + "dev": true, "requires": { "p-try": "^1.0.0" } @@ -2034,72 +1925,64 @@ "p-locate": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "p-limit": "^1.1.0" } }, "p-try": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true } } }, "yargs-parser": { "version": "9.0.2", "bundled": true, + "dev": true, "requires": { "camelcase": "^4.1.0" }, "dependencies": { "camelcase": { "version": "4.1.0", - "bundled": true + "bundled": true, + "dev": true } } } } }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-to-regexp": { "version": "1.7.0", @@ -2110,20 +1993,142 @@ "isarray": "0.0.1" } }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true }, "sinon": { "version": "7.0.0", @@ -2154,23 +2159,59 @@ } }, "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "optional": true, + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "requires": { - "amdefine": ">=0.0.4" + "safe-buffer": "~5.1.0" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + } + }, + "supertest": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.3.0.tgz", + "integrity": "sha512-dMQSzYdaZRSANH5LL8kX3UpgK9G1LRh/jnggs/TI0W2Sz7rkMx9Y48uia3K9NgcaWEV28tYkBnXE4tiFC77ygQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -2184,20 +2225,14 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true }, "type-detect": { "version": "4.0.8", @@ -2205,47 +2240,45 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - } - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, "requires": { - "isexe": "^2.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.18" } }, - "wordwrap": { + "unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true } } } diff --git a/package.json b/package.json index cb13e27..2a3c0cc 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "main": "./lib/cookie-sessions", "version": "1.0.0", "scripts": { - "test": "NODE_PATH=. mocha ./test" + "test": "NODE_PATH=. nyc mocha ./test" }, "repository": { "type": "git", @@ -14,8 +14,12 @@ "url": "http://github.com/auth0/cookie-sessions/issues" }, "devDependencies": { + "cookie-parser": "^1.4.3", + "express": "^4.16.4", "mocha": "^5.2.0", - "sinon": "^7.0.0" + "nyc": "^13.1.0", + "sinon": "^7.0.0", + "supertest": "^3.3.0" }, "licenses": [ { @@ -24,6 +28,6 @@ } ], "dependencies": { - "auth0-magic": "1.0.3" + "lodash": "^4.17.11" } } From ef21d360962035ebe778a19a8bc707da38d71249 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Sun, 28 Oct 2018 15:54:19 -0700 Subject: [PATCH 16/26] updated tests, added integration tests & coverage --- .gitignore | 2 + test/cookie-sessions.test.js | 262 ++++++++++++++++++++++++++--------- 2 files changed, 200 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index 3c3629e..1e59e82 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +.nyc_output +coverage diff --git a/test/cookie-sessions.test.js b/test/cookie-sessions.test.js index 135e9e9..8102040 100644 --- a/test/cookie-sessions.test.js +++ b/test/cookie-sessions.test.js @@ -2,13 +2,16 @@ var sessions = require('../lib/cookie-sessions'); var assert = require('assert') var sinon = require('sinon') var crypto = require('crypto') -var sandbox; +var request = require('supertest'); +var express = require('express'); +var cookieParser = require('cookie-parser') -describe('cookie sessions tests', () => { - var secret = '63e973cd82855e4f5669af6d7fa0ec4be02474871c155ead81a669474adc5e61' +describe('cookie sessions tests', function() { + var secret = '8c06bdc84bf095d76b18c1e3a485dfe6' var message = 'the answer to the ultimate question of life, the universe, and everything' - var encr = '8c17200a08910652154d41b561242f4ff4672e4caf2ac71fc29b40cbe8f1451519e333f26f6b87fbda5e29220171dc350d67b537ee7ac02d2e6dd4f25597de27727a70c2e196242026a15daffaaf6bc9230b03b5786879e9bd' - var nonce = '074800982ca4f120b37acaf86c4997769100843d466e5595' + var encr = '017cb25516cf63f676e680abf99e2797da7f0765b3f19e53adbdadfaf2cd8f9a9378a35b1e52fb00e2a5aa577eb277d98096dbb75afd9dd57b0498e54ead8fc66ed980bf1e60a1b2b8' + var iv = '55df03d8dd90145b7f23211613d46b2d' + var authTag = 'c9e0fe9af2412ce941d18e7eae667402' var user_data = { id: 123, username: 'foobar', @@ -17,91 +20,87 @@ describe('cookie sessions tests', () => { } beforeEach(function () { - sandbox = sinon.createSandbox(); + this.sandbox = sinon.createSandbox(); }); afterEach(function () { - sandbox.restore(); + this.sandbox.restore(); }); - describe('encrypt', () => { - it('should sucessfully return encrypted data', (done) => { - sessions.encrypt(secret, message, (err, result) => { - assert.ok(!err) - done(); - }); + describe('encrypt', function() { + it('should sucessfully return encrypted data', function() { + enc = sessions.encrypt(secret, message) + assert.ok(enc.ciphertext) + assert.ok(enc.iv) + assert.ok(enc.authTag) }) }) - describe('decrypt', () => { - it('should sucessfully return decrypted data', (done) => { - sessions.decrypt(secret, encr, nonce, (err, plaintext) => { - assert.equal(message, plaintext) - done(); - }); + describe('decrypt', function() { + it('should sucessfully return decrypted data', function() { + plaintext = sessions.decrypt(secret, encr, iv, authTag) + assert.equal(message, plaintext) }) }) - describe('serialize', () => { - it('should return a string', (done) => { - sessions.serialize(secret, user_data, (err, result) => { - assert.ok(!err) - var arr = result.split('$') - var nonce = arr[0] - var cipher = arr[1] - assert.ok(nonce) - assert.ok(cipher) - done(); - }); + describe('serialize', function() { + it('should return a string', function() { + var result = sessions.serialize(secret, user_data, 1540678757323, false) + var arr = result.split('$') + var cipher = arr[0] + var iv = arr[1] + var authTag = arr[2] + assert.ok(cipher) + assert.ok(iv) + assert.ok(authTag) }) - it('should return error on long data', (done) => { + it('should return error on long data', function() { var long = ''; - for(var i=0; i<2050; i++){ + for(var i = 0; i < 1500; i++){ long += 'a'; }; - sandbox.stub(sessions, 'encrypt').yields(null, {ciphertext: long, nonce: long}); - sessions.serialize(secret, user_data, (err, result) => { - assert.ok(err) + this.sandbox.stub(sessions, 'encrypt').returns({ciphertext: long, iv: long, authTag: long}); + try { + var cookiestr = sessions.serialize(secret, user_data) + } + catch(err) { assert.equal(err.message, 'Data too long to store in a cookie') - done(); - }); + }; }) }) - describe('deserialize', () => { - it('should return user data in JSON if cookie has not expired', (done) => { - sessions.serialize(secret, user_data, (err, result) => { - sessions.deserialize(secret, result, 1000 * 60 * 60 * 24, (err, data) => { - assert.ok(!err) - assert.deepEqual(data, user_data) - done(); - }); - }); + describe('deserialize', function() { + it('should return user data in JSON if cookie has not expired', function() { + var cookiestr = sessions.serialize(secret, user_data, Date.now(), false) + var jsonData = sessions.deserialize(secret, cookiestr, 1000 * 60 * 60 * 12) + assert.deepEqual(jsonData.data, user_data) }) - it('should return validation error if cookie has expired', (done) => { - var str = 'b5a8af11ce10cd632796df523c9142595b472b686738f6a6$29d28d5d9c63b3daf6d628626b5ce3d045dc506321579af014b7eda3665e84342f65c97f79df314b7991255a41585e325e7a6e7e0218485c27af644cc1517e81801dae85716deeea32fbdccebda1ed59a3b3a1ec689447d5a39d6a37c4541623ef93ffc0ca2fb17cd20e4708a7fab59e3f87c5c5afa277b66bd3' - sessions.deserialize(secret, str, 1000 * 60 * 60 * 24, (err, data) => { + it('should return validation error if cookie has expired', function() { + var str = '3d11d4581d2359247a4183fc3c84aceaa5835d38075117b10c1f8bcff4a63f0d3740f6ff30f60942b9851af38fd8f1d5bcf6b2376c6803f001a844dc71b5d50276cbb735f501c47d320ef6c6d07cd2047b7a6b26521c23687718ebf248bd9b$69d2b05d79b47c3e5a94b0f32f2fbe3b$ed4c06a57df5a2b646f7e5f82bffb17f' + try { + var jsonData = sessions.deserialize(secret, str, 1000 * 60 * 60 * 10) + } + catch(err) { assert.ok(err) assert.equal(err.message, 'Cookie expired') - done(); - }); + } }) }) - describe('readSession', () => { - it('session with key node_session read ok', () => { - sandbox.stub(sessions, 'deserialize').yields(null, {username: 'foobar'}); - sessions.readSession('node_session', secret, 12, {cookies: {node_session: 'lala'}} , (err, data) => { - assert.ok(!err) - assert.deepEqual({username: 'foobar'}, data) - }) + /* + describe('readSession', function() { + it('session with key node_session read ok', function() { + this.sandbox.stub(sessions, 'deserialize').returns({username: 'foobar'}); + var session = sessions.readSession('node_session', secret, 12, {cookies: {node_session: 'lala'}}) + assert.deepEqual({username: 'foobar'}, session) }); }) + */ - describe('onInit', () => { - it('throw error if no secret set in server settings', () => { + describe('onInit', function() { + it('throw error if no secret set in server settings', function() { try { sessions({}) } catch(err) { @@ -109,12 +108,12 @@ describe('cookie sessions tests', () => { assert.equal(err.message, 'No secret set in cookie-session settings') } }); - it('throw error for invalid path', () => { + it('throw error for invalid path', function() { try { - sessions({path: 'foo/bar'}) + sessions({secret: secret, path: 'foo/bar'}) } catch(err) { assert.ok(err) - assert.equal(err.message, 'No secret set in cookie-session settings') + assert.equal(err.message, 'Invalid cookie path, must start with "/"') } }); it('should do nothing for a request not under the specified path', (done) => { @@ -127,7 +126,7 @@ describe('cookie sessions tests', () => { } session(req, res, next) }); - it('throw error for invalid value in sameSite option', () => { + it('throw error for invalid value in sameSite option', function() { try { sessions({secret: secret, sameSite: true}) } catch(err) { @@ -136,4 +135,139 @@ describe('cookie sessions tests', () => { } }); }) + + describe('the middleware', function() { + beforeEach(function () { + this.app = express(); + }); + + describe('when session has already data', function() { + beforeEach(function() { + this.sandbox.stub(sessions, 'deserialize').returns({data: {username: 'foobar'}, time: 1540684048566}); + }); + + describe('and new data are added to req.session', function() { + describe('and autoRenew is set to true', function() { + it('should set a new cookie with the combined data and with new timestamp', function () { + this.app.use(cookieParser()) + this.app.use(sessions({secret: secret, autoRenew: true})) + this.app.get('/', (req, res) => { + req.session.koko = 'bar' + res.send('Hello World!') + }) + return request(this.app) + .get('/') + .then(res => { + this.sandbox.restore(); + assert(res.headers['set-cookie']) + _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] + var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) + assert.notEqual(jsonData.time, 1540684048566) + assert.deepEqual(jsonData.data, { username: 'foobar', koko: 'bar' }) + }) + }); + }); + describe('and autoRenew is set to false', function() { + it('should set a new cookie with the combined data and the initial timestamp', function () { + this.app.use(cookieParser()) + this.app.use(sessions({secret: secret, autoRenew: false})) + this.app.get('/', (req, res) => { + req.session.koko = 'bar' + res.send('Hello World!') + }) + return request(this.app) + .get('/') + .then(res => { + this.sandbox.restore(); + assert(res.headers['set-cookie']) + var _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] + var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) + assert.equal(jsonData.time, 1540684048566) + assert.deepEqual(jsonData.data, { username: 'foobar', koko: 'bar' }) + }) + }); + }); + }); + describe('and no data are added to req.session', function() { + describe('and autoRenew is set to true', function() { + it('should set a new cookie with new timestamp', function () { + this.app.use(cookieParser()) + this.app.use(sessions({secret: secret, autoRenew: true})) + this.app.get('/', (req, res) => { + res.send('Hello World!') + }) + return request(this.app) + .get('/') + .then(res => { + this.sandbox.restore(); + assert(res.headers['set-cookie']) + var _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] + var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) + assert.notEqual(jsonData.time, 1540684048566) + assert.deepEqual(jsonData.data, { username: 'foobar'}) + }) + }); + }); + describe('and autoRenew is set to false', function() { + it('should not set a new cookie', function () { + this.app.use(cookieParser()) + this.app.use(sessions({secret: secret, autoRenew: false})) + this.app.get('/', (req, res) => { + res.send('Hello World!') + }) + return request(this.app) + .get('/') + .then(res => { + this.sandbox.restore(); + assert(!res.headers['set-cookie']) + }) + }); + }); + }); + describe('data get deleted from req.session', function() { + it('should set a new cookie with empty data', function () { + this.app.use(cookieParser()) + this.app.use(sessions({secret: secret})) + this.app.get('/', (req, res) => { + req.session = {} + res.send('Hello World!') + }) + this.sandbox.restore(); + var cookiestr = sessions.serialize(secret, user_data, Date.now(), false) + return request(this.app) + .get('/') + .set('Cookie', '_node=' + encodeURIComponent(cookiestr)) + .then(res => { + assert(res.headers['set-cookie']) + var _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] + var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) + assert.deepEqual(jsonData.data, {}) + }) + }); + }); + }); + describe('when session is empty', function() { + describe('and new data are added to req.session', function() { + it('should set a new cookie with the new data and with a timestamp', function () { + this.sandbox.stub(sessions, 'deserialize').returns({data: {}}); + this.app.use(cookieParser()) + this.app.use(sessions({secret: secret})) + this.app.get('/', (req, res) => { + req.session.koko = 'bar' + res.send('Hello World!') + }) + return request(this.app) + .get('/') + .then(res => { + this.sandbox.restore() + assert(res.headers['set-cookie']) + var _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] + var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) + assert(jsonData.time) + assert.deepEqual(jsonData.data, { koko: 'bar' }) + }) + }); + }); + }); + }) }) From 84e6f127dada9dc94811ec5d2645c2f19bdd039c Mon Sep 17 00:00:00 2001 From: Marcin Hoppe Date: Tue, 30 Oct 2018 11:14:53 +0100 Subject: [PATCH 17/26] Add CODEOWNERS file --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..0804cf3 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @auth0/product-security From b1fea32415d9faab3751720bc472b419b027c52b Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Sun, 28 Oct 2018 15:55:55 -0700 Subject: [PATCH 18/26] update details in readme --- README.md | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f999d7b..a4b5b06 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,14 @@ Session data is stored on the request object in the 'session' property: }) ); ``` -The middleware requires that -[cookie-parser](https://www.npmjs.com/package/cookie-parser) middleware is also -used. + +The [cookie-parser](https://www.npmjs.com/package/cookie-parser) middleware +MUST also used. The session data can be any JSON object. It's timestamped, encrypted and -authenticated automatically. The authenticated encryption uses -`xsalsa20poly1305` offered by -[auth0-magic](https://github.com/auth0/magic) via -[magic.encrypt.sync](https://github.com/auth0/magic/#magicencryptsync--magicdecryptsync) -function. The httpOnly cookie flag is set by default. +authenticated automatically. The authenticated encryption uses `aes-256-gcm`` +offered by the node `crypto` library. The httpOnly and secure cookie flags are +set by default. The main function accepts a number of options: @@ -31,12 +29,13 @@ The main function accepts a number of options: |---------------|----------|-------------------------------------------------------------------------------------------------------------------------|----------| | secret | Yes | The secret to encrypt the session data, which must be 32 bytes long; i.e., a 32-byte buffer or 64-character hex string. | | | timeout | Yes | The amount of time in milliseconds before the cookie expires. | 24 hours | -| sessionKey | Yes | The cookie key name in which to store the session data. | `\_node` | +| name | Yes | The cookie name in which to store the session data. | `\_node` | | path | Yes | The path to use for the cookie. | `/` | | domain | No | Define a specific domain/subdomain scope for the cookie. | | +| autoRenew | No | Boolean: if true, a new cookie will be set in each response with an updated expiration Date.now() + timeout | true | | httpOnly | No | Boolean: if true, the httpOnly cookie flag will be set. | true | | secure | No | Boolean: if true, the secure cookie flag will be set. | true | -| sameSite | No | If set to "lax" or "strict", the sameSite cookie flag with the corresponding mode will be set. | | +| sameSite | No | If set to "lax" or "strict", the sameSite cookie flag with the corresponding mode will be set. | | | sessionCookie | No | Boolean: if true, it's considered a session cookie and no "expires" is set. | | @@ -60,19 +59,11 @@ sessions! ## Migrating to version 1.0.0 * Any cookie created with 0.0.2 version will be invalidated. -* The secret used to encrypt the sessions data can no longer be of any length. - It MUST be 32 bytes long and passed as either a Buffer object or a hex - encoded string. * The `options` object has two naming changes: - * `sessionKey` instead of `session_key` + * `name` instead of `session_key` * `sessionCookie` instead of `session_cookie` -* The following exported functions are now async and expect a callback: - * readSession - * serialize - * deserialize - * encrypt - * decrypt * The following exported functions have been removed: + * readSession * readCookies * checkLength * headersToArray From f4f4de4157e0d2c5c5a3d06951de5497204d8a7e Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Tue, 30 Oct 2018 07:57:48 -0700 Subject: [PATCH 19/26] fix nit errors --- lib/cookie-sessions.js | 6 +++--- test/cookie-sessions.test.js | 10 ---------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/cookie-sessions.js b/lib/cookie-sessions.js index e438bd2..0cb319f 100644 --- a/lib/cookie-sessions.js +++ b/lib/cookie-sessions.js @@ -120,7 +120,7 @@ exports.encrypt = function(secret, message) { exports.decrypt = function(secret, ciphertext, iv, authTag) { try { - var decipher = crypto.createDecipheriv('aes-256-gcm', secret , Buffer.from(iv, 'hex')); + var decipher = crypto.createDecipheriv('aes-256-gcm', secret, Buffer.from(iv, 'hex')); decipher.setAuthTag(Buffer.from(authTag, 'hex')); var plaintext = decipher.update(ciphertext, 'hex', 'utf8'); plaintext += decipher.final('utf8'); @@ -155,8 +155,8 @@ exports.deserialize = function(secret, str, timeout) { data: {} } } - var splited = exports.split(str) - cookieData = exports.decrypt(secret, splited.ciphertext, splited.iv, splited.authTag) + var splitted = exports.split(str) + cookieData = exports.decrypt(secret, splitted.ciphertext, splitted.iv, splitted.authTag) var jsonData = JSON.parse(cookieData) if (!exports.valid(jsonData.t, timeout)) { diff --git a/test/cookie-sessions.test.js b/test/cookie-sessions.test.js index 8102040..74ca11f 100644 --- a/test/cookie-sessions.test.js +++ b/test/cookie-sessions.test.js @@ -89,16 +89,6 @@ describe('cookie sessions tests', function() { }) }) - /* - describe('readSession', function() { - it('session with key node_session read ok', function() { - this.sandbox.stub(sessions, 'deserialize').returns({username: 'foobar'}); - var session = sessions.readSession('node_session', secret, 12, {cookies: {node_session: 'lala'}}) - assert.deepEqual({username: 'foobar'}, session) - }); - }) - */ - describe('onInit', function() { it('throw error if no secret set in server settings', function() { try { From ee14c8150591f55a31ec36b636d672a5c736725d Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Mon, 5 Nov 2018 09:17:38 -0800 Subject: [PATCH 20/26] fix small nit issues --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a4b5b06..bfb1e40 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ Secure cookie-based session middleware for Express. Session data is stored on the request object in the 'session' property: ```js var app = require('express'); + var cookieParser = require('cookie-parser'); var cookieSessions = require('cookie-sessions'); app.use( cookieSessions({ - sessionKey: 'session_data', + name: 'session_data', secret: process.env.SECRET }) ); @@ -19,7 +20,7 @@ The [cookie-parser](https://www.npmjs.com/package/cookie-parser) middleware MUST also used. The session data can be any JSON object. It's timestamped, encrypted and -authenticated automatically. The authenticated encryption uses `aes-256-gcm`` +authenticated automatically. The authenticated encryption uses `aes-256-gcm` offered by the node `crypto` library. The httpOnly and secure cookie flags are set by default. @@ -27,7 +28,7 @@ The main function accepts a number of options: | Option | Required | Description | Default | |---------------|----------|-------------------------------------------------------------------------------------------------------------------------|----------| -| secret | Yes | The secret to encrypt the session data, which must be 32 bytes long; i.e., a 32-byte buffer or 64-character hex string. | | +| secret | Yes | The secret to encrypt the session data. | | | timeout | Yes | The amount of time in milliseconds before the cookie expires. | 24 hours | | name | Yes | The cookie name in which to store the session data. | `\_node` | | path | Yes | The path to use for the cookie. | `/` | From b8349aaf34455ece12c339a4ebb30b2067306353 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Wed, 17 Jul 2019 13:54:21 -0700 Subject: [PATCH 21/26] Update lodash to a non vulnerable version --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc873bc..bb4de11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -651,9 +651,9 @@ "dev": true }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" }, "lodash.get": { "version": "4.4.2", diff --git a/package.json b/package.json index 2a3c0cc..f0172ed 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,6 @@ } ], "dependencies": { - "lodash": "^4.17.11" + "lodash": "^4.17.14" } } From 6c1b4e153349cec9c813ecb3e2d1c6e9da9a623a Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Wed, 17 Jul 2019 13:54:29 -0700 Subject: [PATCH 22/26] Fix bug in errors Tests were failing because of a hardcoded cookie timestamp that resulted in the valid() function returning that the cookie has expired. --- test/cookie-sessions.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/cookie-sessions.test.js b/test/cookie-sessions.test.js index 74ca11f..af1e211 100644 --- a/test/cookie-sessions.test.js +++ b/test/cookie-sessions.test.js @@ -133,7 +133,8 @@ describe('cookie sessions tests', function() { describe('when session has already data', function() { beforeEach(function() { - this.sandbox.stub(sessions, 'deserialize').returns({data: {username: 'foobar'}, time: 1540684048566}); + this.cookieTime = Date.now() + this.sandbox.stub(sessions, 'deserialize').returns({data: {username: 'foobar'}, time: this.cookieTime}); }); describe('and new data are added to req.session', function() { @@ -152,7 +153,7 @@ describe('cookie sessions tests', function() { assert(res.headers['set-cookie']) _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) - assert.notEqual(jsonData.time, 1540684048566) + assert.notEqual(jsonData.time, this.cookieTime) assert.deepEqual(jsonData.data, { username: 'foobar', koko: 'bar' }) }) }); @@ -172,7 +173,7 @@ describe('cookie sessions tests', function() { assert(res.headers['set-cookie']) var _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) - assert.equal(jsonData.time, 1540684048566) + assert.equal(jsonData.time, this.cookieTime) assert.deepEqual(jsonData.data, { username: 'foobar', koko: 'bar' }) }) }); @@ -193,7 +194,7 @@ describe('cookie sessions tests', function() { assert(res.headers['set-cookie']) var _node = res.headers['set-cookie'][0].split(';')[0].split('=')[1] var jsonData = sessions.deserialize(secret, decodeURIComponent(_node), 1000 * 60 * 60 * 24) - assert.notEqual(jsonData.time, 1540684048566) + assert.notEqual(jsonData.time, this.cookieTime) assert.deepEqual(jsonData.data, { username: 'foobar'}) }) }); From 7572fb9da688522ac273efb63eec8f6c34ed67fd Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Wed, 17 Jul 2019 15:10:34 -0700 Subject: [PATCH 23/26] Add CHANGELOG file --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0ec0709 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +## v1.0.1 - 2019-07-17 + +### Misc + +- Minor update in lodash to mitigate a snyk reported vulnerability +- Fixes bug in tests +- Minor updates in README + +## v1.0.0 - 2018-10-30 + +### Adds + +- Adds option for the samesite cookie flag +- Adds autoRenew option + +### Changes + +- Updates cryptographic algorithms. It's now using AES 256 in GCM mode + +### Removes + +- Removes the following exported functions: + - readSession + - readCookies + - checkLength + - headersToArray + - hmac_signature + +### Misc + +- Code refactor From 80b1241125f3b4cce377e2b5c08f5bb5af6eff47 Mon Sep 17 00:00:00 2001 From: Eva Sarafianou Date: Wed, 17 Jul 2019 15:10:49 -0700 Subject: [PATCH 24/26] Release version 1.0.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb4de11..be39786 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cookie-sessions", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f0172ed..51ea8bb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "cookie-sessions", "description": "Secure cookie-based session middleware for Express", "main": "./lib/cookie-sessions", - "version": "1.0.0", + "version": "1.0.1", "scripts": { "test": "NODE_PATH=. nyc mocha ./test" }, From 27bc51f1c2f4414ec316b8d1b259adb8051a690f Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Tue, 23 Feb 2021 00:07:06 +0000 Subject: [PATCH 25/26] fix: package.json & package-lock.json to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-LODASH-1018905 - https://snyk.io/vuln/SNYK-JS-LODASH-1040724 --- package-lock.json | 17 +++++++++++------ package.json | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index be39786..3dc01e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -651,9 +651,9 @@ "dev": true }, "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.get": { "version": "4.4.2", @@ -852,6 +852,7 @@ "version": "0.1.4", "bundled": true, "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -1176,7 +1177,8 @@ "is-buffer": { "version": "1.1.6", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "is-builtin-module": { "version": "1.0.0", @@ -1260,6 +1262,7 @@ "version": "3.2.2", "bundled": true, "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -1306,7 +1309,8 @@ "longest": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "lru-cache": { "version": "4.1.3", @@ -1572,7 +1576,8 @@ "repeat-string": { "version": "1.6.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "require-directory": { "version": "2.1.1", diff --git a/package.json b/package.json index 51ea8bb..b3411be 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,6 @@ } ], "dependencies": { - "lodash": "^4.17.14" + "lodash": "^4.17.21" } } From 80311237bb66186e72c663ed8167426a74564deb Mon Sep 17 00:00:00 2001 From: "sre-57-opslevel[bot]" <113727212+sre-57-opslevel[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 11:54:13 +0000 Subject: [PATCH 26/26] Upload OpsLevel YAML --- opslevel.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 opslevel.yml diff --git a/opslevel.yml b/opslevel.yml new file mode 100644 index 0000000..6650937 --- /dev/null +++ b/opslevel.yml @@ -0,0 +1,6 @@ +--- +version: 1 +repository: + owner: product_security_engineering + tier: + tags: