From 44accc41be29162cd879a4242b55fc1ad2442489 Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Tue, 11 Mar 2014 22:59:30 -0700 Subject: [PATCH 001/154] readme: add not about session stores --- Readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Readme.md b/Readme.md index 710c027..0ab4a07 100644 --- a/Readme.md +++ b/Readme.md @@ -57,6 +57,12 @@ console.log('listening on port 3000'); this.session = null; ``` +## Session Stores + + This module only supports cookie sessions. There are many other modules listed in [koa's wiki](https://github.com/koajs/koa/wiki#wiki-sessions) for sessions that use database storage. Unlike Connect 2.x's session middleware, there is no main "session" middleware that you plugin different stores - each store is a completely different module. + + If you're interested in creating your own koa session store, feel free to fork/extend this repository and add additional tests. At a minimum, it __should__ pass this repositories' tests that apply. Ideally, there would be a central repository with specifications and tests for all koa sessions, which would allow interoperability and consistency between session modules. If you're interested in working on such a project, let us know! + ## License MIT From 118a4a8a20c4eed58c61601435e5e73ee774e9cf Mon Sep 17 00:00:00 2001 From: TJ Holowaychuk Date: Wed, 12 Mar 2014 19:04:59 -0700 Subject: [PATCH 002/154] refactor error handling with finally statement --- index.js | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 80c4c3c..5e38063 100644 --- a/index.js +++ b/index.js @@ -70,27 +70,23 @@ module.exports = function(opts){ throw new Error('this.session can only be set as null or an object.'); }); - var err; try { yield *next; - } catch (_err) { - err = _err; - } - - if (undefined === sess) { - // not accessed - } else if (false === sess) { - // remove - this.cookies.set(key, '', opts); - } else if (!json && !sess.length) { - // do nothing if new and not populated - } else if (sess.changed(json)) { - // save - sess.save(); + } catch (err) { + throw err; + } finally { + if (undefined === sess) { + // not accessed + } else if (false === sess) { + // remove + this.cookies.set(key, '', opts); + } else if (!json && !sess.length) { + // do nothing if new and not populated + } else if (sess.changed(json)) { + // save + sess.save(); + } } - - // rethrow any downstream errors - if (err) throw err; } }; From 9623fcf37f5a9e7f808f944da6b26b59c4b01aa1 Mon Sep 17 00:00:00 2001 From: TJ Holowaychuk Date: Wed, 12 Mar 2014 19:13:01 -0700 Subject: [PATCH 003/154] add newlines to the tests --- test.js | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/test.js b/test.js index 853fd31..fe10d1f 100644 --- a/test.js +++ b/test.js @@ -9,8 +9,10 @@ describe('Koa Session', function(){ describe('when app.keys are set', function(){ it('should work', function(done){ var app = koa(); + app.keys = ['a', 'b']; app.use(session()); + app.use(function *(){ this.session.message = 'hi'; this.body = this.session; @@ -25,7 +27,9 @@ describe('Koa Session', function(){ describe('when app.keys are not set', function(){ it('should throw', function(done){ var app = koa(); + app.use(session()); + app.use(function *(){ this.session.message = 'hi'; this.body = this.session; @@ -42,9 +46,9 @@ describe('Koa Session', function(){ describe('when app.keys are not set', function(){ it('should work', function(done){ var app = koa(); - app.use(session({ - signed: false - })); + + app.use(session({ signed: false })); + app.use(function *(){ this.session.message = 'hi'; this.body = this.session; @@ -60,6 +64,7 @@ describe('Koa Session', function(){ describe('when the session contains a ;', function(){ it('should still work', function(done){ var app = App(); + app.use(function *(){ if (this.method === 'POST') { this.session.string = ';'; @@ -68,7 +73,9 @@ describe('Koa Session', function(){ this.body = this.session.string; } }); + var server = app.listen(); + request(server) .post('/') .expect(204, function(err, res){ @@ -86,9 +93,11 @@ describe('Koa Session', function(){ describe('when not accessed', function(){ it('should not Set-Cookie', function(done) { var app = App(); + app.use(function *(){ this.body = 'greetings'; }) + request(app.listen()) .get('/') .expect(200, function(err, res){ @@ -102,10 +111,12 @@ describe('Koa Session', function(){ describe('when accessed and not populated', function(done){ it('should not Set-Cookie', function(done) { var app = App(); + app.use(function *(){ this.session; this.body = 'greetings'; }) + request(app.listen()) .get('/') .expect(200, function(err, res){ @@ -119,10 +130,12 @@ describe('Koa Session', function(){ describe('when populated', function(done){ it('should Set-Cookie', function(done){ var app = App(); + app.use(function *(){ this.session.message = 'hello'; this.body = ''; }) + request(app.listen()) .get('/') .expect('Set-Cookie', /koa:sess/) @@ -135,9 +148,11 @@ describe('Koa Session', function(){ it('should not Set-Cookie', function(done){ var app = App(); + app.use(function *(){ this.body = this.session; }) + request(app.listen()) .get('/') .expect(200, function(err, res){ @@ -153,9 +168,11 @@ describe('Koa Session', function(){ describe('when not accessed', function(){ it('should not Set-Cookie', function(done){ var app = App(); + app.use(function *(){ this.body = 'aklsdjflasdjf'; }) + request(app.listen()) .get('/') .set('Cookie', cookie) @@ -170,10 +187,12 @@ describe('Koa Session', function(){ describe('when accessed but not changed', function(){ it('should be the same session', function(done){ var app = App(); + app.use(function *(){ this.session.message.should.equal('hello'); this.body = 'aklsdjflasdjf'; }) + request(app.listen()) .get('/') .set('Cookie', cookie) @@ -182,10 +201,12 @@ describe('Koa Session', function(){ it('should not Set-Cookie', function(done){ var app = App(); + app.use(function *(){ this.session.message.should.equal('hello'); this.body = 'aklsdjflasdjf'; }) + request(app.listen()) .get('/') .set('Cookie', cookie) @@ -200,10 +221,12 @@ describe('Koa Session', function(){ describe('when accessed and changed', function(){ it('should Set-Cookie', function(done){ var app = App(); + app.use(function *(){ this.session.money = '$$$'; this.body = 'aklsdjflasdjf'; }) + request(app.listen()) .get('/') .set('Cookie', cookie) @@ -213,14 +236,16 @@ describe('Koa Session', function(){ }) }) - describe('when session = ', function(){ + describe('when session is', function(){ describe('null', function(){ it('should expire the session', function(done){ var app = App(); + app.use(function *(){ this.session = null; this.body = 'asdf'; }) + request(app.listen()) .get('/') .expect('Set-Cookie', /koa:sess/) @@ -231,10 +256,12 @@ describe('Koa Session', function(){ describe('{}', function(){ it('should not Set-Cookie', function(done){ var app = App(); + app.use(function *(){ this.session = {}; this.body = 'asdf'; }) + request(app.listen()) .get('/') .expect(200, function(err, res){ @@ -248,10 +275,12 @@ describe('Koa Session', function(){ describe('{a: b}', function(){ it('should create a session', function(done){ var app = App(); + app.use(function *(){ this.session = { message: 'hello' }; this.body = 'asdf'; }) + request(app.listen()) .get('/') .expect('Set-Cookie', /koa:sess/) @@ -262,9 +291,11 @@ describe('Koa Session', function(){ describe('anything else', function(){ it('should throw', function(done){ var app = App(); + app.use(function *(){ this.session = 'asdf' }) + request(app.listen()) .get('/') .expect(500, done); @@ -275,7 +306,9 @@ describe('Koa Session', function(){ describe('when an error is thrown downstream and caught upstream', function(){ it('should still save the session', function(done){ var app = koa(); + app.keys = ['a', 'b']; + app.use(function *(next){ try { yield *next; @@ -284,14 +317,18 @@ describe('Koa Session', function(){ this.body = err.message; } }); + app.use(session()); + app.use(function *(next){ this.session.name = 'funny'; yield *next; }); + app.use(function *(next){ this.throw(401); }); + request(app.listen()) .get('/') .expect('Set-Cookie', /koa:sess/) From 51d12374414405c4bd3640611e56801f8309552a Mon Sep 17 00:00:00 2001 From: TJ Holowaychuk Date: Wed, 12 Mar 2014 19:14:10 -0700 Subject: [PATCH 004/154] change test labels --- test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test.js b/test.js index fe10d1f..81a160e 100644 --- a/test.js +++ b/test.js @@ -253,7 +253,7 @@ describe('Koa Session', function(){ }) }) - describe('{}', function(){ + describe('an empty object', function(){ it('should not Set-Cookie', function(done){ var app = App(); @@ -272,7 +272,7 @@ describe('Koa Session', function(){ }) }) - describe('{a: b}', function(){ + describe('an object', function(){ it('should create a session', function(done){ var app = App(); From 3495ecb4fb9ddb5b1ff2463aa02b902fd5b22ac3 Mon Sep 17 00:00:00 2001 From: TJ Holowaychuk Date: Wed, 12 Mar 2014 19:26:49 -0700 Subject: [PATCH 005/154] refactor with commit() helper meh pretty lame but at least it gets it out of nested braces --- index.js | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index 5e38063..b770f10 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ module.exports = function(opts){ opts = opts || {}; // key - var key = opts.key || 'koa:sess'; + opts.key = opts.key || 'koa:sess'; // defaults if (null == opts.overwrite) opts.overwrite = true; @@ -32,7 +32,7 @@ module.exports = function(opts){ // to pass to Session() this.sessionOptions = opts; - this.sessionKey = key; + this.sessionKey = opts.key; this.__defineGetter__('session', function(){ // already retrieved @@ -41,7 +41,7 @@ module.exports = function(opts){ // unset if (false === sess) return null; - json = this.cookies.get(key, opts); + json = this.cookies.get(opts.key, opts); if (json) { debug('parse %s', json); @@ -75,21 +75,38 @@ module.exports = function(opts){ } catch (err) { throw err; } finally { - if (undefined === sess) { - // not accessed - } else if (false === sess) { - // remove - this.cookies.set(key, '', opts); - } else if (!json && !sess.length) { - // do nothing if new and not populated - } else if (sess.changed(json)) { - // save - sess.save(); - } + commit(this, json, sess, opts); } } }; +/** + * Commit the session changes or removal. + * + * @param {Context} ctx + * @param {String} json + * @param {Object} sess + * @param {Object} opts + * @api private + */ + +function commit(ctx, json, sess, opts) { + // not accessed + if (undefined === sess) return; + + // removed + if (false === sess) { + ctx.cookies.set(opts.key, '', opts); + return; + } + + // do nothing if new and not populated + if (!json && !sess.length) return; + + // save + if (sess.changed(json)) sess.save(); +} + /** * Session model. * From ab06e2763325c6188f247f90dfb39da8b656f770 Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Mon, 15 Sep 2014 23:30:40 -0700 Subject: [PATCH 006/154] add coverage --- .gitignore | 1 + .travis.yml | 7 ++++--- Makefile | 10 ---------- package.json | 13 ++++++++----- test.js => test/test.js | 4 ++-- 5 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 Makefile rename test.js => test/test.js (99%) diff --git a/.gitignore b/.gitignore index 3c3629e..ba2a97b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +coverage diff --git a/.travis.yml b/.travis.yml index 5a7e991..34acbdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ -language: node_js node_js: - - "0.11" - +- "0.11" +language: node_js +script: "npm run test-travis" +after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" diff --git a/Makefile b/Makefile deleted file mode 100644 index b36b52b..0000000 --- a/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -test: - @NODE_ENV=test ./node_modules/.bin/mocha \ - --require should \ - --harmony-generators \ - --reporter spec - -clean: - @rm -rf node_modules - -.PHONY: test clean \ No newline at end of file diff --git a/package.json b/package.json index 80cb037..3d9b779 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,19 @@ "index.js" ], "devDependencies": { - "koa": "*", - "mocha": "*", - "should": "*", - "supertest": "*" + "istanbul-harmony": "0", + "koa": "0", + "mocha": "1", + "should": "3", + "supertest": "0" }, "license": "MIT", "dependencies": { "debug": "*" }, "scripts": { - "test": "make test" + "test": "NODE_ENV=test mocha --harmony-generators --require should --reporter spec", + "test-cov": "NODE_ENV=test node --harmony-generators ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should", + "test-travis": "NODE_ENV=test node --harmony-generators ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should" } } diff --git a/test.js b/test/test.js similarity index 99% rename from test.js rename to test/test.js index 81a160e..ba2468a 100644 --- a/test.js +++ b/test/test.js @@ -1,6 +1,6 @@ var koa = require('koa'); var request = require('supertest'); -var session = require('./'); +var session = require('..'); describe('Koa Session', function(){ var cookie; @@ -342,4 +342,4 @@ function App(options) { app.keys = ['a', 'b']; app.use(session(options)); return app; -} \ No newline at end of file +} From ea385dbecebb243904d85d0e7fae8df56b09ab35 Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Mon, 15 Sep 2014 23:36:25 -0700 Subject: [PATCH 007/154] useless commit for coverage --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d9b779..88c08d8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "keywords": [ "koa", "middleware", - "session" + "session", + "cookie" ], "files": [ "index.js" From e5a496c0335ef1e07d525e347783e3150e3d1f42 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Mon, 20 Oct 2014 21:55:58 +0800 Subject: [PATCH 008/154] fix example --- example.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/example.js b/example.js index 6d7eb41..3693d80 100644 --- a/example.js +++ b/example.js @@ -7,14 +7,12 @@ app.keys = ['some secret hurr']; app.use(session()); -app.use(function(next){ - return function *(){ - if ('/favicon.ico' == this.path) return; - var n = this.session.views || 0; - this.session.views = ++n; - this.body = n + ' views'; - } -}) +app.use(function* (next){ + if ('/favicon.ico' == this.path) return; + var n = this.session.views || 0; + this.session.views = ++n; + this.body = n + ' views'; +}); app.listen(3000); -console.log('listening on port 3000'); \ No newline at end of file +console.log('listening on port 3000'); From aff00c846cfa497a82c808e759002928aa2e37f9 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Thu, 11 Dec 2014 13:05:04 +0800 Subject: [PATCH 009/154] improve performance by reduce hiddin class on every request Before: qps 2569.09, rt 5.46ms After : qps 3565.10, rt 2.79ms --- Readme.md | 4 +-- example.js | 2 +- index.js | 94 +++++++++++++++++++++++++++++----------------------- test/test.js | 22 +++++++++--- 4 files changed, 73 insertions(+), 49 deletions(-) diff --git a/Readme.md b/Readme.md index 0ab4a07..a2b6d43 100644 --- a/Readme.md +++ b/Readme.md @@ -18,7 +18,7 @@ var koa = require('koa'); var app = koa(); app.keys = ['some secret hurr']; -app.use(session()); +app.use(session(app)); app.use(function *(){ var n = this.session.views || 0; @@ -60,7 +60,7 @@ this.session = null; ## Session Stores This module only supports cookie sessions. There are many other modules listed in [koa's wiki](https://github.com/koajs/koa/wiki#wiki-sessions) for sessions that use database storage. Unlike Connect 2.x's session middleware, there is no main "session" middleware that you plugin different stores - each store is a completely different module. - + If you're interested in creating your own koa session store, feel free to fork/extend this repository and add additional tests. At a minimum, it __should__ pass this repositories' tests that apply. Ideally, there would be a central repository with specifications and tests for all koa sessions, which would allow interoperability and consistency between session modules. If you're interested in working on such a project, let us know! ## License diff --git a/example.js b/example.js index 3693d80..abf009d 100644 --- a/example.js +++ b/example.js @@ -5,7 +5,7 @@ var app = koa(); app.keys = ['some secret hurr']; -app.use(session()); +app.use(session(app)); app.use(function* (next){ if ('/favicon.ico' == this.path) return; diff --git a/index.js b/index.js index b770f10..bd6609f 100644 --- a/index.js +++ b/index.js @@ -11,10 +11,18 @@ var debug = require('debug')('koa-session'); * - all other options are passed as cookie options * * @param {Object} [opts] + * @param {Application} app, koa application instance * @api public */ -module.exports = function(opts){ +module.exports = function(opts, app){ + // session(app[, opts]) + if (opts && typeof opts.use === 'function') { + var tmp = app; + app = opts; + opts = tmp; + } + opts = opts || {}; // key @@ -27,55 +35,59 @@ module.exports = function(opts){ debug('session options %j', opts); - return function *(next){ - var sess, json; - - // to pass to Session() - this.sessionOptions = opts; - this.sessionKey = opts.key; - - this.__defineGetter__('session', function(){ - // already retrieved - if (sess) return sess; - - // unset - if (false === sess) return null; - - json = this.cookies.get(opts.key, opts); - - if (json) { - debug('parse %s', json); - try { - sess = new Session(this, decode(json)); - } catch (err) { - // backwards compatibility: - // create a new session if parsing fails. - // new Buffer(string, 'base64') does not seem to crash - // when `string` is not base64-encoded. - // but `JSON.parse(string)` will crash. - if (!(err instanceof SyntaxError)) throw err; - sess = new Session(this); - } - } else { - debug('new session'); + if (!app || typeof app.use !== 'function') { + throw new TypeError('app instance required: `session(opts, app)`'); + } + + // to pass to Session() + app.context.sessionOptions = opts; + app.context.sessionKey = opts.key; + + app.context.__defineGetter__('session', function(){ + var sess = this._sess; + // already retrieved + if (sess) return sess; + + // unset + if (false === sess) return null; + + var json = this._sessjson = this.cookies.get(opts.key, opts); + + if (json) { + debug('parse %s', json); + try { + sess = new Session(this, decode(json)); + } catch (err) { + // backwards compatibility: + // create a new session if parsing fails. + // new Buffer(string, 'base64') does not seem to crash + // when `string` is not base64-encoded. + // but `JSON.parse(string)` will crash. + if (!(err instanceof SyntaxError)) throw err; sess = new Session(this); } + } else { + debug('new session'); + sess = new Session(this); + } - return sess; - }); + this._sess = sess; + return sess; + }); - this.__defineSetter__('session', function(val){ - if (null == val) return sess = false; - if ('object' == typeof val) return sess = new Session(this, val); - throw new Error('this.session can only be set as null or an object.'); - }); + app.context.__defineSetter__('session', function(val){ + if (null == val) return this._sess = false; + if ('object' == typeof val) return this._sess = new Session(this, val); + throw new Error('this.session can only be set as null or an object.'); + }); + return function* (next){ try { yield *next; } catch (err) { throw err; } finally { - commit(this, json, sess, opts); + commit(this, this._sessjson, this._sess, opts); } } }; @@ -221,4 +233,4 @@ function decode(string) { function encode(body) { body = JSON.stringify(body); return new Buffer(body).toString('base64'); -} \ No newline at end of file +} diff --git a/test/test.js b/test/test.js index ba2468a..0528287 100644 --- a/test/test.js +++ b/test/test.js @@ -11,7 +11,7 @@ describe('Koa Session', function(){ var app = koa(); app.keys = ['a', 'b']; - app.use(session()); + app.use(session({}, app)); app.use(function *(){ this.session.message = 'hi'; @@ -28,7 +28,7 @@ describe('Koa Session', function(){ it('should throw', function(done){ var app = koa(); - app.use(session()); + app.use(session(app)); app.use(function *(){ this.session.message = 'hi'; @@ -40,6 +40,15 @@ describe('Koa Session', function(){ .expect(500, done); }) }) + + describe('when app not set', function(){ + it('should throw', function(){ + var app = koa(); + (function(){ + app.use(session()); + }).should.throw('app instance required: `session(opts, app)`'); + }) + }) }) describe('when options.signed = false', function(){ @@ -47,7 +56,7 @@ describe('Koa Session', function(){ it('should work', function(done){ var app = koa(); - app.use(session({ signed: false })); + app.use(session({ signed: false }, app)); app.use(function *(){ this.session.message = 'hi'; @@ -318,7 +327,7 @@ describe('Koa Session', function(){ } }); - app.use(session()); + app.use(session(app)); app.use(function *(next){ this.session.name = 'funny'; @@ -340,6 +349,9 @@ describe('Koa Session', function(){ function App(options) { var app = koa(); app.keys = ['a', 'b']; - app.use(session(options)); + app.use(session(options, app)); + // app.on('error', function (err) { + // console.log(err.stack); + // }); return app; } From 799f846b8b3fc604c8e519f60ce0bec14f256459 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Thu, 11 Dec 2014 13:32:14 +0800 Subject: [PATCH 010/154] Release 3.0.0 --- History.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 3aec842..e95da34 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,11 @@ +3.0.0 / 2014-12-11 +================== + + * improve performance by reduce hiddin class on every request + * refactor with commit() helper + * refactor error handling with finally statement + 2.0.0 / 2014-02-17 ================== diff --git a/package.json b/package.json index 88c08d8..97fa17a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "2.0.0", + "version": "3.0.0", "keywords": [ "koa", "middleware", From 826a9956d4c623c797d985f7f162335a6511385a Mon Sep 17 00:00:00 2001 From: dead_horse Date: Mon, 20 Oct 2014 23:49:49 +0800 Subject: [PATCH 011/154] set expire in cookie value ensure every session will expire --- index.js | 25 ++++++++++++++++---- package.json | 3 ++- test/test.js | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index bd6609f..cfa4651 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ */ var debug = require('debug')('koa-session'); +var deepEqual = require('deep-equal'); /** * Initialize session middleware with `opts`: @@ -51,12 +52,14 @@ module.exports = function(opts, app){ // unset if (false === sess) return null; - var json = this._sessjson = this.cookies.get(opts.key, opts); + var json = this.cookies.get(opts.key, opts); if (json) { debug('parse %s', json); try { sess = new Session(this, decode(json)); + // make prev a different object from sess + json = decode(json); } catch (err) { // backwards compatibility: // create a new session if parsing fails. @@ -65,6 +68,7 @@ module.exports = function(opts, app){ // but `JSON.parse(string)` will crash. if (!(err instanceof SyntaxError)) throw err; sess = new Session(this); + json = null; } } else { debug('new session'); @@ -72,6 +76,7 @@ module.exports = function(opts, app){ } this._sess = sess; + this._sessjson = json; return sess; }); @@ -165,8 +170,7 @@ Session.prototype.toJSON = function(){ Session.prototype.changed = function(prev){ if (!prev) return true; - this._json = encode(this); - return this._json != prev; + return !deepEqual(prev, this.toJSON()); }; /** @@ -201,10 +205,15 @@ Session.prototype.__defineGetter__('populated', function(){ Session.prototype.save = function(){ var ctx = this._ctx; - var json = this._json || encode(this); + var json = this.toJSON(); var opts = ctx.sessionOptions; var key = ctx.sessionKey; + // set expire into cookie value + var maxAge = opts.maxAge || opts.maxage || 24 * 60 * 60 * 1000; // default 1d + json._expire = maxAge + Date.now(); + + json = encode(json); debug('save %s', json); ctx.cookies.set(key, json, opts); }; @@ -219,7 +228,13 @@ Session.prototype.save = function(){ function decode(string) { var body = new Buffer(string, 'base64').toString('utf8'); - return JSON.parse(body); + var json = JSON.parse(body); + + // check if the cookie is expired + if (!json._expire) return null; + if (json._expire < Date.now()) return null; + delete json._expire; + return json; } /** diff --git a/package.json b/package.json index 97fa17a..81b9238 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ }, "license": "MIT", "dependencies": { - "debug": "*" + "debug": "*", + "deep-equal": "~0.2.1" }, "scripts": { "test": "NODE_ENV=test mocha --harmony-generators --require should --reporter spec", diff --git a/test/test.js b/test/test.js index 0528287..370a833 100644 --- a/test/test.js +++ b/test/test.js @@ -344,6 +344,73 @@ describe('Koa Session', function(){ .expect(401, done); }) }) + + describe('when maxAge present', function () { + describe('and not expire', function () { + it('should not expire the session', function (done) { + var app = App({ maxAge: 100 }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.body = 200; + return; + } + + this.body = this.session.message; + }); + + var server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end(function (err, res) { + if (err) return done(err); + var cookie = res.headers['set-cookie'].join(';'); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }) + }) + }) + + + describe('and expired', function () { + it('should expire the sess', function (done) { + var app = App({ maxAge: 100 }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.status = 200; + return; + } + + this.body = this.session.message || ''; + }); + + var server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end(function (err, res) { + if (err) return done(err); + var cookie = res.headers['set-cookie'].join(';'); + + setTimeout(function () { + request(server) + .get('/') + .set('cookie', cookie) + .expect('', done); + }, 200); + }) + }) + }) + }) }) function App(options) { From 9f89d941123026e8bb2ab524fd21dfc364d465c2 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Tue, 21 Oct 2014 22:55:35 +0800 Subject: [PATCH 012/154] add session.maxAge support different max ages depending on the route. --- Readme.md | 8 ++++++++ index.js | 35 +++++++++++++++++++++++++++++++++-- test/test.js | 31 +++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index a2b6d43..fa2ad5d 100644 --- a/Readme.md +++ b/Readme.md @@ -49,6 +49,14 @@ console.log('listening on port 3000'); Returns __true__ if the session is new. +### Session#maxAge + + Get cookie's maxAge. + +### Session#maxAge= + + Set cookie's maxAge. + ### Destroying a session To destroy a session simply set it to `null`: diff --git a/index.js b/index.js index cfa4651..3907260 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,8 @@ var debug = require('debug')('koa-session'); var deepEqual = require('deep-equal'); +var ONE_DAY = 24 * 60 * 60 * 1000; + /** * Initialize session middleware with `opts`: * @@ -29,6 +31,9 @@ module.exports = function(opts, app){ // key opts.key = opts.key || 'koa:sess'; + // back-compat maxage + if (!('maxAge' in opts)) opts.maxAge = opts.maxage; + // defaults if (null == opts.overwrite) opts.overwrite = true; if (null == opts.httpOnly) opts.httpOnly = true; @@ -41,7 +46,6 @@ module.exports = function(opts, app){ } // to pass to Session() - app.context.sessionOptions = opts; app.context.sessionKey = opts.key; app.context.__defineGetter__('session', function(){ @@ -87,6 +91,11 @@ module.exports = function(opts, app){ }); return function* (next){ + // make sessionOptions independent in each request + this.sessionOptions = {}; + for (var key in opts) { + this.sessionOptions[key] = opts[key]; + } try { yield *next; } catch (err) { @@ -196,6 +205,28 @@ Session.prototype.__defineGetter__('populated', function(){ return !!this.length; }); +/** + * get session maxAge + * + * @return {Number} + * @api public + */ + +Session.prototype.__defineGetter__('maxAge', function(){ + return this._ctx.sessionOptions.maxAge; +}); + +/** + * set session maxAge + * + * @param {Number} + * @api public + */ + +Session.prototype.__defineSetter__('maxAge', function(val){ + this._ctx.sessionOptions.maxAge = val; +}); + /** * Save session changes by * performing a Set-Cookie. @@ -210,7 +241,7 @@ Session.prototype.save = function(){ var key = ctx.sessionKey; // set expire into cookie value - var maxAge = opts.maxAge || opts.maxage || 24 * 60 * 60 * 1000; // default 1d + var maxAge = opts.maxAge || ONE_DAY; json._expire = maxAge + Date.now(); json = encode(json); diff --git a/test/test.js b/test/test.js index 370a833..2e2f990 100644 --- a/test/test.js +++ b/test/test.js @@ -411,6 +411,37 @@ describe('Koa Session', function(){ }) }) }) + + describe('ctx.session.maxAge', function (){ + it('should return opt.maxAge', function(done){ + var app = App({maxAge: 100}); + + app.use(function *(){ + this.body = this.session.maxAge; + }); + + request(app.listen()) + .get('/') + .expect('100', done); + }) + }) + + describe('ctx.session.maxAge=', function () { + it('should set sessionOptions.maxAge', function(done){ + var app = App(); + + app.use(function* (){ + this.session.foo = 'bar'; + this.session.maxAge = 100; + this.body = this.session.foo; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }) + }) }) function App(options) { From 64b396aebc4b7bce60c52dcbc37abd6bdb9d0b29 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Thu, 25 Dec 2014 20:32:02 +0800 Subject: [PATCH 013/154] fix(index.js): set maxAge in cookie session --- index.js | 16 +++++++++++++--- test/test.js | 3 --- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 3907260..f09224b 100644 --- a/index.js +++ b/index.js @@ -143,8 +143,16 @@ function commit(ctx, json, sess, opts) { function Session(ctx, obj) { this._ctx = ctx; - if (!obj) this.isNew = true; - else for (var k in obj) this[k] = obj[k]; + if (!obj) { + this.isNew = true; + } + else { + for (var k in obj) { + // change session options + if ('_maxAge' == k) this._ctx.sessionOptions.maxAge = obj._maxAge; + else this[k] = obj[k]; + } + } } /** @@ -179,6 +187,8 @@ Session.prototype.toJSON = function(){ Session.prototype.changed = function(prev){ if (!prev) return true; + delete prev._expire; + delete prev._maxAge; return !deepEqual(prev, this.toJSON()); }; @@ -243,6 +253,7 @@ Session.prototype.save = function(){ // set expire into cookie value var maxAge = opts.maxAge || ONE_DAY; json._expire = maxAge + Date.now(); + json._maxAge = maxAge; json = encode(json); debug('save %s', json); @@ -264,7 +275,6 @@ function decode(string) { // check if the cookie is expired if (!json._expire) return null; if (json._expire < Date.now()) return null; - delete json._expire; return json; } diff --git a/test/test.js b/test/test.js index 2e2f990..a5e4eec 100644 --- a/test/test.js +++ b/test/test.js @@ -448,8 +448,5 @@ function App(options) { var app = koa(); app.keys = ['a', 'b']; app.use(session(options, app)); - // app.on('error', function (err) { - // console.log(err.stack); - // }); return app; } From f8c95804360d82d969762ed9a727fb3910299577 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Thu, 25 Dec 2014 20:37:45 +0800 Subject: [PATCH 014/154] refactor(index.js): rename _sessjson to _prevjson --- index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index f09224b..4fd7aa5 100644 --- a/index.js +++ b/index.js @@ -80,7 +80,7 @@ module.exports = function(opts, app){ } this._sess = sess; - this._sessjson = json; + this._prevjson = json; return sess; }); @@ -101,7 +101,7 @@ module.exports = function(opts, app){ } catch (err) { throw err; } finally { - commit(this, this._sessjson, this._sess, opts); + commit(this, this._prevjson, this._sess, opts); } } }; @@ -110,13 +110,13 @@ module.exports = function(opts, app){ * Commit the session changes or removal. * * @param {Context} ctx - * @param {String} json + * @param {Object} prevjson * @param {Object} sess * @param {Object} opts * @api private */ -function commit(ctx, json, sess, opts) { +function commit(ctx, prevjson, sess, opts) { // not accessed if (undefined === sess) return; @@ -127,10 +127,10 @@ function commit(ctx, json, sess, opts) { } // do nothing if new and not populated - if (!json && !sess.length) return; + if (!prevjson && !sess.length) return; // save - if (sess.changed(json)) sess.save(); + if (sess.changed(prevjson)) sess.save(); } /** @@ -180,7 +180,7 @@ Session.prototype.toJSON = function(){ * Check if the session has changed relative to the `prev` * JSON value from the request. * - * @param {String} [prev] + * @param {Object} [prev] * @return {Boolean} * @api private */ From 1ef8115bdbdbb6d3ce86e65c333b162d59166a0c Mon Sep 17 00:00:00 2001 From: dead_horse Date: Thu, 25 Dec 2014 20:40:37 +0800 Subject: [PATCH 015/154] Release 3.1.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index e95da34..0988b7f 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +3.1.0 / 2014-12-25 +================== + + * add session.maxAge + * set expire in cookie value + 3.0.0 / 2014-12-11 ================== diff --git a/package.json b/package.json index 81b9238..5342fc6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "3.0.0", + "version": "3.1.0", "keywords": [ "koa", "middleware", From d2a41e12f937bb8235676ac11ce58ac0acb87ca2 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Thu, 4 Jun 2015 23:31:25 +0800 Subject: [PATCH 016/154] fix: allow get session property before enter session middleware make sure sessionOptions exists --- index.js | 21 +++++++++++++++------ test/test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 4fd7aa5..e3e2e5b 100644 --- a/index.js +++ b/index.js @@ -61,6 +61,8 @@ module.exports = function(opts, app){ if (json) { debug('parse %s', json); try { + // make sure sessionOptions exists + initSessionOptions(this, opts); sess = new Session(this, decode(json)); // make prev a different object from sess json = decode(json); @@ -92,20 +94,27 @@ module.exports = function(opts, app){ return function* (next){ // make sessionOptions independent in each request - this.sessionOptions = {}; - for (var key in opts) { - this.sessionOptions[key] = opts[key]; - } + initSessionOptions(this, opts); try { - yield *next; + yield* next; } catch (err) { throw err; } finally { commit(this, this._prevjson, this._sess, opts); } - } + }; }; +function initSessionOptions(ctx, opts) { + if (ctx.sessionOptions) { + return; + } + ctx.sessionOptions = {}; + for (var key in opts) { + ctx.sessionOptions[key] = opts[key]; + } +} + /** * Commit the session changes or removal. * diff --git a/test/test.js b/test/test.js index a5e4eec..7e21953 100644 --- a/test/test.js +++ b/test/test.js @@ -1,5 +1,6 @@ var koa = require('koa'); var request = require('supertest'); +var should = require('should'); var session = require('..'); describe('Koa Session', function(){ @@ -442,6 +443,35 @@ describe('Koa Session', function(){ .expect(200, done); }) }) + + describe('when get session before enter session middleware', function(){ + it('should work', function(done){ + var app = koa(); + + app.keys = ['a', 'b']; + app.use(function* (next) { + this.session.foo = 'hi'; + yield next; + }); + app.use(session({}, app)); + app.use(function* (){ + this.body = this.session; + }); + + request(app.callback()) + .get('/') + .expect(200, function (err, res) { + should.not.exist(err); + var cookies = res.headers['set-cookie'].join(';'); + cookies.should.containEql('koa:sess='); + + request(app.callback()) + .get('/') + .set('Cookie', cookies) + .expect(200, done); + }); + }) + }) }) function App(options) { From 30bf7ee9c90f83369cf9b629202ddfebafe4937a Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Thu, 4 Jun 2015 23:32:36 +0800 Subject: [PATCH 017/154] docs: add status image to readme --- .travis.yml | 7 +++++-- Readme.md | 26 ++++++++++++++++++++++++++ package.json | 12 ++++++------ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 34acbdf..f7c965e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ node_js: -- "0.11" + - "0.11" + - "0.12" + - "iojs-1" + - "iojs-2" language: node_js script: "npm run test-travis" -after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" +after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls" diff --git a/Readme.md b/Readme.md index fa2ad5d..9f78b75 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,31 @@ # koa-session +[![NPM version][npm-image]][npm-url] +[![build status][travis-image]][travis-url] +[![Test coverage][coveralls-image]][coveralls-url] +[![Gittip][gittip-image]][gittip-url] +[![David deps][david-image]][david-url] +[![iojs version][iojs-image]][iojs-url] +[![node version][node-image]][node-url] +[![npm download][download-image]][download-url] + +[npm-image]: https://img.shields.io/npm/v/koa-session.svg?style=flat-square +[npm-url]: https://npmjs.org/package/koa-session +[travis-image]: https://img.shields.io/travis/koajs/session.svg?style=flat-square +[travis-url]: https://travis-ci.org/koajs/session +[coveralls-image]: https://img.shields.io/coveralls/koajs/session.svg?style=flat-square +[coveralls-url]: https://coveralls.io/r/koajs/session?branch=master +[gittip-image]: https://img.shields.io/gittip/fengmk2.svg?style=flat-square +[gittip-url]: https://www.gittip.com/fengmk2/ +[david-image]: https://img.shields.io/david/koajs/session.svg?style=flat-square +[david-url]: https://david-dm.org/koajs/session +[iojs-image]: https://img.shields.io/badge/io.js-%3E=_1.0-yellow.svg?style=flat-square +[iojs-url]: http://iojs.org/ +[node-image]: https://img.shields.io/badge/node.js-%3E=_0.12-green.svg?style=flat-square +[node-url]: http://nodejs.org/download/ +[download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square +[download-url]: https://npmjs.org/package/koa-session + Simple cookie-based session middleware for Koa. ## Installation diff --git a/package.json b/package.json index 5342fc6..e914a9e 100644 --- a/package.json +++ b/package.json @@ -15,18 +15,18 @@ "devDependencies": { "istanbul-harmony": "0", "koa": "0", - "mocha": "1", - "should": "3", + "mocha": "2", + "should": "6", "supertest": "0" }, "license": "MIT", "dependencies": { - "debug": "*", + "debug": "~2.2.0", "deep-equal": "~0.2.1" }, "scripts": { - "test": "NODE_ENV=test mocha --harmony-generators --require should --reporter spec", - "test-cov": "NODE_ENV=test node --harmony-generators ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should", - "test-travis": "NODE_ENV=test node --harmony-generators ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should" + "test": "NODE_ENV=test mocha --harmony --require should --reporter spec", + "test-cov": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should", + "test-travis": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should" } } From 3935a71d63f2de29f06ce79ffa6592c8c2f28d40 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Thu, 4 Jun 2015 23:34:25 +0800 Subject: [PATCH 018/154] deps: upgrade deep-equal to 1.0.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e914a9e..fbfeb28 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,12 @@ "koa": "0", "mocha": "2", "should": "6", - "supertest": "0" + "supertest": "1" }, "license": "MIT", "dependencies": { "debug": "~2.2.0", - "deep-equal": "~0.2.1" + "deep-equal": "~1.0.0" }, "scripts": { "test": "NODE_ENV=test mocha --harmony --require should --reporter spec", From 5a5e26f527d59ba22b5af44ff2c48426d9d5ffdd Mon Sep 17 00:00:00 2001 From: dead_horse Date: Thu, 4 Jun 2015 23:45:17 +0800 Subject: [PATCH 019/154] Release 3.1.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 0988b7f..62dec12 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +3.1.1 / 2015-06-04 +================== + + * deps: upgrade deep-equal to 1.0.0 + * fix: allow get session property before enter session middleware + 3.1.0 / 2014-12-25 ================== diff --git a/package.json b/package.json index fbfeb28..4f9678f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "3.1.0", + "version": "3.1.1", "keywords": [ "koa", "middleware", From 11710680336c62c25d065b414ca682255ff47bdf Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 7 Jun 2015 02:50:17 +0800 Subject: [PATCH 020/154] feat: add opts.valid() and opts.beforeSave() hooks - valid() hook: valid session value before use it - beforeSave() hook: set addition values before save it --- index.js | 22 +++++++++++++++++---- test/test.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index e3e2e5b..464c709 100644 --- a/index.js +++ b/index.js @@ -63,15 +63,24 @@ module.exports = function(opts, app){ try { // make sure sessionOptions exists initSessionOptions(this, opts); - sess = new Session(this, decode(json)); - // make prev a different object from sess - json = decode(json); + var obj = decode(json); + if (typeof opts.valid === 'function' && !opts.valid(this, obj)) { + // valid session value fail, ignore this session + sess = new Session(this); + json = obj; + debug('invalid %j', obj); + } else { + sess = new Session(this, obj); + // make prev a different object from sess + json = decode(json); + } } catch (err) { // backwards compatibility: // create a new session if parsing fails. // new Buffer(string, 'base64') does not seem to crash // when `string` is not base64-encoded. // but `JSON.parse(string)` will crash. + debug('decode %j error: %s', json, err); if (!(err instanceof SyntaxError)) throw err; sess = new Session(this); json = null; @@ -139,7 +148,12 @@ function commit(ctx, prevjson, sess, opts) { if (!prevjson && !sess.length) return; // save - if (sess.changed(prevjson)) sess.save(); + if (sess.changed(prevjson)) { + if (typeof opts.beforeSave === 'function') { + opts.beforeSave(ctx, sess); + } + sess.save(); + } } /** diff --git a/test/test.js b/test/test.js index 7e21953..e7d4900 100644 --- a/test/test.js +++ b/test/test.js @@ -472,6 +472,61 @@ describe('Koa Session', function(){ }); }) }) + + describe('when valid and beforeSave set', function(){ + it('should ignore session when uid changed', function(done){ + var app = koa(); + + app.keys = ['a', 'b']; + app.use(session({ + valid: function (ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave: function (ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + } + }, app)); + app.use(function* () { + if (!this.session.foo) { + this.session.foo = Date.now() + '|uid:' + this.cookies.get('uid'); + } + + this.body = { + foo: this.session.foo, + uid: this.cookies.get('uid'), + }; + }); + + request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200, function (err, res) { + should.not.exist(err); + var data = res.body; + var cookies = res.headers['set-cookie'].join(';'); + cookies.should.containEql('koa:sess='); + + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data, function (err) { + should.not.exist(err); + + // should ignore uid:123 session and create a new session for uid:456 + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200, function (err, res) { + should.not.exist(err); + res.body.uid.should.equal('456'); + res.body.foo.should.not.equal(data.foo); + done(); + }); + }); + }); + }) + }) }) function App(options) { From 9472d81ba7a74f467bebec493d48ab03966286ad Mon Sep 17 00:00:00 2001 From: dead_horse Date: Mon, 8 Jun 2015 02:16:22 +0800 Subject: [PATCH 021/154] update hooks in readme --- Readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Readme.md b/Readme.md index 9f78b75..2be8e21 100644 --- a/Readme.md +++ b/Readme.md @@ -71,6 +71,11 @@ console.log('listening on port 3000'); `ctx.cookies.set()` allowing you to control security, domain, path, and signing among other settings. +### Hooks + + - `valid()`: valid session value before use it + - `beforeSave()`: hook before save session + ### Session#isNew Returns __true__ if the session is new. From 2f3fe880bb412a22c28c0555d22b1efa7580df6d Mon Sep 17 00:00:00 2001 From: dead_horse Date: Mon, 8 Jun 2015 02:16:41 +0800 Subject: [PATCH 022/154] Release 3.2.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 62dec12..aeceeec 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +3.2.0 / 2015-06-08 +================== + + * feat: add opts.valid() and opts.beforeSave() hooks + 3.1.1 / 2015-06-04 ================== diff --git a/package.json b/package.json index 4f9678f..1278cfd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "3.1.1", + "version": "3.2.0", "keywords": [ "koa", "middleware", From 63b46d5a4093a1a34c68d6980cd6bf11f0910dec Mon Sep 17 00:00:00 2001 From: Joakim Larsson Date: Mon, 6 Jul 2015 17:10:50 +0200 Subject: [PATCH 023/154] custom encode/decode support --- index.js | 23 ++++++++++++++++++----- test/test.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 464c709..c9044bd 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,14 @@ module.exports = function(opts, app){ throw new TypeError('app instance required: `session(opts, app)`'); } + // setup encoding/decoding + if (!(typeof opts.encode === 'function')) { + opts.encode = encode + } + if (!(typeof opts.decode === 'function')) { + opts.decode = decode + } + // to pass to Session() app.context.sessionKey = opts.key; @@ -63,7 +71,7 @@ module.exports = function(opts, app){ try { // make sure sessionOptions exists initSessionOptions(this, opts); - var obj = decode(json); + var obj = opts.decode(json); if (typeof opts.valid === 'function' && !opts.valid(this, obj)) { // valid session value fail, ignore this session sess = new Session(this); @@ -72,7 +80,7 @@ module.exports = function(opts, app){ } else { sess = new Session(this, obj); // make prev a different object from sess - json = decode(json); + json = opts.decode(json); } } catch (err) { // backwards compatibility: @@ -278,9 +286,14 @@ Session.prototype.save = function(){ json._expire = maxAge + Date.now(); json._maxAge = maxAge; - json = encode(json); - debug('save %s', json); - ctx.cookies.set(key, json, opts); + try { + json = opts.encode(json); + debug('save %s', json); + ctx.cookies.set(key, json, opts); + } catch (e) { + debug('encode %j error: %s', json, err); + ctx.cookies.set(key, '', opts); + } }; /** diff --git a/test/test.js b/test/test.js index e7d4900..4290409 100644 --- a/test/test.js +++ b/test/test.js @@ -527,6 +527,55 @@ describe('Koa Session', function(){ }); }) }) + + describe('when options.encode and options.decode are functions', function () { + describe('they are used to encode/decode stored cookie values', function () { + it('should work', function (done) { + var encodeCallCount = 0 + var decodeCallCount = 0 + + function encode(data) { + ++encodeCallCount + return JSON.stringify({enveloped: data}) + } + function decode(data) { + ++decodeCallCount + return JSON.parse(data).enveloped + } + + var app = koa(); + app.keys = ['a', 'b']; + app.use(session({ + encode: encode, + decode: decode + }, app)); + + app.use(function * () { + this.session.counter = (this.session.counter || 0) + 1 + this.body = this.session + return + }) + + request(app.callback()) + .get('/') + .expect(function () { encodeCallCount.should.above(0, 'encode was not called'); }) + .expect(200, function (err, res) { + should.not.exist(err) + res.body.counter.should.equal(1, 'expected body to be equal to session.counter') + var cookies = res.headers['set-cookie'].join(';'); + request(app.callback()) + .get('/') + .set('Cookie', cookies) + .expect(function () { decodeCallCount.should.be.above(1, 'decode was not called'); }) + .expect(200, function (err, res) { + should.not.exist(err); + res.body.counter.should.equal(2); + done(); + }) + }) + }) + }) + }) }) function App(options) { From d6fe4eee3650335704ea44e6d798ee3474e3a269 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Tue, 7 Jul 2015 00:10:07 +0800 Subject: [PATCH 024/154] style --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index c9044bd..80746c9 100644 --- a/index.js +++ b/index.js @@ -46,10 +46,10 @@ module.exports = function(opts, app){ } // setup encoding/decoding - if (!(typeof opts.encode === 'function')) { + if (typeof opts.encode !== 'function') { opts.encode = encode } - if (!(typeof opts.decode === 'function')) { + if (typeof opts.decode !== 'function') { opts.decode = decode } @@ -289,11 +289,11 @@ Session.prototype.save = function(){ try { json = opts.encode(json); debug('save %s', json); - ctx.cookies.set(key, json, opts); } catch (e) { debug('encode %j error: %s', json, err); - ctx.cookies.set(key, '', opts); + json = ''; } + ctx.cookies.set(key, json, opts); }; /** From 845628fc009cf4eb823bf6105e0295db8bc53918 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Tue, 7 Jul 2015 00:12:54 +0800 Subject: [PATCH 025/154] readme: add custom encode/decode --- Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Readme.md b/Readme.md index 2be8e21..c6ff3e4 100644 --- a/Readme.md +++ b/Readme.md @@ -71,6 +71,10 @@ console.log('listening on port 3000'); `ctx.cookies.set()` allowing you to control security, domain, path, and signing among other settings. +#### Custom `encode/decode` Support + + Use `options.encode` and `options.decode` to customize your own encode/decode methods. + ### Hooks - `valid()`: valid session value before use it From 2d08771bf9c99e99d95cb27d7a7760c07ea44793 Mon Sep 17 00:00:00 2001 From: dead_horse Date: Tue, 7 Jul 2015 00:15:09 +0800 Subject: [PATCH 026/154] Release 3.3.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index aeceeec..73726a1 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +3.3.0 / 2015-07-07 +================== + + * custom encode/decode support + 3.2.0 / 2015-06-08 ================== diff --git a/package.json b/package.json index 1278cfd..b2eeb38 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "3.2.0", + "version": "3.3.0", "keywords": [ "koa", "middleware", From 73bbbe00e893aa3d61fdd9545ff46f508a78e30d Mon Sep 17 00:00:00 2001 From: Limian Wang Date: Tue, 7 Jul 2015 10:27:15 -0700 Subject: [PATCH 027/154] code: fix error in variable referencing --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 80746c9..0b4a6e9 100644 --- a/index.js +++ b/index.js @@ -290,7 +290,7 @@ Session.prototype.save = function(){ json = opts.encode(json); debug('save %s', json); } catch (e) { - debug('encode %j error: %s', json, err); + debug('encode %j error: %s', json, e); json = ''; } ctx.cookies.set(key, json, opts); From 018b10722e63f4fca44be72f527e5dc4f599541d Mon Sep 17 00:00:00 2001 From: dead_horse Date: Wed, 8 Jul 2015 22:24:22 +0800 Subject: [PATCH 028/154] Release 3.3.1 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 73726a1..4a60929 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +3.3.1 / 2015-07-08 +================== + + * code: fix error in variable referencing + 3.3.0 / 2015-07-07 ================== diff --git a/package.json b/package.json index b2eeb38..eb9f542 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "3.3.0", + "version": "3.3.1", "keywords": [ "koa", "middleware", From 4d6d61087f8d363a18f03af9f2cca9c061d66b4c Mon Sep 17 00:00:00 2001 From: dead_horse Date: Sun, 30 Aug 2015 17:11:28 +0800 Subject: [PATCH 029/154] readme: ignore favicon in example closes #44 --- Readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Readme.md b/Readme.md index c6ff3e4..18570d4 100644 --- a/Readme.md +++ b/Readme.md @@ -47,6 +47,9 @@ app.keys = ['some secret hurr']; app.use(session(app)); app.use(function *(){ + // ignore favicon + if (this.path === '/favicon.ico') return; + var n = this.session.views || 0; this.session.views = ++n; this.body = n + ' views'; From 163613829b223a0dd95bf674273a3b2ad3b2f758 Mon Sep 17 00:00:00 2001 From: XIE Qianyue Date: Fri, 20 Nov 2015 16:49:50 +0100 Subject: [PATCH 030/154] Update Readme.md Add the instruction to make compatible with Koa 2. --- Readme.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Readme.md b/Readme.md index 18570d4..0ede205 100644 --- a/Readme.md +++ b/Readme.md @@ -58,6 +58,18 @@ app.use(function *(){ app.listen(3000); console.log('listening on port 3000'); ``` +For Koa 2, use [koa-convert](https://github.com/gyson/koa-convert) to convert the session middleware : + +```js +const koa = require('koa'); +const session = require('koa-session') +const convert = require('koa-convert'); + +const app = new koa(); +app.use(convert(session(app))); + +// codes +``` ## Semantics From 62699f820d3dc3e20133e30865f793d1edb43188 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 31 Dec 2015 23:06:07 -0800 Subject: [PATCH 031/154] chore(package): update dependencies http://greenkeeper.io/ --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index eb9f542..b18769a 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ ], "devDependencies": { "istanbul-harmony": "0", - "koa": "0", + "koa": "1.1.2", "mocha": "2", - "should": "6", + "should": "8.0.2", "supertest": "1" }, "license": "MIT", From 3b0335fcbd9e1a782e7d4aaca3786c8c8f2e2bf3 Mon Sep 17 00:00:00 2001 From: haoxin Date: Sat, 9 Apr 2016 22:50:50 +0800 Subject: [PATCH 032/154] travis++ --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7c965e..b3b7e85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,7 @@ node_js: - - "0.11" - - "0.12" - - "iojs-1" - - "iojs-2" + - 0.12 + - 4 + - 5 language: node_js script: "npm run test-travis" after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls" From c16986585f76529bac6e8b6a2891b607bd0f73a7 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 15 Oct 2016 11:44:02 +0800 Subject: [PATCH 033/154] fix: add 'session' name for middleware function (#58) --- .travis.yml | 13 +++++++------ index.js | 2 +- package.json | 12 ++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index b3b7e85..01de166 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ -node_js: - - 0.12 - - 4 - - 5 +sudo: false language: node_js -script: "npm run test-travis" -after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls" +node_js: + - '0.12' + - '4' + - '6' +script: 'npm run test-travis' +after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' diff --git a/index.js b/index.js index 0b4a6e9..953be78 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,7 @@ module.exports = function(opts, app){ throw new Error('this.session can only be set as null or an object.'); }); - return function* (next){ + return function* session(next){ // make sessionOptions independent in each request initSessionOptions(this, opts); try { diff --git a/package.json b/package.json index b18769a..fa456fd 100644 --- a/package.json +++ b/package.json @@ -13,16 +13,16 @@ "index.js" ], "devDependencies": { - "istanbul-harmony": "0", - "koa": "1.1.2", + "istanbul": "0", + "koa": "1", "mocha": "2", - "should": "8.0.2", - "supertest": "1" + "should": "8", + "supertest": "2" }, "license": "MIT", "dependencies": { - "debug": "~2.2.0", - "deep-equal": "~1.0.0" + "debug": "^2.2.0", + "deep-equal": "^1.0.0" }, "scripts": { "test": "NODE_ENV=test mocha --harmony --require should --reporter spec", From 615e26b06889f86dd6512bdf3cc711176837f4b6 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 15 Oct 2016 11:46:24 +0800 Subject: [PATCH 034/154] Release 3.4.0 --- History.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 4a60929..99918d0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,11 @@ +3.4.0 / 2016-10-15 +================== + + * fix: add 'session' name for middleware function (#58) + * chore(package): update dependencies + * readme: ignore favicon in example + 3.3.1 / 2015-07-08 ================== diff --git a/package.json b/package.json index fa456fd..45e1dcf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "3.3.1", + "version": "3.4.0", "keywords": [ "koa", "middleware", From 2ce916d088a2d82d986e42ace5a663d72907b93a Mon Sep 17 00:00:00 2001 From: piggyman007 Date: Sat, 15 Oct 2016 16:17:42 +0700 Subject: [PATCH 035/154] Update Readme.md add detail about config --- Readme.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 0ede205..b79a8b0 100644 --- a/Readme.md +++ b/Readme.md @@ -44,7 +44,16 @@ var koa = require('koa'); var app = koa(); app.keys = ['some secret hurr']; -app.use(session(app)); + +var CONFIG = { + key: xxx /** (string) cookie key (default is koa:sess) */, + maxAge: xxx /** (number) maxAge in ms (default is 1 days) */, + overwrite: xxx /** (boolean) can overwrite or not (default true) */, + httpOnly: xxx /** (boolean) httpOnly or not (default true) */, + signed: xxx /** (boolean) signed or not (default true) */ +}; +app.use(session(CONFIG, app)); +// or if you prefer all default config, just use => app.use(session(app)); app.use(function *(){ // ignore favicon From fdcfba893d1185d8fae6a53ff7674da49e540870 Mon Sep 17 00:00:00 2001 From: piggyman007 Date: Sat, 15 Oct 2016 16:22:44 +0700 Subject: [PATCH 036/154] Update Readme.md add example of isNew property --- Readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Readme.md b/Readme.md index b79a8b0..e50b3e9 100644 --- a/Readme.md +++ b/Readme.md @@ -107,6 +107,15 @@ app.use(convert(session(app))); ### Session#isNew Returns __true__ if the session is new. + + ```js +if (this.session.isNew) { + // user has not logged in +} +else { + // user has already logged in +} +``` ### Session#maxAge From 3bb2f0b3977c92115ed9c431070a2a706d33aace Mon Sep 17 00:00:00 2001 From: haoxin Date: Fri, 28 Oct 2016 14:05:59 +0800 Subject: [PATCH 037/154] update readme --- Readme.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Readme.md b/Readme.md index e50b3e9..0101283 100644 --- a/Readme.md +++ b/Readme.md @@ -46,11 +46,11 @@ var app = koa(); app.keys = ['some secret hurr']; var CONFIG = { - key: xxx /** (string) cookie key (default is koa:sess) */, - maxAge: xxx /** (number) maxAge in ms (default is 1 days) */, - overwrite: xxx /** (boolean) can overwrite or not (default true) */, - httpOnly: xxx /** (boolean) httpOnly or not (default true) */, - signed: xxx /** (boolean) signed or not (default true) */ + key: 'koa:sess', /** (string) cookie key (default is koa:sess) */ + maxAge: 86400000, /** (number) maxAge in ms (default is 1 days) */ + overwrite: true, /** (boolean) can overwrite or not (default true) */ + httpOnly: true, /** (boolean) httpOnly or not (default true) */ + signed: true, /** (boolean) signed or not (default true) */ }; app.use(session(CONFIG, app)); // or if you prefer all default config, just use => app.use(session(app)); @@ -107,12 +107,11 @@ app.use(convert(session(app))); ### Session#isNew Returns __true__ if the session is new. - - ```js + +```js if (this.session.isNew) { // user has not logged in -} -else { +} else { // user has already logged in } ``` From 5c46711699c4717370b7f009c4eed88c5624185a Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 20 Feb 2017 18:38:12 +0800 Subject: [PATCH 038/154] refactor: support external --- index.js | 189 ++++++++++++++++++++++++++++++--------------------- package.json | 4 +- test/test.js | 3 +- 3 files changed, 115 insertions(+), 81 deletions(-) diff --git a/index.js b/index.js index 953be78..4a0fb87 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,8 @@ */ var debug = require('debug')('koa-session'); -var deepEqual = require('deep-equal'); +var crc = require('crc').crc32; +var uid = require('uid-safe').sync; var ONE_DAY = 24 * 60 * 60 * 1000; @@ -64,43 +65,12 @@ module.exports = function(opts, app){ // unset if (false === sess) return null; - var json = this.cookies.get(opts.key, opts); - - if (json) { - debug('parse %s', json); - try { - // make sure sessionOptions exists - initSessionOptions(this, opts); - var obj = opts.decode(json); - if (typeof opts.valid === 'function' && !opts.valid(this, obj)) { - // valid session value fail, ignore this session - sess = new Session(this); - json = obj; - debug('invalid %j', obj); - } else { - sess = new Session(this, obj); - // make prev a different object from sess - json = opts.decode(json); - } - } catch (err) { - // backwards compatibility: - // create a new session if parsing fails. - // new Buffer(string, 'base64') does not seem to crash - // when `string` is not base64-encoded. - // but `JSON.parse(string)` will crash. - debug('decode %j error: %s', json, err); - if (!(err instanceof SyntaxError)) throw err; - sess = new Session(this); - json = null; - } - } else { - debug('new session'); - sess = new Session(this); - } + if (opts.external) return null; - this._sess = sess; - this._prevjson = json; - return sess; + // cookie session store + initSessionFromCookie(this, opts); + if (!this._sess) initSessionFromNull(this); + return this._sess; }); app.context.__defineSetter__('session', function(val){ @@ -112,12 +82,20 @@ module.exports = function(opts, app){ return function* session(next){ // make sessionOptions independent in each request initSessionOptions(this, opts); + // external session store + if (opts.external) { + yield initSessionFromExternal(this, opts); + if (!this._sess) { + initSessionFromNull(this); + initSessionKey(this); + } + } try { - yield* next; + yield next; } catch (err) { throw err; } finally { - commit(this, this._prevjson, this._sess, opts); + yield commit(this, this._prevSessHash, this._sess, opts); } }; }; @@ -142,7 +120,7 @@ function initSessionOptions(ctx, opts) { * @api private */ -function commit(ctx, prevjson, sess, opts) { +function* commit(ctx, prevHash, sess, opts) { // not accessed if (undefined === sess) return; @@ -151,19 +129,99 @@ function commit(ctx, prevjson, sess, opts) { ctx.cookies.set(opts.key, '', opts); return; } - + var json = sess.toJSON(); // do nothing if new and not populated - if (!prevjson && !sess.length) return; + if (!prevHash && !Object.keys(json).length) return; + if (prevHash === hash(json)) return; + + if (typeof opts.beforeSave === 'function') { + debug('before save'); + opts.beforeSave(ctx, sess); + } + yield saveSession(ctx, sess); +} + +function *initSessionFromExternal(ctx, opts) { + var key = ctx.cookies.get(opts.key, opts); + if (!key) return null; + + var obj = yield opts.external.get(key, ctx); + if (!obj) return null; + + ctx._sess = new Session(ctx, obj); + ctx._sessExternalKey = key; + ctx._prevjson = hash(ctx._sess); +} + +function initSessionKey(ctx, opts) { + ctx._sessExternalKey = uid(24); +} + +function initSessionFromCookie(ctx, opts) { + var json = ctx.cookies.get(opts.key, opts); + if (!json) return null; - // save - if (sess.changed(prevjson)) { - if (typeof opts.beforeSave === 'function') { - opts.beforeSave(ctx, sess); + debug('parse %s', json); + try { + var obj = opts.decode(json); + debug('parsed %j', obj); + if (typeof opts.valid === 'function' && !opts.valid(ctx, obj)) { + // valid session value fail, ignore this session + debug('invalid %j', obj); + return null; } - sess.save(); + initSessionOptions(ctx, opts); + ctx._sess = new Session(ctx, obj); + ctx._prevSessHash = hash(ctx._sess); + } catch (err) { + // backwards compatibility: + // create a new session if parsing fails. + // new Buffer(string, 'base64') does not seem to crash + // when `string` is not base64-encoded. + // but `JSON.parse(string)` will crash. + debug('decode %j error: %s', json, err); + if (!(err instanceof SyntaxError)) throw err; + return null; } } +function initSessionFromNull(ctx) { + ctx._sess = new Session(ctx); + ctx._prevSessHash = hash(ctx._sess); +} + +function* saveSession(ctx, sess) { + var json = sess.toJSON(); + var opts = ctx.sessionOptions; + var key = ctx.sessionKey; + var externalKey = ctx._sessExternalKey; + + var maxAge = opts.maxAge || ONE_DAY; + + // save to external store + if (externalKey) { + debug('save %s to external key %s', json, externalKey); + yield opts.external.set(externalKey, json, maxAge); + ctx.cookies.set(key, externalKey, opts); + return; + } + + // save to cookie + debug('save %j to cookie', json); + try { + // set expire into cookie value + json._expire = maxAge + Date.now(); + json._maxAge = maxAge; + json = opts.encode(json); + debug('save %s', json); + } catch (e) { + debug('encode %j error: %s', json, e); + json = ''; + } + + ctx.cookies.set(key, json, opts); +} + /** * Session model. * @@ -218,9 +276,7 @@ Session.prototype.toJSON = function(){ Session.prototype.changed = function(prev){ if (!prev) return true; - delete prev._expire; - delete prev._maxAge; - return !deepEqual(prev, this.toJSON()); + return hash(sess) !== prev; }; /** @@ -268,34 +324,6 @@ Session.prototype.__defineSetter__('maxAge', function(val){ this._ctx.sessionOptions.maxAge = val; }); -/** - * Save session changes by - * performing a Set-Cookie. - * - * @api private - */ - -Session.prototype.save = function(){ - var ctx = this._ctx; - var json = this.toJSON(); - var opts = ctx.sessionOptions; - var key = ctx.sessionKey; - - // set expire into cookie value - var maxAge = opts.maxAge || ONE_DAY; - json._expire = maxAge + Date.now(); - json._maxAge = maxAge; - - try { - json = opts.encode(json); - debug('save %s', json); - } catch (e) { - debug('encode %j error: %s', json, e); - json = ''; - } - ctx.cookies.set(key, json, opts); -}; - /** * Decode the base64 cookie value to an object. * @@ -326,3 +354,8 @@ function encode(body) { body = JSON.stringify(body); return new Buffer(body).toString('base64'); } + +function hash(sess) { + if (sess instanceof Session) sess = sess.toJSON(); + return crc(JSON.stringify(sess)); +} diff --git a/package.json b/package.json index 45e1dcf..37c7b61 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,10 @@ }, "license": "MIT", "dependencies": { + "crc": "^3.4.4", "debug": "^2.2.0", - "deep-equal": "^1.0.0" + "deep-equal": "^1.0.0", + "uid-safe": "^2.1.3" }, "scripts": { "test": "NODE_ENV=test mocha --harmony --require should --reporter spec", diff --git a/test/test.js b/test/test.js index 4290409..eb66c09 100644 --- a/test/test.js +++ b/test/test.js @@ -357,7 +357,6 @@ describe('Koa Session', function(){ this.body = 200; return; } - this.body = this.session.message; }); @@ -566,7 +565,7 @@ describe('Koa Session', function(){ request(app.callback()) .get('/') .set('Cookie', cookies) - .expect(function () { decodeCallCount.should.be.above(1, 'decode was not called'); }) + .expect(function () { decodeCallCount.should.be.above(0, 'decode was not called'); }) .expect(200, function (err, res) { should.not.exist(err); res.body.counter.should.equal(2); From 9c9c815aac4b0a464169b6978363161b76f91059 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 20 Feb 2017 18:42:42 +0800 Subject: [PATCH 039/154] feat: support remove from external --- index.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4a0fb87..e574e49 100644 --- a/index.js +++ b/index.js @@ -126,7 +126,7 @@ function* commit(ctx, prevHash, sess, opts) { // removed if (false === sess) { - ctx.cookies.set(opts.key, '', opts); + yield removeSession(ctx); return; } var json = sess.toJSON(); @@ -190,6 +190,17 @@ function initSessionFromNull(ctx) { ctx._prevSessHash = hash(ctx._sess); } +function* removeSession(ctx) { + var key = ctx.sessionKey; + var opts = ctx.sessionOptions; + var externalKey = ctx._sessExternalKey; + + if (externalKey) { + yield opts.external.remove(externalKey); + } + ctx.cookies.set(opts.key, '', opts); +} + function* saveSession(ctx, sess) { var json = sess.toJSON(); var opts = ctx.sessionOptions; From 44f6b539ecf6ca0817ee782d8a6ca098116b5077 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 20 Feb 2017 19:00:50 +0800 Subject: [PATCH 040/154] refactor: seperate session model --- index.js | 103 +---------------------------------------------- lib/session.js | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 102 deletions(-) create mode 100644 lib/session.js diff --git a/index.js b/index.js index e574e49..3e8a6e7 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ var debug = require('debug')('koa-session'); var crc = require('crc').crc32; var uid = require('uid-safe').sync; +const Session = require('./lib/session'); var ONE_DAY = 24 * 60 * 60 * 1000; @@ -233,108 +234,6 @@ function* saveSession(ctx, sess) { ctx.cookies.set(key, json, opts); } -/** - * Session model. - * - * @param {Context} ctx - * @param {Object} obj - * @api private - */ - -function Session(ctx, obj) { - this._ctx = ctx; - if (!obj) { - this.isNew = true; - } - else { - for (var k in obj) { - // change session options - if ('_maxAge' == k) this._ctx.sessionOptions.maxAge = obj._maxAge; - else this[k] = obj[k]; - } - } -} - -/** - * JSON representation of the session. - * - * @return {Object} - * @api public - */ - -Session.prototype.inspect = -Session.prototype.toJSON = function(){ - var self = this; - var obj = {}; - - Object.keys(this).forEach(function(key){ - if ('isNew' == key) return; - if ('_' == key[0]) return; - obj[key] = self[key]; - }); - - return obj; -}; - -/** - * Check if the session has changed relative to the `prev` - * JSON value from the request. - * - * @param {Object} [prev] - * @return {Boolean} - * @api private - */ - -Session.prototype.changed = function(prev){ - if (!prev) return true; - return hash(sess) !== prev; -}; - -/** - * Return how many values there are in the session object. - * Used to see if it's "populated". - * - * @return {Number} - * @api public - */ - -Session.prototype.__defineGetter__('length', function(){ - return Object.keys(this.toJSON()).length; -}); - -/** - * populated flag, which is just a boolean alias of .length. - * - * @return {Boolean} - * @api public - */ - -Session.prototype.__defineGetter__('populated', function(){ - return !!this.length; -}); - -/** - * get session maxAge - * - * @return {Number} - * @api public - */ - -Session.prototype.__defineGetter__('maxAge', function(){ - return this._ctx.sessionOptions.maxAge; -}); - -/** - * set session maxAge - * - * @param {Number} - * @api public - */ - -Session.prototype.__defineSetter__('maxAge', function(val){ - this._ctx.sessionOptions.maxAge = val; -}); - /** * Decode the base64 cookie value to an object. * diff --git a/lib/session.js b/lib/session.js new file mode 100644 index 0000000..b76e7a5 --- /dev/null +++ b/lib/session.js @@ -0,0 +1,107 @@ +'use strict'; + +/** + * Session model. + */ + +class Session { + /** + * Session constructor + * @param {Context} ctx + * @param {Object} obj + * @api private + */ + + constructor(ctx, obj) { + this._ctx = ctx; + if (!obj) { + this.isNew = true; + } + else { + for (var k in obj) { + // change session options + if ('_maxAge' == k) this._ctx.sessionOptions.maxAge = obj._maxAge; + else this[k] = obj[k]; + } + } + } + + + /** + * JSON representation of the session. + * + * @return {Object} + * @api public + */ + + toJSON() { + var obj = {}; + + Object.keys(this).forEach(key => { + if ('isNew' == key) return; + if ('_' == key[0]) return; + obj[key] = this[key]; + }); + + return obj; + } + + /** + * + * alias to `toJSON` + * @api public + */ + + inspect() { + return this.toJSON(); + } + + /** + * Return how many values there are in the session object. + * Used to see if it's "populated". + * + * @return {Number} + * @api public + */ + + get length() { + return Object.keys(this.toJSON()).length; + } + + + /** + * populated flag, which is just a boolean alias of .length. + * + * @return {Boolean} + * @api public + */ + + get populated() { + return !!this.length; + } + + + /** + * get session maxAge + * + * @return {Number} + * @api public + */ + + get maxAge() { + return this._ctx.sessionOptions.maxAge; + } + + /** + * set session maxAge + * + * @param {Number} + * @api public + */ + + set maxAge(val) { + this._ctx.sessionOptions.maxAge = val; + } +}; + +module.exports = Session; From e34d3db09b6df088331d58fe3a01d3f4a8118c41 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 11:43:52 +0800 Subject: [PATCH 041/154] refactor: use context session --- index.js | 263 +++++++++---------------------------------------- lib/context.js | 182 ++++++++++++++++++++++++++++++++++ lib/util.js | 39 ++++++++ 3 files changed, 268 insertions(+), 216 deletions(-) create mode 100644 lib/context.js create mode 100644 lib/util.js diff --git a/index.js b/index.js index 3e8a6e7..c2a37c8 100644 --- a/index.js +++ b/index.js @@ -2,12 +2,9 @@ * Module dependencies. */ -var debug = require('debug')('koa-session'); -var crc = require('crc').crc32; -var uid = require('uid-safe').sync; -const Session = require('./lib/session'); - -var ONE_DAY = 24 * 60 * 60 * 1000; +const debug = require('debug')('koa-session'); +const ContextSession = require('./lib/context'); +const util = require('./lib/util'); /** * Initialize session middleware with `opts`: @@ -27,9 +24,29 @@ module.exports = function(opts, app){ app = opts; opts = tmp; } + // app required + if (!app || typeof app.use !== 'function') { + throw new TypeError('app instance required: `session(opts, app)`'); + } - opts = opts || {}; + opts = formatOpts(opts); + extendContext(app.context, opts); + + return function* session(next) { + if (this.sess.external) yield this.sess.initFromExternal(); + try { + yield next; + } catch (err) { + throw err; + } finally { + yield this.sess.commit(); + } + }; +}; + +function formatOpts(opts) { + opts = opts || {}; // key opts.key = opts.key || 'koa:sess'; @@ -43,229 +60,43 @@ module.exports = function(opts, app){ debug('session options %j', opts); - if (!app || typeof app.use !== 'function') { - throw new TypeError('app instance required: `session(opts, app)`'); - } - // setup encoding/decoding if (typeof opts.encode !== 'function') { - opts.encode = encode + opts.encode = util.encode; } if (typeof opts.decode !== 'function') { - opts.decode = decode + opts.decode = util.decode; } - // to pass to Session() - app.context.sessionKey = opts.key; + return opts; +} - app.context.__defineGetter__('session', function(){ - var sess = this._sess; - // already retrieved - if (sess) return sess; +function extendContext(context, opts) { + context.__defineGetter__('sess', function() { + if (this._sess) return this._sess; + this._sess = new ContextSession(this, opts); + return this._sess; + }); + context.__defineGetter__('session', function() { + const session = this.sess.session; + // already retrieved + if (session) return session; // unset - if (false === sess) return null; - - if (opts.external) return null; + if (false === session) return null; // cookie session store - initSessionFromCookie(this, opts); - if (!this._sess) initSessionFromNull(this); - return this._sess; + this.sess.initFromCookie(this, opts); + return this.sess.session; }); - app.context.__defineSetter__('session', function(val){ - if (null == val) return this._sess = false; - if ('object' == typeof val) return this._sess = new Session(this, val); + context.__defineSetter__('session', function(val) { + if (null == val) return this.sess.session = false; + if ('object' == typeof val) return this.sess.createSession(val); throw new Error('this.session can only be set as null or an object.'); }); - return function* session(next){ - // make sessionOptions independent in each request - initSessionOptions(this, opts); - // external session store - if (opts.external) { - yield initSessionFromExternal(this, opts); - if (!this._sess) { - initSessionFromNull(this); - initSessionKey(this); - } - } - try { - yield next; - } catch (err) { - throw err; - } finally { - yield commit(this, this._prevSessHash, this._sess, opts); - } - }; -}; - -function initSessionOptions(ctx, opts) { - if (ctx.sessionOptions) { - return; - } - ctx.sessionOptions = {}; - for (var key in opts) { - ctx.sessionOptions[key] = opts[key]; - } -} - -/** - * Commit the session changes or removal. - * - * @param {Context} ctx - * @param {Object} prevjson - * @param {Object} sess - * @param {Object} opts - * @api private - */ - -function* commit(ctx, prevHash, sess, opts) { - // not accessed - if (undefined === sess) return; - - // removed - if (false === sess) { - yield removeSession(ctx); - return; - } - var json = sess.toJSON(); - // do nothing if new and not populated - if (!prevHash && !Object.keys(json).length) return; - if (prevHash === hash(json)) return; - - if (typeof opts.beforeSave === 'function') { - debug('before save'); - opts.beforeSave(ctx, sess); - } - yield saveSession(ctx, sess); -} - -function *initSessionFromExternal(ctx, opts) { - var key = ctx.cookies.get(opts.key, opts); - if (!key) return null; - - var obj = yield opts.external.get(key, ctx); - if (!obj) return null; - - ctx._sess = new Session(ctx, obj); - ctx._sessExternalKey = key; - ctx._prevjson = hash(ctx._sess); -} - -function initSessionKey(ctx, opts) { - ctx._sessExternalKey = uid(24); -} - -function initSessionFromCookie(ctx, opts) { - var json = ctx.cookies.get(opts.key, opts); - if (!json) return null; - - debug('parse %s', json); - try { - var obj = opts.decode(json); - debug('parsed %j', obj); - if (typeof opts.valid === 'function' && !opts.valid(ctx, obj)) { - // valid session value fail, ignore this session - debug('invalid %j', obj); - return null; - } - initSessionOptions(ctx, opts); - ctx._sess = new Session(ctx, obj); - ctx._prevSessHash = hash(ctx._sess); - } catch (err) { - // backwards compatibility: - // create a new session if parsing fails. - // new Buffer(string, 'base64') does not seem to crash - // when `string` is not base64-encoded. - // but `JSON.parse(string)` will crash. - debug('decode %j error: %s', json, err); - if (!(err instanceof SyntaxError)) throw err; - return null; - } -} - -function initSessionFromNull(ctx) { - ctx._sess = new Session(ctx); - ctx._prevSessHash = hash(ctx._sess); -} - -function* removeSession(ctx) { - var key = ctx.sessionKey; - var opts = ctx.sessionOptions; - var externalKey = ctx._sessExternalKey; - - if (externalKey) { - yield opts.external.remove(externalKey); - } - ctx.cookies.set(opts.key, '', opts); -} - -function* saveSession(ctx, sess) { - var json = sess.toJSON(); - var opts = ctx.sessionOptions; - var key = ctx.sessionKey; - var externalKey = ctx._sessExternalKey; - - var maxAge = opts.maxAge || ONE_DAY; - - // save to external store - if (externalKey) { - debug('save %s to external key %s', json, externalKey); - yield opts.external.set(externalKey, json, maxAge); - ctx.cookies.set(key, externalKey, opts); - return; - } - - // save to cookie - debug('save %j to cookie', json); - try { - // set expire into cookie value - json._expire = maxAge + Date.now(); - json._maxAge = maxAge; - json = opts.encode(json); - debug('save %s', json); - } catch (e) { - debug('encode %j error: %s', json, e); - json = ''; - } - - ctx.cookies.set(key, json, opts); -} - -/** - * Decode the base64 cookie value to an object. - * - * @param {String} string - * @return {Object} - * @api private - */ - -function decode(string) { - var body = new Buffer(string, 'base64').toString('utf8'); - var json = JSON.parse(body); - - // check if the cookie is expired - if (!json._expire) return null; - if (json._expire < Date.now()) return null; - return json; -} - -/** - * Encode an object into a base64-encoded JSON string. - * - * @param {Object} body - * @return {String} - * @api private - */ - -function encode(body) { - body = JSON.stringify(body); - return new Buffer(body).toString('base64'); -} - -function hash(sess) { - if (sess instanceof Session) sess = sess.toJSON(); - return crc(JSON.stringify(sess)); + context.__defineGetter__('sessionOptions', function() { + return this.sess.opts; + }); } diff --git a/lib/context.js b/lib/context.js new file mode 100644 index 0000000..8af36a9 --- /dev/null +++ b/lib/context.js @@ -0,0 +1,182 @@ +'use strict'; + +const debug = require('debug')('koa-session:context'); +const Session = require('./session'); +const uid = require('uid-safe'); +const util = require('./util'); + +const ONE_DAY = 24 * 60 * 60 * 1000; + +class ContextSession { + /** + * context session constructor + * @api public + */ + + constructor(ctx, opts) { + this.ctx = ctx; + this.opts = Object.assign({}, opts); + } + + /** + * init session from external store + * @api public + */ + + * initFromExternal() { + const ctx = this.ctx; + const opts = this.opts; + + const externalKey = ctx.cookies.get(opts.key, opts); + if (!externalKey) return this.createSession(); + + var obj = yield opts.external.get(externalKey, ctx); + if (!obj) return this.createSession(); + + this.createSession(obj, externalKey); + this.prevHash = util.hash(this.ctx._sess.toJSON()); + } + + /** + * init session from cookie + * @api public + */ + initFromCookie() { + const ctx = this.ctx; + const opts = this.opts; + + const cookie = ctx.cookies.get(opts.key, opts); + if (!cookie) return this.createSession(); + + let json; + debug('parse %s', cookie); + try { + json = opts.decode(cookie); + } catch (err) { + // backwards compatibility: + // create a new session if parsing fails. + // new Buffer(string, 'base64') does not seem to crash + // when `string` is not base64-encoded. + // but `JSON.parse(string)` will crash. + debug('decode %j error: %s', cookie, err); + if (!(err instanceof SyntaxError)) throw err; + return this.createSession(); + } + + debug('parsed %j', json); + + if (!json._expire || json._expire < Date.now()) { + debug('expired session'); + return this.createSession(); + } + + if (typeof opts.valid === 'function' && !opts.valid(ctx, json)) { + // valid session value fail, ignore this session + debug('invalid session'); + return this.createSession(); + } + + // support access `ctx.session` before session middleware + this.createSession(json); + this.prevHash = util.hash(this.session.toJSON()); + } + + /** + * create a new session and attach to ctx.sess + * + * @param {Object} [val] + * @param {String} [externalKey] + * @api private + */ + createSession(val, externalKey) { + if (this.opts.external) this.externalKey = externalKey || uid(24); + this.session = new Session(this.ctx, val); + } + + /** + * Commit the session changes or removal. + * + * @api public + */ + + * commit() { + const session = this.session; + const prevHash = this.prevHash; + const opts = this.opts; + const ctx = this.ctx; + + // not accessed + if (undefined === session) return; + + // removed + if (false === session) { + yield this.removeSession(); + return; + } + const json = session.toJSON(); + // do nothing if new and not populated + if (!prevHash && !Object.keys(json).length) return; + // do nothing if not changed + if (prevHash === util.hash(json)) return; + + if (typeof opts.beforeSave === 'function') { + debug('before save'); + opts.beforeSave(ctx, session); + } + yield this.saveSession(); + } + + + /** + * remove session + * @api private + */ + * removeSession() { + const opts = this.opts; + const ctx = this.ctx; + const key = opts.key; + const externalKey = this.externalKey; + + if (externalKey) yield opts.external.remove(externalKey); + ctx.cookies.set(key, '', opts); + } + + /** + * save session + * @api private + */ + * saveSession() { + let json = this.session.toJSON(); + // + const opts = this.opts; + const key = opts.key; + const externalKey = this.externalKey; + + const maxAge = opts.maxAge || ONE_DAY; + + // save to external store + if (externalKey) { + debug('save %s to external key %s', json, externalKey); + yield opts.external.set(externalKey, json, maxAge); + this.ctx.cookies.set(key, externalKey, opts); + return; + } + + // save to cookie + debug('save %j to cookie', json); + try { + // set expire into cookie value + json._expire = maxAge + Date.now(); + json._maxAge = maxAge; + json = opts.encode(json); + debug('save %s', json); + } catch (e) { + debug('encode %j error: %s', json, e); + json = ''; + } + + this.ctx.cookies.set(key, json, opts); + } +} + +module.exports = ContextSession; diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..2e89d0f --- /dev/null +++ b/lib/util.js @@ -0,0 +1,39 @@ +'use strict'; + +const crc = require('crc').crc32; +const Session = require('./session'); + +module.exports = { + + /** + * Decode the base64 cookie value to an object. + * + * @param {String} string + * @return {Object} + * @api private + */ + + decode(string) { + var body = new Buffer(string, 'base64').toString('utf8'); + var json = JSON.parse(body); + return json; + }, + + /** + * Encode an object into a base64-encoded JSON string. + * + * @param {Object} body + * @return {String} + * @api private + */ + + encode(body) { + body = JSON.stringify(body); + return new Buffer(body).toString('base64'); + }, + + hash(sess) { + if (sess instanceof Session) sess = sess.toJSON(); + return crc(JSON.stringify(sess)); + }, +}; From c7c9b33e2b89b06d2f9237dd73e3c0b9da81b055 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 12:49:55 +0800 Subject: [PATCH 042/154] fix: files add lib --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 37c7b61..cd32306 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "cookie" ], "files": [ - "index.js" + "index.js", + "lib" ], "devDependencies": { "istanbul": "0", From ec44f964eaa36d793737fc74c2e5ca4e1f05b277 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 13:23:13 +0800 Subject: [PATCH 043/154] test: remove 0.12, add 7 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 01de166..0c63626 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ sudo: false language: node_js node_js: - - '0.12' - '4' - '6' + - '7' script: 'npm run test-travis' after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' From 83bb00171d15f1da5d32a24c6d4cae69d1f9ffd1 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 17:53:12 +0800 Subject: [PATCH 044/154] test: add external test cases and eslint --- .eslintrc | 3 + index.js | 1 - lib/context.js | 89 ++++-- lib/session.js | 18 +- lib/util.js | 4 +- package.json | 11 +- test/{test.js => cookie.test.js} | 495 ++++++++++++++++--------------- test/external.js | 17 ++ test/external.test.js | 447 ++++++++++++++++++++++++++++ 9 files changed, 793 insertions(+), 292 deletions(-) create mode 100644 .eslintrc rename test/{test.js => cookie.test.js} (52%) create mode 100644 test/external.js create mode 100644 test/external.test.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..79794d7 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "eslint-config-egg", +} diff --git a/index.js b/index.js index c2a37c8..36e2d94 100644 --- a/index.js +++ b/index.js @@ -34,7 +34,6 @@ module.exports = function(opts, app){ return function* session(next) { if (this.sess.external) yield this.sess.initFromExternal(); - try { yield next; } catch (err) { diff --git a/lib/context.js b/lib/context.js index 8af36a9..016b8d3 100644 --- a/lib/context.js +++ b/lib/context.js @@ -16,6 +16,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; this.opts = Object.assign({}, opts); + this.external = this.opts.external; } /** @@ -24,17 +25,31 @@ class ContextSession { */ * initFromExternal() { + debug('init from external'); const ctx = this.ctx; const opts = this.opts; const externalKey = ctx.cookies.get(opts.key, opts); - if (!externalKey) return this.createSession(); + debug('get external key from cookie %s', externalKey); - var obj = yield opts.external.get(externalKey, ctx); - if (!obj) return this.createSession(); + if (!externalKey) { + this.createSession(); + return; + } + + const json = yield this.external.get(externalKey, ctx); + if (!this.valid(json)) { + this.createSession(); + return; + } + + if (!json) { + this.createSession(); + return; + } - this.createSession(obj, externalKey); - this.prevHash = util.hash(this.ctx._sess.toJSON()); + this.createSession(json, externalKey); + this.prevHash = util.hash(this.session.toJSON()); } /** @@ -42,11 +57,15 @@ class ContextSession { * @api public */ initFromCookie() { + debug('init from cookie'); const ctx = this.ctx; const opts = this.opts; const cookie = ctx.cookies.get(opts.key, opts); - if (!cookie) return this.createSession(); + if (!cookie) { + this.createSession(); + return; + } let json; debug('parse %s', cookie); @@ -60,36 +79,53 @@ class ContextSession { // but `JSON.parse(string)` will crash. debug('decode %j error: %s', cookie, err); if (!(err instanceof SyntaxError)) throw err; - return this.createSession(); + this.createSession(); + return; } debug('parsed %j', json); + if (!this.valid(json)) { + this.createSession(); + return; + } + + // support access `ctx.session` before session middleware + this.createSession(json); + this.prevHash = util.hash(this.session.toJSON()); + } + + /** + * verify session(expired or ) + * @param {Object} json session object + * @return {Boolean} valid + * @api private + */ + valid(json) { if (!json._expire || json._expire < Date.now()) { debug('expired session'); - return this.createSession(); + return false; } - if (typeof opts.valid === 'function' && !opts.valid(ctx, json)) { + const valid = this.opts.valid; + if (typeof valid === 'function' && !valid(this.ctx, json)) { // valid session value fail, ignore this session debug('invalid session'); - return this.createSession(); + return false; } - - // support access `ctx.session` before session middleware - this.createSession(json); - this.prevHash = util.hash(this.session.toJSON()); + return true; } /** * create a new session and attach to ctx.sess * - * @param {Object} [val] - * @param {String} [externalKey] + * @param {Object} [val] session data + * @param {String} [externalKey] session external key * @api private */ createSession(val, externalKey) { - if (this.opts.external) this.externalKey = externalKey || uid(24); + debug('create session with val: %j externalKey: %s', val, externalKey); + if (this.opts.external) this.externalKey = externalKey || uid.sync(24); this.session = new Session(this.ctx, val); } @@ -109,7 +145,7 @@ class ContextSession { if (undefined === session) return; // removed - if (false === session) { + if (session === false) { yield this.removeSession(); return; } @@ -126,7 +162,6 @@ class ContextSession { yield this.saveSession(); } - /** * remove session * @api private @@ -137,7 +172,7 @@ class ContextSession { const key = opts.key; const externalKey = this.externalKey; - if (externalKey) yield opts.external.remove(externalKey); + if (externalKey) yield this.external.remove(externalKey); ctx.cookies.set(key, '', opts); } @@ -146,18 +181,21 @@ class ContextSession { * @api private */ * saveSession() { - let json = this.session.toJSON(); - // const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; const maxAge = opts.maxAge || ONE_DAY; + let json = this.session.toJSON(); + // set expire for check + json._expire = maxAge + Date.now(); + json._maxAge = maxAge; + // save to external store if (externalKey) { - debug('save %s to external key %s', json, externalKey); - yield opts.external.set(externalKey, json, maxAge); + debug('save %j to external key %s', json, externalKey); + yield this.external.set(externalKey, json, maxAge); this.ctx.cookies.set(key, externalKey, opts); return; } @@ -165,9 +203,6 @@ class ContextSession { // save to cookie debug('save %j to cookie', json); try { - // set expire into cookie value - json._expire = maxAge + Date.now(); - json._maxAge = maxAge; json = opts.encode(json); debug('save %s', json); } catch (e) { diff --git a/lib/session.js b/lib/session.js index b76e7a5..0c140e8 100644 --- a/lib/session.js +++ b/lib/session.js @@ -16,17 +16,15 @@ class Session { this._ctx = ctx; if (!obj) { this.isNew = true; - } - else { - for (var k in obj) { + } else { + for (const k in obj) { // change session options - if ('_maxAge' == k) this._ctx.sessionOptions.maxAge = obj._maxAge; + if (k === '_maxAge') this._ctx.sessionOptions.maxAge = obj._maxAge; else this[k] = obj[k]; } } } - /** * JSON representation of the session. * @@ -35,11 +33,11 @@ class Session { */ toJSON() { - var obj = {}; + const obj = {}; Object.keys(this).forEach(key => { - if ('isNew' == key) return; - if ('_' == key[0]) return; + if (key === 'isNew') return; + if (key[0] === '_') return; obj[key] = this[key]; }); @@ -68,7 +66,6 @@ class Session { return Object.keys(this.toJSON()).length; } - /** * populated flag, which is just a boolean alias of .length. * @@ -80,7 +77,6 @@ class Session { return !!this.length; } - /** * get session maxAge * @@ -102,6 +98,6 @@ class Session { set maxAge(val) { this._ctx.sessionOptions.maxAge = val; } -}; +} module.exports = Session; diff --git a/lib/util.js b/lib/util.js index 2e89d0f..31642e9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -14,8 +14,8 @@ module.exports = { */ decode(string) { - var body = new Buffer(string, 'base64').toString('utf8'); - var json = JSON.parse(body); + const body = new Buffer(string, 'base64').toString('utf8'); + const json = JSON.parse(body); return json; }, diff --git a/package.json b/package.json index cd32306..ae154fe 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "lib" ], "devDependencies": { + "eslint": "3", + "eslint-config-egg": "3", "istanbul": "0", "koa": "1", - "mocha": "2", + "mocha": "3 ", "should": "8", "supertest": "2" }, @@ -28,8 +30,9 @@ "uid-safe": "^2.1.3" }, "scripts": { - "test": "NODE_ENV=test mocha --harmony --require should --reporter spec", - "test-cov": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should", - "test-travis": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should" + "test": "npm run lint && NODE_ENV=test1 mocha --harmony --require should --reporter spec test/*.test.js", + "test-cov": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", + "test-travis": "npm run lint && NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", + "lint": "eslint lib test" } } diff --git a/test/test.js b/test/cookie.test.js similarity index 52% rename from test/test.js rename to test/cookie.test.js index eb66c09..d0adbbf 100644 --- a/test/test.js +++ b/test/cookie.test.js @@ -1,20 +1,22 @@ -var koa = require('koa'); -var request = require('supertest'); -var should = require('should'); -var session = require('..'); +'use strict'; -describe('Koa Session', function(){ - var cookie; +const koa = require('koa'); +const request = require('supertest'); +const should = require('should'); +const session = require('..'); - describe('when options.signed = true', function(){ - describe('when app.keys are set', function(){ - it('should work', function(done){ - var app = koa(); +describe('Koa Session Cookie', () => { + let cookie; - app.keys = ['a', 'b']; + describe('when options.signed = true', () => { + describe('when app.keys are set', () => { + it('should work', done => { + const app = koa(); + + app.keys = [ 'a', 'b' ]; app.use(session({}, app)); - app.use(function *(){ + app.use(function* () { this.session.message = 'hi'; this.body = this.session; }); @@ -22,16 +24,16 @@ describe('Koa Session', function(){ request(app.listen()) .get('/') .expect(200, done); - }) - }) + }); + }); - describe('when app.keys are not set', function(){ - it('should throw', function(done){ - var app = koa(); + describe('when app.keys are not set', () => { + it('should throw', done => { + const app = koa(); app.use(session(app)); - app.use(function *(){ + app.use(function* () { this.session.message = 'hi'; this.body = this.session; }); @@ -39,27 +41,27 @@ describe('Koa Session', function(){ request(app.listen()) .get('/') .expect(500, done); - }) - }) + }); + }); - describe('when app not set', function(){ - it('should throw', function(){ - var app = koa(); - (function(){ + describe('when app not set', () => { + it('should throw', () => { + const app = koa(); + (function() { app.use(session()); }).should.throw('app instance required: `session(opts, app)`'); - }) - }) - }) + }); + }); + }); - describe('when options.signed = false', function(){ - describe('when app.keys are not set', function(){ - it('should work', function(done){ - var app = koa(); + describe('when options.signed = false', () => { + describe('when app.keys are not set', () => { + it('should work', done => { + const app = koa(); app.use(session({ signed: false }, app)); - app.use(function *(){ + app.use(function* () { this.session.message = 'hi'; this.body = this.session; }); @@ -67,15 +69,15 @@ describe('Koa Session', function(){ request(app.listen()) .get('/') .expect(200, done); - }) - }) - }) + }); + }); + }); - describe('when the session contains a ;', function(){ - it('should still work', function(done){ - var app = App(); + describe('when the session contains a ;', () => { + it('should still work', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { if (this.method === 'POST') { this.session.string = ';'; this.status = 204; @@ -84,244 +86,244 @@ describe('Koa Session', function(){ } }); - var server = app.listen(); + const server = app.listen(); request(server) .post('/') - .expect(204, function(err, res){ + .expect(204, (err, res) => { if (err) return done(err); - var cookie = res.headers['set-cookie']; + const cookie = res.headers['set-cookie']; request(server) .get('/') .set('Cookie', cookie.join(';')) .expect(';', done); - }) - }) - }) + }); + }); + }); - describe('new session', function(){ - describe('when not accessed', function(){ - it('should not Set-Cookie', function(done) { - var app = App(); + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.body = 'greetings'; - }) + }); request(app.listen()) .get('/') - .expect(200, function(err, res){ + .expect(200, (err, res) => { if (err) return done(err); res.header.should.not.have.property('set-cookie'); done(); - }) - }) - }) + }); + }); + }); - describe('when accessed and not populated', function(done){ - it('should not Set-Cookie', function(done) { - var app = App(); + describe('when accessed and not populated', () => { + it('should not Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session; this.body = 'greetings'; - }) + }); request(app.listen()) .get('/') - .expect(200, function(err, res){ + .expect(200, (err, res) => { if (err) return done(err); res.header.should.not.have.property('set-cookie'); done(); - }) - }) - }) + }); + }); + }); - describe('when populated', function(done){ - it('should Set-Cookie', function(done){ - var app = App(); + describe('when populated', () => { + it('should Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session.message = 'hello'; this.body = ''; - }) + }); request(app.listen()) .get('/') .expect('Set-Cookie', /koa:sess/) - .expect(200, function(err, res){ + .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); done(); - }) - }) + }); + }); - it('should not Set-Cookie', function(done){ - var app = App(); + it('should not Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.body = this.session; - }) + }); request(app.listen()) .get('/') - .expect(200, function(err, res){ + .expect(200, (err, res) => { if (err) return done(err); res.header.should.not.have.property('set-cookie'); done(); - }) - }) - }) - }) + }); + }); + }); + }); - describe('saved session', function(){ - describe('when not accessed', function(){ - it('should not Set-Cookie', function(done){ - var app = App(); + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.body = 'aklsdjflasdjf'; - }) + }); request(app.listen()) .get('/') .set('Cookie', cookie) - .expect(200, function(err, res){ + .expect(200, (err, res) => { if (err) return done(err); res.header.should.not.have.property('set-cookie'); done(); - }) - }) - }) + }); + }); + }); - describe('when accessed but not changed', function(){ - it('should be the same session', function(done){ - var app = App(); + describe('when accessed but not changed', () => { + it('should be the same session', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session.message.should.equal('hello'); this.body = 'aklsdjflasdjf'; - }) + }); request(app.listen()) .get('/') .set('Cookie', cookie) .expect(200, done); - }) + }); - it('should not Set-Cookie', function(done){ - var app = App(); + it('should not Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session.message.should.equal('hello'); this.body = 'aklsdjflasdjf'; - }) + }); request(app.listen()) .get('/') .set('Cookie', cookie) - .expect(200, function(err, res){ + .expect(200, (err, res) => { if (err) return done(err); res.header.should.not.have.property('set-cookie'); done(); - }) - }) - }) + }); + }); + }); - describe('when accessed and changed', function(){ - it('should Set-Cookie', function(done){ - var app = App(); + describe('when accessed and changed', () => { + it('should Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session.money = '$$$'; this.body = 'aklsdjflasdjf'; - }) + }); request(app.listen()) .get('/') .set('Cookie', cookie) .expect('Set-Cookie', /koa:sess/) .expect(200, done); - }) - }) - }) + }); + }); + }); - describe('when session is', function(){ - describe('null', function(){ - it('should expire the session', function(done){ - var app = App(); + describe('when session is', () => { + describe('null', () => { + it('should expire the session', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session = null; this.body = 'asdf'; - }) + }); request(app.listen()) .get('/') .expect('Set-Cookie', /koa:sess/) .expect(200, done); - }) - }) + }); + }); - describe('an empty object', function(){ - it('should not Set-Cookie', function(done){ - var app = App(); + describe('an empty object', () => { + it('should not Set-Cookie', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session = {}; this.body = 'asdf'; - }) + }); request(app.listen()) .get('/') - .expect(200, function(err, res){ + .expect(200, (err, res) => { if (err) return done(err); res.header.should.not.have.property('set-cookie'); done(); }); - }) - }) + }); + }); - describe('an object', function(){ - it('should create a session', function(done){ - var app = App(); + describe('an object', () => { + it('should create a session', done => { + const app = App(); - app.use(function *(){ + app.use(function* () { this.session = { message: 'hello' }; this.body = 'asdf'; - }) + }); request(app.listen()) .get('/') .expect('Set-Cookie', /koa:sess/) .expect(200, done); - }) - }) + }); + }); - describe('anything else', function(){ - it('should throw', function(done){ - var app = App(); + describe('anything else', () => { + it('should throw', done => { + const app = App(); - app.use(function *(){ - this.session = 'asdf' - }) + app.use(function* () { + this.session = 'asdf'; + }); request(app.listen()) .get('/') .expect(500, done); - }) - }) - }) + }); + }); + }); - describe('when an error is thrown downstream and caught upstream', function(){ - it('should still save the session', function(done){ - var app = koa(); + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', done => { + const app = koa(); - app.keys = ['a', 'b']; + app.keys = [ 'a', 'b' ]; - app.use(function *(next){ + app.use(function* (next) { try { - yield *next; + yield* next; } catch (err) { this.status = err.status; this.body = err.message; @@ -330,12 +332,12 @@ describe('Koa Session', function(){ app.use(session(app)); - app.use(function *(next){ + app.use(function* (next) { this.session.name = 'funny'; - yield *next; + yield* next; }); - app.use(function *(next){ + app.use(function* () { this.throw(401); }); @@ -343,13 +345,13 @@ describe('Koa Session', function(){ .get('/') .expect('Set-Cookie', /koa:sess/) .expect(401, done); - }) - }) + }); + }); - describe('when maxAge present', function () { - describe('and not expire', function () { - it('should not expire the session', function (done) { - var app = App({ maxAge: 100 }); + describe('when maxAge present', () => { + describe('and not expire', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 100 }); app.use(function* () { if (this.method === 'POST') { @@ -360,27 +362,26 @@ describe('Koa Session', function(){ this.body = this.session.message; }); - var server = app.listen(); + const server = app.listen(); request(server) .post('/') .expect('Set-Cookie', /koa:sess/) - .end(function (err, res) { + .end((err, res) => { if (err) return done(err); - var cookie = res.headers['set-cookie'].join(';'); + const cookie = res.headers['set-cookie'].join(';'); request(server) .get('/') .set('cookie', cookie) .expect('hi', done); - }) - }) - }) - + }); + }); + }); - describe('and expired', function () { - it('should expire the sess', function (done) { - var app = App({ maxAge: 100 }); + describe('and expired', () => { + it('should expire the sess', done => { + const app = App({ maxAge: 100 }); app.use(function* () { if (this.method === 'POST') { @@ -392,45 +393,45 @@ describe('Koa Session', function(){ this.body = this.session.message || ''; }); - var server = app.listen(); + const server = app.listen(); request(server) .post('/') .expect('Set-Cookie', /koa:sess/) - .end(function (err, res) { + .end((err, res) => { if (err) return done(err); - var cookie = res.headers['set-cookie'].join(';'); + const cookie = res.headers['set-cookie'].join(';'); - setTimeout(function () { - request(server) + setTimeout(() => { + request(server) .get('/') .set('cookie', cookie) .expect('', done); }, 200); - }) - }) - }) - }) + }); + }); + }); + }); - describe('ctx.session.maxAge', function (){ - it('should return opt.maxAge', function(done){ - var app = App({maxAge: 100}); + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', done => { + const app = App({ maxAge: 100 }); - app.use(function *(){ + app.use(function* () { this.body = this.session.maxAge; }); request(app.listen()) .get('/') .expect('100', done); - }) - }) + }); + }); - describe('ctx.session.maxAge=', function () { - it('should set sessionOptions.maxAge', function(done){ - var app = App(); + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', done => { + const app = App(); - app.use(function* (){ + app.use(function* () { this.session.foo = 'bar'; this.session.maxAge = 100; this.body = this.session.foo; @@ -440,28 +441,28 @@ describe('Koa Session', function(){ .get('/') .expect('Set-Cookie', /expires=/) .expect(200, done); - }) - }) + }); + }); - describe('when get session before enter session middleware', function(){ - it('should work', function(done){ - var app = koa(); + describe('when get session before enter session middleware', () => { + it('should work', done => { + const app = koa(); - app.keys = ['a', 'b']; + app.keys = [ 'a', 'b' ]; app.use(function* (next) { this.session.foo = 'hi'; yield next; }); app.use(session({}, app)); - app.use(function* (){ + app.use(function* () { this.body = this.session; }); request(app.callback()) .get('/') - .expect(200, function (err, res) { + .expect(200, (err, res) => { should.not.exist(err); - var cookies = res.headers['set-cookie'].join(';'); + const cookies = res.headers['set-cookie'].join(';'); cookies.should.containEql('koa:sess='); request(app.callback()) @@ -469,21 +470,21 @@ describe('Koa Session', function(){ .set('Cookie', cookies) .expect(200, done); }); - }) - }) + }); + }); - describe('when valid and beforeSave set', function(){ - it('should ignore session when uid changed', function(done){ - var app = koa(); + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', done => { + const app = koa(); - app.keys = ['a', 'b']; + app.keys = [ 'a', 'b' ]; app.use(session({ - valid: function (ctx, sess) { + valid(ctx, sess) { return ctx.cookies.get('uid') === sess.uid; }, - beforeSave: function (ctx, sess) { + beforeSave(ctx, sess) { sess.uid = ctx.cookies.get('uid'); - } + }, }, app)); app.use(function* () { if (!this.session.foo) { @@ -499,24 +500,24 @@ describe('Koa Session', function(){ request(app.callback()) .get('/') .set('Cookie', 'uid=123') - .expect(200, function (err, res) { + .expect(200, (err, res) => { should.not.exist(err); - var data = res.body; - var cookies = res.headers['set-cookie'].join(';'); + const data = res.body; + const cookies = res.headers['set-cookie'].join(';'); cookies.should.containEql('koa:sess='); request(app.callback()) .get('/') .set('Cookie', cookies + ';uid=123') .expect(200) - .expect(data, function (err) { + .expect(data, err => { should.not.exist(err); // should ignore uid:123 session and create a new session for uid:456 request(app.callback()) .get('/') .set('Cookie', cookies + ';uid=456') - .expect(200, function (err, res) { + .expect(200, (err, res) => { should.not.exist(err); res.body.uid.should.equal('456'); res.body.foo.should.not.equal(data.foo); @@ -524,62 +525,62 @@ describe('Koa Session', function(){ }); }); }); - }) - }) + }); + }); - describe('when options.encode and options.decode are functions', function () { - describe('they are used to encode/decode stored cookie values', function () { - it('should work', function (done) { - var encodeCallCount = 0 - var decodeCallCount = 0 + describe('when options.encode and options.decode are functions', () => { + describe('they are used to encode/decode stored cookie values', () => { + it('should work', done => { + let encodeCallCount = 0; + let decodeCallCount = 0; function encode(data) { - ++encodeCallCount - return JSON.stringify({enveloped: data}) + ++encodeCallCount; + return JSON.stringify({ enveloped: data }); } function decode(data) { - ++decodeCallCount - return JSON.parse(data).enveloped + ++decodeCallCount; + return JSON.parse(data).enveloped; } - var app = koa(); - app.keys = ['a', 'b']; + const app = koa(); + app.keys = [ 'a', 'b' ]; app.use(session({ - encode: encode, - decode: decode + encode, + decode, }, app)); - app.use(function * () { - this.session.counter = (this.session.counter || 0) + 1 - this.body = this.session - return - }) + app.use(function* () { + this.session.counter = (this.session.counter || 0) + 1; + this.body = this.session; + return; + }); request(app.callback()) .get('/') - .expect(function () { encodeCallCount.should.above(0, 'encode was not called'); }) - .expect(200, function (err, res) { - should.not.exist(err) - res.body.counter.should.equal(1, 'expected body to be equal to session.counter') - var cookies = res.headers['set-cookie'].join(';'); + .expect(() => { encodeCallCount.should.above(0, 'encode was not called'); }) + .expect(200, (err, res) => { + should.not.exist(err); + res.body.counter.should.equal(1, 'expected body to be equal to session.counter'); + const cookies = res.headers['set-cookie'].join(';'); request(app.callback()) .get('/') .set('Cookie', cookies) - .expect(function () { decodeCallCount.should.be.above(0, 'decode was not called'); }) - .expect(200, function (err, res) { + .expect(() => { decodeCallCount.should.be.above(0, 'decode was not called'); }) + .expect(200, (err, res) => { should.not.exist(err); res.body.counter.should.equal(2); done(); - }) - }) - }) - }) - }) -}) + }); + }); + }); + }); + }); +}); function App(options) { - var app = koa(); - app.keys = ['a', 'b']; + const app = koa(); + app.keys = [ 'a', 'b' ]; app.use(session(options, app)); return app; } diff --git a/test/external.js b/test/external.js new file mode 100644 index 0000000..c5cce12 --- /dev/null +++ b/test/external.js @@ -0,0 +1,17 @@ +'use strict'; + +const session = {}; + +module.exports = { + * get(key) { + return session[key]; + }, + + * set(key, value) { + session[key] = value; + }, + + * remove(key) { + session[key] = undefined; + }, +}; diff --git a/test/external.test.js b/test/external.test.js new file mode 100644 index 0000000..a4918a7 --- /dev/null +++ b/test/external.test.js @@ -0,0 +1,447 @@ +'use strict'; + +const koa = require('koa'); +const request = require('supertest'); +const should = require('should'); +const session = require('..'); +const external = require('./external'); + +describe('Koa Session External', () => { + let cookie; + + describe('when the session contains a ;', () => { + it('should still work', done => { + const app = App(); + + app.use(function* () { + if (this.method === 'POST') { + this.session.string = ';'; + this.status = 204; + } else { + this.body = this.session.string; + } + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect(204, (err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie']; + request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';', done); + }); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.body = 'greetings'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.session; + this.body = 'greetings'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.session.message = 'hello'; + this.body = ''; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, (err, res) => { + if (err) return done(err); + cookie = res.header['set-cookie'].join(';'); + done(); + }); + }); + + it('should not Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.body = this.session; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', done => { + const app = App(); + + app.use(function* () { + this.session.message.should.equal('hello'); + this.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, done); + }); + + it('should not Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.session.message.should.equal('hello'); + this.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.session.money = '$$$'; + this.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', done => { + const app = App(); + + app.use(function* () { + this.session = null; + this.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(function* () { + this.session = {}; + this.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('an object', () => { + it('should create a session', done => { + const app = App(); + + app.use(function* () { + this.session = { message: 'hello' }; + this.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + + describe('anything else', () => { + it('should throw', done => { + const app = App(); + + app.use(function* () { + this.session = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect(500, done); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', done => { + const app = koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(function* (next) { + try { + yield next; + } catch (err) { + this.status = err.status; + this.body = err.message; + } + }); + + app.use(session({ external }, app)); + + app.use(function* (next) { + this.session.name = 'funny'; + yield next; + }); + + app.use(function* () { + this.throw(401); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(401, done); + }); + }); + + describe('when maxAge present', () => { + describe('and not expire', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 100 }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.body = 200; + return; + } + this.body = this.session.message; + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + }); + + describe('and expired', () => { + it('should expire the sess', done => { + const app = App({ maxAge: 100 }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.status = 200; + return; + } + + this.body = this.session.message || ''; + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + + setTimeout(() => { + request(server) + .get('/') + .set('cookie', cookie) + .expect('', done); + }, 200); + }); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', done => { + const app = App({ maxAge: 100 }); + + app.use(function* () { + this.body = this.session.maxAge; + }); + + request(app.listen()) + .get('/') + .expect('100', done); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', done => { + const app = App(); + + app.use(function* () { + this.session.foo = 'bar'; + this.session.maxAge = 100; + this.body = this.session.foo; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', done => { + const app = koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + external, + }, app)); + app.use(function* () { + if (!this.session.foo) { + this.session.foo = Date.now() + '|uid:' + this.cookies.get('uid'); + } + + this.body = { + foo: this.session.foo, + uid: this.cookies.get('uid'), + }; + }); + + request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200, (err, res) => { + should.not.exist(err); + const data = res.body; + const cookies = res.headers['set-cookie'].join(';'); + cookies.should.containEql('koa:sess='); + + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data, err => { + should.not.exist(err); + + // should ignore uid:123 session and create a new session for uid:456 + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200, (err, res) => { + should.not.exist(err); + res.body.uid.should.equal('456'); + res.body.foo.should.not.equal(data.foo); + done(); + }); + }); + }); + }); + }); +}); + +function App(options) { + const app = koa(); + app.keys = [ 'a', 'b' ]; + options = options || {}; + options.external = external; + app.use(session(options, app)); + return app; +} From b748403d20529927364ad78982e046efec695672 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 18:14:50 +0800 Subject: [PATCH 045/154] chore: eslint --- index.js | 30 +++++++++++++++++++----------- package.json | 4 ++-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 36e2d94..28d3b42 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,9 @@ -/** - * Module dependencies. - */ +'use strict'; const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); +const assert = require('assert'); /** * Initialize session middleware with `opts`: @@ -17,10 +16,10 @@ const util = require('./lib/util'); * @api public */ -module.exports = function(opts, app){ +module.exports = function(opts, app) { // session(app[, opts]) if (opts && typeof opts.use === 'function') { - var tmp = app; + const tmp = app; app = opts; opts = tmp; } @@ -53,9 +52,9 @@ function formatOpts(opts) { if (!('maxAge' in opts)) opts.maxAge = opts.maxage; // defaults - if (null == opts.overwrite) opts.overwrite = true; - if (null == opts.httpOnly) opts.httpOnly = true; - if (null == opts.signed) opts.signed = true; + if (opts.overwrite == null) opts.overwrite = true; + if (opts.httpOnly == null) opts.httpOnly = true; + if (opts.signed == null) opts.signed = true; debug('session options %j', opts); @@ -67,6 +66,12 @@ function formatOpts(opts) { opts.decode = util.decode; } + if (opts.external) { + assert(typeof opts.external.get === 'function', 'external.get must be function'); + assert(typeof opts.external.set === 'function', 'external.set must be function'); + assert(typeof opts.external.remove === 'function', 'external.remove must be function'); + } + return opts; } @@ -82,7 +87,7 @@ function extendContext(context, opts) { // already retrieved if (session) return session; // unset - if (false === session) return null; + if (session === false) return null; // cookie session store this.sess.initFromCookie(this, opts); @@ -90,8 +95,11 @@ function extendContext(context, opts) { }); context.__defineSetter__('session', function(val) { - if (null == val) return this.sess.session = false; - if ('object' == typeof val) return this.sess.createSession(val); + if (val === null) { + this.sess.session = false; + return; + } + if (typeof val === 'object') return this.sess.createSession(val); throw new Error('this.session can only be set as null or an object.'); }); diff --git a/package.json b/package.json index ae154fe..9017ced 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ "uid-safe": "^2.1.3" }, "scripts": { - "test": "npm run lint && NODE_ENV=test1 mocha --harmony --require should --reporter spec test/*.test.js", + "test": "npm run lint && NODE_ENV=test mocha --harmony --require should --reporter spec test/*.test.js", "test-cov": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", "test-travis": "npm run lint && NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", - "lint": "eslint lib test" + "lint": "eslint lib test index.js" } } From 54576d046bf3f82d9b067ff3ea0103278aa72a9a Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 19:35:32 +0800 Subject: [PATCH 046/154] refactor: use store instead of external --- Readme.md | 21 +++++++++++++------ index.js | 10 ++++----- lib/context.js | 12 ++++++----- package.json | 2 +- test/external.js | 17 ---------------- test/store.js | 17 ++++++++++++++++ test/{external.test.js => store.test.js} | 26 +++++++++++++++++++----- 7 files changed, 66 insertions(+), 39 deletions(-) delete mode 100644 test/external.js create mode 100644 test/store.js rename test/{external.test.js => store.test.js} (95%) diff --git a/Readme.md b/Readme.md index 0101283..e8338bc 100644 --- a/Readme.md +++ b/Readme.md @@ -80,12 +80,6 @@ app.use(convert(session(app))); // codes ``` -## Semantics - - This module provides "guest" sessions, meaning any visitor will have a session, - authenticated or not. If a session is _new_ a Set-Cookie will be produced regardless - of populating the session. - ## API ### Options @@ -104,6 +98,21 @@ app.use(convert(session(app))); - `valid()`: valid session value before use it - `beforeSave()`: hook before save session +### External Store + + Session will store in cookie by default, but it has some disadvantages: + + - Session stored in client side unencrypted. + - [Browser cookie always have length limit](http://browsercookielimits.squawky.net/). + + You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be generator function or async function): + + - `get(key)`: get session object by key + - `set(key, sess, maxAge)`: set session object for key, with a `maxAge` (in ms) + - `destroy(key)`: destroy session for key + + Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. + ### Session#isNew Returns __true__ if the session is new. diff --git a/index.js b/index.js index 28d3b42..c95fbe6 100644 --- a/index.js +++ b/index.js @@ -32,7 +32,7 @@ module.exports = function(opts, app) { extendContext(app.context, opts); return function* session(next) { - if (this.sess.external) yield this.sess.initFromExternal(); + if (this.sess.store) yield this.sess.initFromExternal(); try { yield next; } catch (err) { @@ -66,10 +66,10 @@ function formatOpts(opts) { opts.decode = util.decode; } - if (opts.external) { - assert(typeof opts.external.get === 'function', 'external.get must be function'); - assert(typeof opts.external.set === 'function', 'external.set must be function'); - assert(typeof opts.external.remove === 'function', 'external.remove must be function'); + if (opts.store) { + assert(typeof opts.store.get === 'function', 'store.get must be function'); + assert(typeof opts.store.set === 'function', 'store.set must be function'); + assert(typeof opts.store.destroy === 'function', 'store.remove must be function'); } return opts; diff --git a/lib/context.js b/lib/context.js index 016b8d3..b955aa7 100644 --- a/lib/context.js +++ b/lib/context.js @@ -16,7 +16,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; this.opts = Object.assign({}, opts); - this.external = this.opts.external; + this.store = this.opts.store; } /** @@ -37,7 +37,7 @@ class ContextSession { return; } - const json = yield this.external.get(externalKey, ctx); + const json = yield this.store.get(externalKey, ctx); if (!this.valid(json)) { this.createSession(); return; @@ -102,6 +102,8 @@ class ContextSession { * @api private */ valid(json) { + if (!json) return false; + if (!json._expire || json._expire < Date.now()) { debug('expired session'); return false; @@ -125,7 +127,7 @@ class ContextSession { */ createSession(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.opts.external) this.externalKey = externalKey || uid.sync(24); + if (this.opts.store) this.externalKey = externalKey || uid.sync(24); this.session = new Session(this.ctx, val); } @@ -172,7 +174,7 @@ class ContextSession { const key = opts.key; const externalKey = this.externalKey; - if (externalKey) yield this.external.remove(externalKey); + if (externalKey) yield this.store.destroy(externalKey); ctx.cookies.set(key, '', opts); } @@ -195,7 +197,7 @@ class ContextSession { // save to external store if (externalKey) { debug('save %j to external key %s', json, externalKey); - yield this.external.set(externalKey, json, maxAge); + yield this.store.set(externalKey, json, maxAge); this.ctx.cookies.set(key, externalKey, opts); return; } diff --git a/package.json b/package.json index 9017ced..7889774 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "uid-safe": "^2.1.3" }, "scripts": { - "test": "npm run lint && NODE_ENV=test mocha --harmony --require should --reporter spec test/*.test.js", + "test": "npm run lint && NODE_ENV=test1 mocha --harmony --require should --reporter spec test/*.test.js", "test-cov": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", "test-travis": "npm run lint && NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" diff --git a/test/external.js b/test/external.js deleted file mode 100644 index c5cce12..0000000 --- a/test/external.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -const session = {}; - -module.exports = { - * get(key) { - return session[key]; - }, - - * set(key, value) { - session[key] = value; - }, - - * remove(key) { - session[key] = undefined; - }, -}; diff --git a/test/store.js b/test/store.js new file mode 100644 index 0000000..9371ad3 --- /dev/null +++ b/test/store.js @@ -0,0 +1,17 @@ +'use strict'; + +const sessions = {}; + +module.exports = { + * get(key) { + return sessions[key]; + }, + + * set(key, value) { + sessions[key] = value; + }, + + * destroy(key) { + sessions[key] = undefined; + }, +}; diff --git a/test/external.test.js b/test/store.test.js similarity index 95% rename from test/external.test.js rename to test/store.test.js index a4918a7..405efc0 100644 --- a/test/external.test.js +++ b/test/store.test.js @@ -4,9 +4,9 @@ const koa = require('koa'); const request = require('supertest'); const should = require('should'); const session = require('..'); -const external = require('./external'); +const store = require('./store'); -describe('Koa Session External', () => { +describe('Koa Session External Store', () => { let cookie; describe('when the session contains a ;', () => { @@ -266,7 +266,7 @@ describe('Koa Session External', () => { } }); - app.use(session({ external }, app)); + app.use(session({ store }, app)); app.use(function* (next) { this.session.name = 'funny'; @@ -380,6 +380,22 @@ describe('Koa Session External', () => { }); }); + describe('when store return empty', () => { + it('should create new Session', done => { + const app = App({ signed: false }); + + app.use(function* () { + this.body = String(this.session.isNew); + }); + + request(app.listen()) + .get('/') + .set('cookie', 'koa:sess=invalid-key') + .expect('true') + .expect(200, done); + }); + }); + describe('when valid and beforeSave set', () => { it('should ignore session when uid changed', done => { const app = koa(); @@ -392,7 +408,7 @@ describe('Koa Session External', () => { beforeSave(ctx, sess) { sess.uid = ctx.cookies.get('uid'); }, - external, + store, }, app)); app.use(function* () { if (!this.session.foo) { @@ -441,7 +457,7 @@ function App(options) { const app = koa(); app.keys = [ 'a', 'b' ]; options = options || {}; - options.external = external; + options.store = store; app.use(session(options, app)); return app; } From bc9dc974753e8a27ccea64cb72b95cd92cd3ba42 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 20:01:18 +0800 Subject: [PATCH 047/154] refactor: rename methods --- index.js | 19 ++++++++++++++++++- lib/context.js | 31 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index c95fbe6..6216a36 100644 --- a/index.js +++ b/index.js @@ -43,6 +43,14 @@ module.exports = function(opts, app) { }; }; +/** + * format and check session options + * @param {Object} opts session options + * @return {Object} new session options + * + * @api private + */ + function formatOpts(opts) { opts = opts || {}; // key @@ -75,6 +83,15 @@ function formatOpts(opts) { return opts; } +/** + * extend context prototype, add session properties + * + * @param {Object} context koa's context prototype + * @param {Object} opts session options + * + * @api private + */ + function extendContext(context, opts) { context.__defineGetter__('sess', function() { if (this._sess) return this._sess; @@ -99,7 +116,7 @@ function extendContext(context, opts) { this.sess.session = false; return; } - if (typeof val === 'object') return this.sess.createSession(val); + if (typeof val === 'object') return this.sess.create(val); throw new Error('this.session can only be set as null or an object.'); }); diff --git a/lib/context.js b/lib/context.js index b955aa7..9d98c3b 100644 --- a/lib/context.js +++ b/lib/context.js @@ -33,22 +33,22 @@ class ContextSession { debug('get external key from cookie %s', externalKey); if (!externalKey) { - this.createSession(); + this.create(); return; } const json = yield this.store.get(externalKey, ctx); if (!this.valid(json)) { - this.createSession(); + this.create(); return; } if (!json) { - this.createSession(); + this.create(); return; } - this.createSession(json, externalKey); + this.create(json, externalKey); this.prevHash = util.hash(this.session.toJSON()); } @@ -56,6 +56,7 @@ class ContextSession { * init session from cookie * @api public */ + initFromCookie() { debug('init from cookie'); const ctx = this.ctx; @@ -63,7 +64,7 @@ class ContextSession { const cookie = ctx.cookies.get(opts.key, opts); if (!cookie) { - this.createSession(); + this.create(); return; } @@ -79,19 +80,19 @@ class ContextSession { // but `JSON.parse(string)` will crash. debug('decode %j error: %s', cookie, err); if (!(err instanceof SyntaxError)) throw err; - this.createSession(); + this.create(); return; } debug('parsed %j', json); if (!this.valid(json)) { - this.createSession(); + this.create(); return; } // support access `ctx.session` before session middleware - this.createSession(json); + this.create(json); this.prevHash = util.hash(this.session.toJSON()); } @@ -101,6 +102,7 @@ class ContextSession { * @return {Boolean} valid * @api private */ + valid(json) { if (!json) return false; @@ -125,7 +127,8 @@ class ContextSession { * @param {String} [externalKey] session external key * @api private */ - createSession(val, externalKey) { + + create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); if (this.opts.store) this.externalKey = externalKey || uid.sync(24); this.session = new Session(this.ctx, val); @@ -148,7 +151,7 @@ class ContextSession { // removed if (session === false) { - yield this.removeSession(); + yield this.remove(); return; } const json = session.toJSON(); @@ -161,14 +164,15 @@ class ContextSession { debug('before save'); opts.beforeSave(ctx, session); } - yield this.saveSession(); + yield this.save(); } /** * remove session * @api private */ - * removeSession() { + + * remove() { const opts = this.opts; const ctx = this.ctx; const key = opts.key; @@ -182,7 +186,8 @@ class ContextSession { * save session * @api private */ - * saveSession() { + + * save() { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; From 369616e204c40c7899a782c0f93f319b5d8b5cad Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 20:13:26 +0800 Subject: [PATCH 048/154] chore: remove unused code --- lib/context.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/context.js b/lib/context.js index 9d98c3b..7c8b058 100644 --- a/lib/context.js +++ b/lib/context.js @@ -43,11 +43,6 @@ class ContextSession { return; } - if (!json) { - this.create(); - return; - } - this.create(json, externalKey); this.prevHash = util.hash(this.session.toJSON()); } From 490f31a9cc4f78f153a59742e1debbcc9d1eb169 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 23:02:56 +0800 Subject: [PATCH 049/154] feat: throw error when encode error, add more test cases --- index.js | 17 +----- lib/context.js | 56 ++++++++++++++++--- lib/util.js | 2 - test/cookie.test.js | 133 ++++++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 53 ++++++++++++++++++ 5 files changed, 236 insertions(+), 25 deletions(-) diff --git a/index.js b/index.js index 6216a36..0de6b7b 100644 --- a/index.js +++ b/index.js @@ -100,24 +100,11 @@ function extendContext(context, opts) { }); context.__defineGetter__('session', function() { - const session = this.sess.session; - // already retrieved - if (session) return session; - // unset - if (session === false) return null; - - // cookie session store - this.sess.initFromCookie(this, opts); - return this.sess.session; + return this.sess.get(); }); context.__defineSetter__('session', function(val) { - if (val === null) { - this.sess.session = false; - return; - } - if (typeof val === 'object') return this.sess.create(val); - throw new Error('this.session can only be set as null or an object.'); + this.sess.set(val); }); context.__defineGetter__('sessionOptions', function() { diff --git a/lib/context.js b/lib/context.js index 7c8b058..1598399 100644 --- a/lib/context.js +++ b/lib/context.js @@ -15,12 +15,54 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; + this.app = ctx.app; this.opts = Object.assign({}, opts); this.store = this.opts.store; } + /** + * internal logic of `ctx.session` + * @return {Session} session object + * + * @api public + */ + + get() { + const session = this.session; + // already retrieved + if (session) return session; + // unset + if (session === false) return null; + + // cookie session store + if (!this.store) this.initFromCookie(); + return this.session; + } + + /** + * internal logic of `ctx.session=` + * @param {Object} val session object + * + * @api public + */ + + set(val) { + if (val === null) { + this.session = false; + return; + } + if (typeof val === 'object') { + // use the original `externalKey` if exists to avoid waste storage + this.create(val, this.externalKey); + return; + } + throw new Error('this.session can only be set as null or an object.'); + } + /** * init session from external store + * will be called in the front of session middleware + * * @api public */ @@ -33,23 +75,26 @@ class ContextSession { debug('get external key from cookie %s', externalKey); if (!externalKey) { + // create a new `externalKey` this.create(); return; } const json = yield this.store.get(externalKey, ctx); if (!this.valid(json)) { + // create a new `externalKey` this.create(); return; } + // create with original `externalKey` this.create(json, externalKey); this.prevHash = util.hash(this.session.toJSON()); } /** * init session from cookie - * @api public + * @api private */ initFromCookie() { @@ -204,13 +249,8 @@ class ContextSession { // save to cookie debug('save %j to cookie', json); - try { - json = opts.encode(json); - debug('save %s', json); - } catch (e) { - debug('encode %j error: %s', json, e); - json = ''; - } + json = opts.encode(json); + debug('save %s', json); this.ctx.cookies.set(key, json, opts); } diff --git a/lib/util.js b/lib/util.js index 31642e9..2bdd7b8 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,7 +1,6 @@ 'use strict'; const crc = require('crc').crc32; -const Session = require('./session'); module.exports = { @@ -33,7 +32,6 @@ module.exports = { }, hash(sess) { - if (sess instanceof Session) sess = sess.toJSON(); return crc(JSON.stringify(sess)); }, }; diff --git a/test/cookie.test.js b/test/cookie.test.js index d0adbbf..458c80b 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -248,6 +248,139 @@ describe('Koa Session Cookie', () => { }); }); + describe('when get session after set to null', () => { + it('should return null', done => { + const app = App(); + + app.use(function* () { + this.session.hello = {}; + this.session = null; + this.body = String(this.session === null); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=;/) + .expect('true') + .expect(200, done); + }); + }); + + describe('when decode session', () => { + describe('SyntaxError', () => { + it('should create new session', done => { + const app = App({ signed: false }); + + app.use(function* () { + this.body = String(this.session.isNew); + }); + + request(app.listen()) + .get('/') + .set('cookie', 'koa:sess=invalid-session;') + .expect('true') + .expect(200, done); + }); + }); + + describe('Other Error', () => { + it('should throw', done => { + const app = App({ + signed: false, + decode() { + throw new Error('decode error'); + }, + }); + + app.use(function* () { + this.body = String(this.session.isNew); + }); + + request(app.listen()) + .get('/') + .set('cookie', 'koa:sess=invalid-session;') + .expect(500, done); + }); + }); + }); + + describe('when encode session error', () => { + it('should throw', done => { + const app = App({ + encode() { + throw new Error('encode error'); + }, + }); + + app.use(function* () { + this.session.foo = 'bar'; + this.body = 'hello'; + }); + + app.once('error', (err, ctx) => { + err.message.should.equal('encode error'); + should.exists(ctx); + }); + + request(app.listen()) + .get('/') + .expect(500, done); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', done => { + const app = App(); + + app.use(function* () { + this.session.foo = 'bar'; + this.body = this.session.inspect(); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200, done); + }); + }); + + describe('.length', () => { + it('should return session length', done => { + const app = App(); + + app.use(function* () { + this.session.foo = 'bar'; + this.body = String(this.session.length); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('1') + .expect(200, done); + }); + }); + + describe('.populated', () => { + it('should return session populated', done => { + const app = App(); + + app.use(function* () { + this.session.foo = 'bar'; + this.body = String(this.session.populated); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('true') + .expect(200, done); + }); + }); + }); + describe('when session is', () => { describe('null', () => { it('should expire the session', done => { diff --git a/test/store.test.js b/test/store.test.js index 405efc0..e3fbf50 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -251,6 +251,59 @@ describe('Koa Session External Store', () => { }); }); + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', done => { + const app = App(); + + app.use(function* () { + this.session.foo = 'bar'; + this.body = this.session.inspect(); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200, done); + }); + }); + + describe('.length', () => { + it('should return session length', done => { + const app = App(); + + app.use(function* () { + this.session.foo = 'bar'; + this.body = String(this.session.length); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('1') + .expect(200, done); + }); + }); + + describe('.populated', () => { + it('should return session populated', done => { + const app = App(); + + app.use(function* () { + this.session.foo = 'bar'; + this.body = String(this.session.populated); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('true') + .expect(200, done); + }); + }); + }); + describe('when an error is thrown downstream and caught upstream', () => { it('should still save the session', done => { const app = koa(); From 4687a38e129f078c721e9eb0b2b20001007515ef Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 23:09:15 +0800 Subject: [PATCH 050/154] chore: remove unused code --- lib/context.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index 1598399..956ad2f 100644 --- a/lib/context.js +++ b/lib/context.js @@ -15,7 +15,6 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; - this.app = ctx.app; this.opts = Object.assign({}, opts); this.store = this.opts.store; } From 66ca6e3a4aa3ec1a082c8e4b89fdcf5f4b43f3f3 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 23:56:19 +0800 Subject: [PATCH 051/154] feat: clean cookie when decode error --- lib/context.js | 10 +++++++++- test/cookie.test.js | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/context.js b/lib/context.js index 956ad2f..7f6da82 100644 --- a/lib/context.js +++ b/lib/context.js @@ -118,7 +118,15 @@ class ContextSession { // when `string` is not base64-encoded. // but `JSON.parse(string)` will crash. debug('decode %j error: %s', cookie, err); - if (!(err instanceof SyntaxError)) throw err; + if (!(err instanceof SyntaxError)) { + // clean this cookie to ensure next request won't throw again + ctx.cookies.set(opts.key, '', opts); + // ctx.onerror will unset all headers, and set those specified in err + err.headers = { + 'set-cookie': ctx.response.get('set-cookie'), + }; + throw err; + } this.create(); return; } diff --git a/test/cookie.test.js b/test/cookie.test.js index 458c80b..c8786db 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -28,7 +28,7 @@ describe('Koa Session Cookie', () => { }); describe('when app.keys are not set', () => { - it('should throw', done => { + it('should throw and clean this cookie', done => { const app = koa(); app.use(session(app)); @@ -299,6 +299,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') .set('cookie', 'koa:sess=invalid-session;') + .expect('Set-Cookie', /koa:sess=;/) .expect(500, done); }); }); From ef64dcc7e9db7799f579093fb5e8321fdd0e1848 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Feb 2017 23:56:49 +0800 Subject: [PATCH 052/154] test: remove harmony --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7889774..591389f 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ "uid-safe": "^2.1.3" }, "scripts": { - "test": "npm run lint && NODE_ENV=test1 mocha --harmony --require should --reporter spec test/*.test.js", - "test-cov": "NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", - "test-travis": "npm run lint && NODE_ENV=test node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", + "test": "npm run lint && NODE_ENV=test mocha --require should --reporter spec test/*.test.js", + "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", + "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" } } From 46ee6a55b0148d996e4fb3779d1daff4c6de2bbd Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 23 Feb 2017 10:36:36 +0800 Subject: [PATCH 053/154] feat: keep session.save() --- lib/context.js | 14 +++++++++----- lib/session.js | 10 ++++++++++ test/cookie.test.js | 17 +++++++++++++++++ test/store.test.js | 17 +++++++++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/lib/context.js b/lib/context.js index 7f6da82..b91da0d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -201,11 +201,15 @@ class ContextSession { yield this.remove(); return; } - const json = session.toJSON(); - // do nothing if new and not populated - if (!prevHash && !Object.keys(json).length) return; - // do nothing if not changed - if (prevHash === util.hash(json)) return; + + // force save session when `session._save` set + if (!session._save) { + const json = session.toJSON(); + // do nothing if new and not populated + if (!prevHash && !Object.keys(json).length) return; + // do nothing if not changed + if (prevHash === util.hash(json)) return; + } if (typeof opts.beforeSave === 'function') { debug('before save'); diff --git a/lib/session.js b/lib/session.js index 0c140e8..e9b982c 100644 --- a/lib/session.js +++ b/lib/session.js @@ -98,6 +98,16 @@ class Session { set maxAge(val) { this._ctx.sessionOptions.maxAge = val; } + + /** + * save this session no matter whether it is populated + * + * @api public + */ + + save() { + this._save = true; + } } module.exports = Session; diff --git a/test/cookie.test.js b/test/cookie.test.js index c8786db..2cf3cec 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -380,6 +380,23 @@ describe('Koa Session Cookie', () => { .expect(200, done); }); }); + + describe('.save()', () => { + it('should save session', done => { + const app = App(); + + app.use(function* () { + this.session.save(); + this.body = 'hello'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('hello') + .expect(200, done); + }); + }); }); describe('when session is', () => { diff --git a/test/store.test.js b/test/store.test.js index e3fbf50..62c1492 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -302,6 +302,23 @@ describe('Koa Session External Store', () => { .expect(200, done); }); }); + + describe('.save()', () => { + it('should save session', done => { + const app = App(); + + app.use(function* () { + this.session.save(); + this.body = 'hello'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('hello') + .expect(200, done); + }); + }); }); describe('when an error is thrown downstream and caught upstream', () => { From ffb15e541580999b7848054c1c9ea4c350dafe00 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 23 Feb 2017 14:06:08 +0800 Subject: [PATCH 054/154] refactor: use defineProperties and symbol --- index.js | 45 +++++++++++++++++++++++++++------------------ lib/context.js | 2 +- package.json | 5 ++++- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 0de6b7b..6f41d84 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,9 @@ const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); +const CONTEXT_SESSION = Symbol('context#contextSession'); +const _CONTEXT_SESSION = Symbol('context#_contextSession'); + /** * Initialize session middleware with `opts`: * @@ -32,13 +35,14 @@ module.exports = function(opts, app) { extendContext(app.context, opts); return function* session(next) { - if (this.sess.store) yield this.sess.initFromExternal(); + const sess = this[CONTEXT_SESSION]; + if (sess.store) yield sess.initFromExternal(); try { yield next; } catch (err) { throw err; } finally { - yield this.sess.commit(); + yield sess.commit(); } }; }; @@ -93,21 +97,26 @@ function formatOpts(opts) { */ function extendContext(context, opts) { - context.__defineGetter__('sess', function() { - if (this._sess) return this._sess; - this._sess = new ContextSession(this, opts); - return this._sess; - }); - - context.__defineGetter__('session', function() { - return this.sess.get(); - }); - - context.__defineSetter__('session', function(val) { - this.sess.set(val); - }); - - context.__defineGetter__('sessionOptions', function() { - return this.sess.opts; + Object.defineProperties(context, { + [CONTEXT_SESSION]: { + get() { + if (this[_CONTEXT_SESSION]) return this[_CONTEXT_SESSION]; + this[_CONTEXT_SESSION] = new ContextSession(this, opts); + return this[_CONTEXT_SESSION]; + }, + }, + session: { + get() { + return this[CONTEXT_SESSION].get(); + }, + set(val) { + this[CONTEXT_SESSION].set(val); + }, + }, + sessionOptions: { + get() { + return this[CONTEXT_SESSION].opts; + }, + }, }); } diff --git a/lib/context.js b/lib/context.js index b91da0d..ae54a72 100644 --- a/lib/context.js +++ b/lib/context.js @@ -79,7 +79,7 @@ class ContextSession { return; } - const json = yield this.store.get(externalKey, ctx); + const json = yield this.store.get(externalKey); if (!this.valid(json)) { // create a new `externalKey` this.create(); diff --git a/package.json b/package.json index 591389f..180ba3a 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,11 @@ "deep-equal": "^1.0.0", "uid-safe": "^2.1.3" }, + "engines": { + "node": ">=4" + }, "scripts": { - "test": "npm run lint && NODE_ENV=test mocha --require should --reporter spec test/*.test.js", + "test": "npm run lint && NODE_ENV=test1 mocha --require should --reporter spec test/*.test.js", "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" From d1b21124bc211609f790b8f540e1aed632c21ec8 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 23 Feb 2017 14:16:31 +0800 Subject: [PATCH 055/154] docs: declare only use external session stores if necessary --- Readme.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index e8338bc..1129ab7 100644 --- a/Readme.md +++ b/Readme.md @@ -98,7 +98,7 @@ app.use(convert(session(app))); - `valid()`: valid session value before use it - `beforeSave()`: hook before save session -### External Store +### External Session Stores Session will store in cookie by default, but it has some disadvantages: @@ -111,7 +111,7 @@ app.use(convert(session(app))); - `set(key, sess, maxAge)`: set session object for key, with a `maxAge` (in ms) - `destroy(key)`: destroy session for key - Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. + Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** ### Session#isNew @@ -141,12 +141,6 @@ if (this.session.isNew) { this.session = null; ``` -## Session Stores - - This module only supports cookie sessions. There are many other modules listed in [koa's wiki](https://github.com/koajs/koa/wiki#wiki-sessions) for sessions that use database storage. Unlike Connect 2.x's session middleware, there is no main "session" middleware that you plugin different stores - each store is a completely different module. - - If you're interested in creating your own koa session store, feel free to fork/extend this repository and add additional tests. At a minimum, it __should__ pass this repositories' tests that apply. Ideally, there would be a central repository with specifications and tests for all koa sessions, which would allow interoperability and consistency between session modules. If you're interested in working on such a project, let us know! - ## License MIT From 012283e1957da0e9ed86a7bf79550af31f694fb8 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 24 Feb 2017 11:10:20 +0800 Subject: [PATCH 056/154] fix: typo --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 6f41d84..cd4a449 100644 --- a/index.js +++ b/index.js @@ -81,7 +81,7 @@ function formatOpts(opts) { if (opts.store) { assert(typeof opts.store.get === 'function', 'store.get must be function'); assert(typeof opts.store.set === 'function', 'store.set must be function'); - assert(typeof opts.store.destroy === 'function', 'store.remove must be function'); + assert(typeof opts.store.destroy === 'function', 'store.destroy must be function'); } return opts; From e33923b0db2c4384f8a7f22be3c344560c70f2b9 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 24 Feb 2017 11:12:20 +0800 Subject: [PATCH 057/154] deps: remove deep-equal --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 180ba3a..b26a777 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "dependencies": { "crc": "^3.4.4", "debug": "^2.2.0", - "deep-equal": "^1.0.0", "uid-safe": "^2.1.3" }, "engines": { From f916f209e3c3b5dde33ebec97911113c42c8a932 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 27 Feb 2017 20:58:03 +0800 Subject: [PATCH 058/154] feat: save session when maxAge changed --- lib/context.js | 4 ++-- lib/session.js | 6 ++++-- test/cookie.test.js | 28 ++++++++++++++++++++++++++++ test/store.test.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/lib/context.js b/lib/context.js index ae54a72..61650b0 100644 --- a/lib/context.js +++ b/lib/context.js @@ -202,8 +202,8 @@ class ContextSession { return; } - // force save session when `session._save` set - if (!session._save) { + // force save session when `session._requireSave` set + if (!session._requireSave) { const json = session.toJSON(); // do nothing if new and not populated if (!prevHash && !Object.keys(json).length) return; diff --git a/lib/session.js b/lib/session.js index e9b982c..b585f2a 100644 --- a/lib/session.js +++ b/lib/session.js @@ -18,7 +18,7 @@ class Session { this.isNew = true; } else { for (const k in obj) { - // change session options + // restore maxAge from store if (k === '_maxAge') this._ctx.sessionOptions.maxAge = obj._maxAge; else this[k] = obj[k]; } @@ -97,6 +97,8 @@ class Session { set maxAge(val) { this._ctx.sessionOptions.maxAge = val; + // maxAge changed, must save to cookie and store + this._requireSave = true; } /** @@ -106,7 +108,7 @@ class Session { */ save() { - this._save = true; + this._requireSave = true; } } diff --git a/test/cookie.test.js b/test/cookie.test.js index 2cf3cec..56cfcf2 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -593,6 +593,34 @@ describe('Koa Session Cookie', () => { .expect('Set-Cookie', /expires=/) .expect(200, done); }); + + it('should save even session not change', done => { + const app = App(); + + app.use(function* () { + this.session.maxAge = 100; + this.body = this.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + + it('should save when create session only with maxAge', done => { + const app = App(); + + app.use(function* () { + this.session = { maxAge: 100 }; + this.body = this.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); }); describe('when get session before enter session middleware', () => { diff --git a/test/store.test.js b/test/store.test.js index 62c1492..282e107 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -448,6 +448,34 @@ describe('Koa Session External Store', () => { .expect('Set-Cookie', /expires=/) .expect(200, done); }); + + it('should save even session not change', done => { + const app = App(); + + app.use(function* () { + this.session.maxAge = 100; + this.body = this.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + + it('should save when create session only with maxAge', done => { + const app = App(); + + app.use(function* () { + this.session = { maxAge: 100 }; + this.body = this.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); }); describe('when store return empty', () => { From 025636c208061cf014d3de680cf1c3b8e93e5c4c Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 27 Feb 2017 21:11:56 +0800 Subject: [PATCH 059/154] test: set env to test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b26a777..5fb6c2e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "node": ">=4" }, "scripts": { - "test": "npm run lint && NODE_ENV=test1 mocha --require should --reporter spec test/*.test.js", + "test": "npm run lint && NODE_ENV=test mocha --require should --reporter spec test/*.test.js", "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" From b53103d4e29240cd8aaeaf0565b6c08d1e05f338 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 27 Feb 2017 21:16:04 +0800 Subject: [PATCH 060/154] Release 4.0.0 --- History.md | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 99918d0..ee8a9b8 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,19 @@ +4.0.0 / 2017-02-27 +================== + + * [BREAKING CHANGE]: Drop support for node < 4. + * [BREAKING CHANGE]: Internal implementations are changed, so some private API is changed. + * Change private api `session.save()`, won't set cookie immediately now. + * Remove private api `session.changed()`. + * Remove undocumented property context.sessionKey, can use opts.key instead. + * Change undocumented property context.sessionOptions to getter. + * feat: Support external store by pass options.store. + * feat: Throw when encode session error, consider a breaking change. + * feat: Clean cookie when decode session throw error, ensure next request won't throw again. + * fix: Customize options.decode will check expired now + * docs: Remove Semantics in README because it's not "guest" sessions any more + 3.4.0 / 2016-10-15 ================== diff --git a/package.json b/package.json index 5fb6c2e..878b441 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "3.4.0", + "version": "4.0.0", "keywords": [ "koa", "middleware", From c1fef1f2176f4155b93894d0906a674d55ac78a9 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 27 Feb 2017 21:52:13 +0800 Subject: [PATCH 061/154] docs: format --- Readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 1129ab7..da7e8ae 100644 --- a/Readme.md +++ b/Readme.md @@ -102,8 +102,9 @@ app.use(convert(session(app))); Session will store in cookie by default, but it has some disadvantages: - - Session stored in client side unencrypted. - - [Browser cookie always have length limit](http://browsercookielimits.squawky.net/). + - Session stored in client side unencrypted. + - [Browser cookie always have length limit](http://browsercookielimits.squawky.net/). + You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be generator function or async function): @@ -111,6 +112,7 @@ app.use(convert(session(app))); - `set(key, sess, maxAge)`: set session object for key, with a `maxAge` (in ms) - `destroy(key)`: destroy session for key + Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** ### Session#isNew From 177c1497e8c893a01220cc2235f46b025a51529e Mon Sep 17 00:00:00 2001 From: Haoliang Gao Date: Wed, 1 Mar 2017 22:54:33 +0800 Subject: [PATCH 062/154] fix: ctx.session should be configurable (#67) --- index.js | 1 + package.json | 1 + test/store.test.js | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/index.js b/index.js index cd4a449..53ca820 100644 --- a/index.js +++ b/index.js @@ -112,6 +112,7 @@ function extendContext(context, opts) { set(val) { this[CONTEXT_SESSION].set(val); }, + configurable: true, }, sessionOptions: { get() { diff --git a/package.json b/package.json index 878b441..b23e965 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "eslint-config-egg": "3", "istanbul": "0", "koa": "1", + "mm": "^2.1.0", "mocha": "3 ", "should": "8", "supertest": "2" diff --git a/test/store.test.js b/test/store.test.js index 282e107..1e54227 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -3,6 +3,7 @@ const koa = require('koa'); const request = require('supertest'); const should = require('should'); +const mm = require('mm'); const session = require('..'); const store = require('./store'); @@ -549,6 +550,30 @@ describe('Koa Session External Store', () => { }); }); }); + + describe('ctx.session', () => { + after(mm.restore); + + it('should be mocked', done => { + const app = App(); + + app.use(function* () { + this.body = this.session; + }); + + mm(app.context, 'session', { + foo: 'bar', + }); + + request(app.listen()) + .get('/') + .expect({ + foo: 'bar', + }) + .expect(200, done); + }); + }); + }); function App(options) { From 6655744a91f75e26dde479d15cf054b2a3fca2c4 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 1 Mar 2017 22:55:43 +0800 Subject: [PATCH 063/154] Release 4.0.1 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index ee8a9b8..b33ced1 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +4.0.1 / 2017-03-01 +================== + + * fix: ctx.session should be configurable (#67) + 4.0.0 / 2017-02-27 ================== diff --git a/package.json b/package.json index b23e965..9691b3c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "4.0.0", + "version": "4.0.1", "keywords": [ "koa", "middleware", From e52d789ab73ea7716c671a7ebf56cb7c5972b726 Mon Sep 17 00:00:00 2001 From: easthing Date: Sun, 12 Mar 2017 13:27:15 +0800 Subject: [PATCH 064/154] feat: async/await support (#70) --- .eslintrc | 3 + .travis.yml | 2 - index.js | 10 +- lib/context.js | 18 ++-- package.json | 6 +- test/cookie.test.js | 248 ++++++++++++++++++++++---------------------- test/store.js | 6 +- test/store.test.js | 189 +++++++++++++++++---------------- 8 files changed, 241 insertions(+), 241 deletions(-) diff --git a/.eslintrc b/.eslintrc index 79794d7..7364c13 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,6 @@ { "extends": "eslint-config-egg", + "parserOptions": { + "ecmaVersion": 2017 + } } diff --git a/.travis.yml b/.travis.yml index 0c63626..4eb70eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ sudo: false language: node_js node_js: - - '4' - - '6' - '7' script: 'npm run test-travis' after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' diff --git a/index.js b/index.js index 53ca820..47992fc 100644 --- a/index.js +++ b/index.js @@ -34,15 +34,15 @@ module.exports = function(opts, app) { opts = formatOpts(opts); extendContext(app.context, opts); - return function* session(next) { - const sess = this[CONTEXT_SESSION]; - if (sess.store) yield sess.initFromExternal(); + return async function session(ctx, next) { + const sess = ctx[CONTEXT_SESSION]; + if (sess.store) await sess.initFromExternal(); try { - yield next; + await next(); } catch (err) { throw err; } finally { - yield sess.commit(); + await sess.commit(); } }; }; diff --git a/lib/context.js b/lib/context.js index 61650b0..88cea73 100644 --- a/lib/context.js +++ b/lib/context.js @@ -65,7 +65,7 @@ class ContextSession { * @api public */ - * initFromExternal() { + async initFromExternal() { debug('init from external'); const ctx = this.ctx; const opts = this.opts; @@ -79,7 +79,7 @@ class ContextSession { return; } - const json = yield this.store.get(externalKey); + const json = await this.store.get(externalKey); if (!this.valid(json)) { // create a new `externalKey` this.create(); @@ -187,7 +187,7 @@ class ContextSession { * @api public */ - * commit() { + async commit() { const session = this.session; const prevHash = this.prevHash; const opts = this.opts; @@ -198,7 +198,7 @@ class ContextSession { // removed if (session === false) { - yield this.remove(); + await this.remove(); return; } @@ -215,7 +215,7 @@ class ContextSession { debug('before save'); opts.beforeSave(ctx, session); } - yield this.save(); + await this.save(); } /** @@ -223,13 +223,13 @@ class ContextSession { * @api private */ - * remove() { + async remove() { const opts = this.opts; const ctx = this.ctx; const key = opts.key; const externalKey = this.externalKey; - if (externalKey) yield this.store.destroy(externalKey); + if (externalKey) await this.store.destroy(externalKey); ctx.cookies.set(key, '', opts); } @@ -238,7 +238,7 @@ class ContextSession { * @api private */ - * save() { + async save() { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; @@ -253,7 +253,7 @@ class ContextSession { // save to external store if (externalKey) { debug('save %j to external key %s', json, externalKey); - yield this.store.set(externalKey, json, maxAge); + await this.store.set(externalKey, json, maxAge); this.ctx.cookies.set(key, externalKey, opts); return; } diff --git a/package.json b/package.json index 9691b3c..bb24a87 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,9 @@ "eslint": "3", "eslint-config-egg": "3", "istanbul": "0", - "koa": "1", + "koa": "2", "mm": "^2.1.0", - "mocha": "3 ", + "mocha": "3", "should": "8", "supertest": "2" }, @@ -30,7 +30,7 @@ "uid-safe": "^2.1.3" }, "engines": { - "node": ">=4" + "node": ">=7.6" }, "scripts": { "test": "npm run lint && NODE_ENV=test mocha --require should --reporter spec test/*.test.js", diff --git a/test/cookie.test.js b/test/cookie.test.js index 56cfcf2..b1fa1e5 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -1,6 +1,6 @@ 'use strict'; -const koa = require('koa'); +const Koa = require('koa'); const request = require('supertest'); const should = require('should'); const session = require('..'); @@ -11,14 +11,14 @@ describe('Koa Session Cookie', () => { describe('when options.signed = true', () => { describe('when app.keys are set', () => { it('should work', done => { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; app.use(session({}, app)); - app.use(function* () { - this.session.message = 'hi'; - this.body = this.session; + app.use(async function(ctx) { + ctx.session.message = 'hi'; + ctx.body = ctx.session; }); request(app.listen()) @@ -29,13 +29,13 @@ describe('Koa Session Cookie', () => { describe('when app.keys are not set', () => { it('should throw and clean this cookie', done => { - const app = koa(); + const app = new Koa(); app.use(session(app)); - app.use(function* () { - this.session.message = 'hi'; - this.body = this.session; + app.use(async function(ctx) { + ctx.session.message = 'hi'; + ctx.body = ctx.session; }); request(app.listen()) @@ -46,7 +46,7 @@ describe('Koa Session Cookie', () => { describe('when app not set', () => { it('should throw', () => { - const app = koa(); + const app = new Koa(); (function() { app.use(session()); }).should.throw('app instance required: `session(opts, app)`'); @@ -57,13 +57,13 @@ describe('Koa Session Cookie', () => { describe('when options.signed = false', () => { describe('when app.keys are not set', () => { it('should work', done => { - const app = koa(); + const app = new Koa(); app.use(session({ signed: false }, app)); - app.use(function* () { - this.session.message = 'hi'; - this.body = this.session; + app.use(async function(ctx) { + ctx.session.message = 'hi'; + ctx.body = ctx.session; }); request(app.listen()) @@ -77,12 +77,12 @@ describe('Koa Session Cookie', () => { it('should still work', done => { const app = App(); - app.use(function* () { - if (this.method === 'POST') { - this.session.string = ';'; - this.status = 204; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; } else { - this.body = this.session.string; + ctx.body = ctx.session.string; } }); @@ -106,8 +106,8 @@ describe('Koa Session Cookie', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.body = 'greetings'; + app.use(async function(ctx) { + ctx.body = 'greetings'; }); request(app.listen()) @@ -124,9 +124,9 @@ describe('Koa Session Cookie', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session; - this.body = 'greetings'; + app.use(async function(ctx) { + ctx.session; + ctx.body = 'greetings'; }); request(app.listen()) @@ -143,9 +143,9 @@ describe('Koa Session Cookie', () => { it('should Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session.message = 'hello'; - this.body = ''; + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.body = ''; }); request(app.listen()) @@ -161,8 +161,8 @@ describe('Koa Session Cookie', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.body = this.session; + app.use(async function(ctx) { + ctx.body = ctx.session; }); request(app.listen()) @@ -181,8 +181,8 @@ describe('Koa Session Cookie', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -200,9 +200,9 @@ describe('Koa Session Cookie', () => { it('should be the same session', done => { const app = App(); - app.use(function* () { - this.session.message.should.equal('hello'); - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -214,9 +214,9 @@ describe('Koa Session Cookie', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session.message.should.equal('hello'); - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -234,9 +234,9 @@ describe('Koa Session Cookie', () => { it('should Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session.money = '$$$'; - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.session.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -252,10 +252,10 @@ describe('Koa Session Cookie', () => { it('should return null', done => { const app = App(); - app.use(function* () { - this.session.hello = {}; - this.session = null; - this.body = String(this.session === null); + app.use(async function(ctx) { + ctx.session.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); }); request(app.listen()) @@ -271,8 +271,8 @@ describe('Koa Session Cookie', () => { it('should create new session', done => { const app = App({ signed: false }); - app.use(function* () { - this.body = String(this.session.isNew); + app.use(async function(ctx) { + ctx.body = String(ctx.session.isNew); }); request(app.listen()) @@ -292,8 +292,8 @@ describe('Koa Session Cookie', () => { }, }); - app.use(function* () { - this.body = String(this.session.isNew); + app.use(async function(ctx) { + ctx.body = String(ctx.session.isNew); }); request(app.listen()) @@ -313,9 +313,9 @@ describe('Koa Session Cookie', () => { }, }); - app.use(function* () { - this.session.foo = 'bar'; - this.body = 'hello'; + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = 'hello'; }); app.once('error', (err, ctx) => { @@ -334,9 +334,9 @@ describe('Koa Session Cookie', () => { it('should return session content', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.body = this.session.inspect(); + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = ctx.session.inspect(); }); request(app.listen()) @@ -351,9 +351,9 @@ describe('Koa Session Cookie', () => { it('should return session length', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.body = String(this.session.length); + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.length); }); request(app.listen()) @@ -368,9 +368,9 @@ describe('Koa Session Cookie', () => { it('should return session populated', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.body = String(this.session.populated); + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.populated); }); request(app.listen()) @@ -385,9 +385,9 @@ describe('Koa Session Cookie', () => { it('should save session', done => { const app = App(); - app.use(function* () { - this.session.save(); - this.body = 'hello'; + app.use(async function(ctx) { + ctx.session.save(); + ctx.body = 'hello'; }); request(app.listen()) @@ -404,9 +404,9 @@ describe('Koa Session Cookie', () => { it('should expire the session', done => { const app = App(); - app.use(function* () { - this.session = null; - this.body = 'asdf'; + app.use(async function(ctx) { + ctx.session = null; + ctx.body = 'asdf'; }); request(app.listen()) @@ -420,9 +420,9 @@ describe('Koa Session Cookie', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session = {}; - this.body = 'asdf'; + app.use(async function(ctx) { + ctx.session = {}; + ctx.body = 'asdf'; }); request(app.listen()) @@ -439,9 +439,9 @@ describe('Koa Session Cookie', () => { it('should create a session', done => { const app = App(); - app.use(function* () { - this.session = { message: 'hello' }; - this.body = 'asdf'; + app.use(async function(ctx) { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; }); request(app.listen()) @@ -455,8 +455,8 @@ describe('Koa Session Cookie', () => { it('should throw', done => { const app = App(); - app.use(function* () { - this.session = 'asdf'; + app.use(async function(ctx) { + ctx.session = 'asdf'; }); request(app.listen()) @@ -468,28 +468,28 @@ describe('Koa Session Cookie', () => { describe('when an error is thrown downstream and caught upstream', () => { it('should still save the session', done => { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; - app.use(function* (next) { + app.use(async function(ctx, next) { try { - yield* next; + await next(); } catch (err) { - this.status = err.status; - this.body = err.message; + ctx.status = err.status; + ctx.body = err.message; } }); app.use(session(app)); - app.use(function* (next) { - this.session.name = 'funny'; - yield* next; + app.use(async function(ctx, next) { + ctx.session.name = 'funny'; + await next(); }); - app.use(function* () { - this.throw(401); + app.use(async function(ctx) { + ctx.throw(401); }); request(app.listen()) @@ -504,13 +504,13 @@ describe('Koa Session Cookie', () => { it('should not expire the session', done => { const app = App({ maxAge: 100 }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.body = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; return; } - this.body = this.session.message; + ctx.body = ctx.session.message; }); const server = app.listen(); @@ -534,14 +534,14 @@ describe('Koa Session Cookie', () => { it('should expire the sess', done => { const app = App({ maxAge: 100 }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.status = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.status = 200; return; } - this.body = this.session.message || ''; + ctx.body = ctx.session.message || ''; }); const server = app.listen(); @@ -568,8 +568,8 @@ describe('Koa Session Cookie', () => { it('should return opt.maxAge', done => { const app = App({ maxAge: 100 }); - app.use(function* () { - this.body = this.session.maxAge; + app.use(async function(ctx) { + ctx.body = ctx.session.maxAge; }); request(app.listen()) @@ -582,10 +582,10 @@ describe('Koa Session Cookie', () => { it('should set sessionOptions.maxAge', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.session.maxAge = 100; - this.body = this.session.foo; + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.session.maxAge = 100; + ctx.body = ctx.session.foo; }); request(app.listen()) @@ -597,9 +597,9 @@ describe('Koa Session Cookie', () => { it('should save even session not change', done => { const app = App(); - app.use(function* () { - this.session.maxAge = 100; - this.body = this.session; + app.use(async function(ctx) { + ctx.session.maxAge = 100; + ctx.body = ctx.session; }); request(app.listen()) @@ -611,9 +611,9 @@ describe('Koa Session Cookie', () => { it('should save when create session only with maxAge', done => { const app = App(); - app.use(function* () { - this.session = { maxAge: 100 }; - this.body = this.session; + app.use(async function(ctx) { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; }); request(app.listen()) @@ -625,16 +625,16 @@ describe('Koa Session Cookie', () => { describe('when get session before enter session middleware', () => { it('should work', done => { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; - app.use(function* (next) { - this.session.foo = 'hi'; - yield next; + app.use(async function(ctx, next) { + ctx.session.foo = 'hi'; + await next(); }); app.use(session({}, app)); - app.use(function* () { - this.body = this.session; + app.use(async function(ctx) { + ctx.body = ctx.session; }); request(app.callback()) @@ -654,7 +654,7 @@ describe('Koa Session Cookie', () => { describe('when valid and beforeSave set', () => { it('should ignore session when uid changed', done => { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; app.use(session({ @@ -665,14 +665,14 @@ describe('Koa Session Cookie', () => { sess.uid = ctx.cookies.get('uid'); }, }, app)); - app.use(function* () { - if (!this.session.foo) { - this.session.foo = Date.now() + '|uid:' + this.cookies.get('uid'); + app.use(async function(ctx) { + if (!ctx.session.foo) { + ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); } - this.body = { - foo: this.session.foo, - uid: this.cookies.get('uid'), + ctx.body = { + foo: ctx.session.foo, + uid: ctx.cookies.get('uid'), }; }); @@ -722,16 +722,16 @@ describe('Koa Session Cookie', () => { return JSON.parse(data).enveloped; } - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; app.use(session({ encode, decode, }, app)); - app.use(function* () { - this.session.counter = (this.session.counter || 0) + 1; - this.body = this.session; + app.use(async function(ctx) { + ctx.session.counter = (ctx.session.counter || 0) + 1; + ctx.body = ctx.session; return; }); @@ -758,7 +758,7 @@ describe('Koa Session Cookie', () => { }); function App(options) { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; app.use(session(options, app)); return app; diff --git a/test/store.js b/test/store.js index 9371ad3..e344c72 100644 --- a/test/store.js +++ b/test/store.js @@ -3,15 +3,15 @@ const sessions = {}; module.exports = { - * get(key) { + async get(key) { return sessions[key]; }, - * set(key, value) { + async set(key, value) { sessions[key] = value; }, - * destroy(key) { + async destroy(key) { sessions[key] = undefined; }, }; diff --git a/test/store.test.js b/test/store.test.js index 1e54227..008a35f 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -1,6 +1,6 @@ 'use strict'; -const koa = require('koa'); +const Koa = require('koa'); const request = require('supertest'); const should = require('should'); const mm = require('mm'); @@ -14,12 +14,12 @@ describe('Koa Session External Store', () => { it('should still work', done => { const app = App(); - app.use(function* () { - if (this.method === 'POST') { - this.session.string = ';'; - this.status = 204; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; } else { - this.body = this.session.string; + ctx.body = ctx.session.string; } }); @@ -43,8 +43,8 @@ describe('Koa Session External Store', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.body = 'greetings'; + app.use(async function(ctx) { + ctx.body = 'greetings'; }); request(app.listen()) @@ -61,9 +61,9 @@ describe('Koa Session External Store', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session; - this.body = 'greetings'; + app.use(async function(ctx) { + ctx.session; + ctx.body = 'greetings'; }); request(app.listen()) @@ -80,9 +80,9 @@ describe('Koa Session External Store', () => { it('should Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session.message = 'hello'; - this.body = ''; + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.body = ''; }); request(app.listen()) @@ -98,8 +98,8 @@ describe('Koa Session External Store', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.body = this.session; + app.use(async function(ctx) { + ctx.body = ctx.session; }); request(app.listen()) @@ -118,8 +118,8 @@ describe('Koa Session External Store', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -137,9 +137,9 @@ describe('Koa Session External Store', () => { it('should be the same session', done => { const app = App(); - app.use(function* () { - this.session.message.should.equal('hello'); - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -151,9 +151,9 @@ describe('Koa Session External Store', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session.message.should.equal('hello'); - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -171,9 +171,9 @@ describe('Koa Session External Store', () => { it('should Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session.money = '$$$'; - this.body = 'aklsdjflasdjf'; + app.use(async function(ctx) { + ctx.session.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; }); request(app.listen()) @@ -190,9 +190,9 @@ describe('Koa Session External Store', () => { it('should expire the session', done => { const app = App(); - app.use(function* () { - this.session = null; - this.body = 'asdf'; + app.use(async function(ctx) { + ctx.session = null; + ctx.body = 'asdf'; }); request(app.listen()) @@ -206,9 +206,9 @@ describe('Koa Session External Store', () => { it('should not Set-Cookie', done => { const app = App(); - app.use(function* () { - this.session = {}; - this.body = 'asdf'; + app.use(async function(ctx) { + ctx.session = {}; + ctx.body = 'asdf'; }); request(app.listen()) @@ -225,9 +225,9 @@ describe('Koa Session External Store', () => { it('should create a session', done => { const app = App(); - app.use(function* () { - this.session = { message: 'hello' }; - this.body = 'asdf'; + app.use(async function(ctx) { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; }); request(app.listen()) @@ -241,8 +241,8 @@ describe('Koa Session External Store', () => { it('should throw', done => { const app = App(); - app.use(function* () { - this.session = 'asdf'; + app.use(async function(ctx) { + ctx.session = 'asdf'; }); request(app.listen()) @@ -257,9 +257,9 @@ describe('Koa Session External Store', () => { it('should return session content', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.body = this.session.inspect(); + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = ctx.session.inspect(); }); request(app.listen()) @@ -274,9 +274,9 @@ describe('Koa Session External Store', () => { it('should return session length', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.body = String(this.session.length); + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.length); }); request(app.listen()) @@ -291,9 +291,9 @@ describe('Koa Session External Store', () => { it('should return session populated', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.body = String(this.session.populated); + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.populated); }); request(app.listen()) @@ -308,9 +308,9 @@ describe('Koa Session External Store', () => { it('should save session', done => { const app = App(); - app.use(function* () { - this.session.save(); - this.body = 'hello'; + app.use(async function(ctx) { + ctx.session.save(); + ctx.body = 'hello'; }); request(app.listen()) @@ -324,28 +324,28 @@ describe('Koa Session External Store', () => { describe('when an error is thrown downstream and caught upstream', () => { it('should still save the session', done => { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; - app.use(function* (next) { + app.use(async function(ctx, next) { try { - yield next; + await next(); } catch (err) { - this.status = err.status; - this.body = err.message; + ctx.status = err.status; + ctx.body = err.message; } }); app.use(session({ store }, app)); - app.use(function* (next) { - this.session.name = 'funny'; - yield next; + app.use(async function(ctx, next) { + ctx.session.name = 'funny'; + await next(); }); - app.use(function* () { - this.throw(401); + app.use(async function(ctx) { + ctx.throw(401); }); request(app.listen()) @@ -360,13 +360,13 @@ describe('Koa Session External Store', () => { it('should not expire the session', done => { const app = App({ maxAge: 100 }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.body = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; return; } - this.body = this.session.message; + ctx.body = ctx.session.message; }); const server = app.listen(); @@ -390,14 +390,14 @@ describe('Koa Session External Store', () => { it('should expire the sess', done => { const app = App({ maxAge: 100 }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.status = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.status = 200; return; } - this.body = this.session.message || ''; + ctx.body = ctx.session.message || ''; }); const server = app.listen(); @@ -424,8 +424,8 @@ describe('Koa Session External Store', () => { it('should return opt.maxAge', done => { const app = App({ maxAge: 100 }); - app.use(function* () { - this.body = this.session.maxAge; + app.use(async function(ctx) { + ctx.body = ctx.session.maxAge; }); request(app.listen()) @@ -438,10 +438,10 @@ describe('Koa Session External Store', () => { it('should set sessionOptions.maxAge', done => { const app = App(); - app.use(function* () { - this.session.foo = 'bar'; - this.session.maxAge = 100; - this.body = this.session.foo; + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.session.maxAge = 100; + ctx.body = ctx.session.foo; }); request(app.listen()) @@ -453,9 +453,9 @@ describe('Koa Session External Store', () => { it('should save even session not change', done => { const app = App(); - app.use(function* () { - this.session.maxAge = 100; - this.body = this.session; + app.use(async function(ctx) { + ctx.session.maxAge = 100; + ctx.body = ctx.session; }); request(app.listen()) @@ -467,9 +467,9 @@ describe('Koa Session External Store', () => { it('should save when create session only with maxAge', done => { const app = App(); - app.use(function* () { - this.session = { maxAge: 100 }; - this.body = this.session; + app.use(async function(ctx) { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; }); request(app.listen()) @@ -483,8 +483,8 @@ describe('Koa Session External Store', () => { it('should create new Session', done => { const app = App({ signed: false }); - app.use(function* () { - this.body = String(this.session.isNew); + app.use(async function(ctx) { + ctx.body = String(ctx.session.isNew); }); request(app.listen()) @@ -497,7 +497,7 @@ describe('Koa Session External Store', () => { describe('when valid and beforeSave set', () => { it('should ignore session when uid changed', done => { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; app.use(session({ @@ -509,14 +509,14 @@ describe('Koa Session External Store', () => { }, store, }, app)); - app.use(function* () { - if (!this.session.foo) { - this.session.foo = Date.now() + '|uid:' + this.cookies.get('uid'); + app.use(async function(ctx) { + if (!ctx.session.foo) { + ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); } - this.body = { - foo: this.session.foo, - uid: this.cookies.get('uid'), + ctx.body = { + foo: ctx.session.foo, + uid: ctx.cookies.get('uid'), }; }); @@ -557,8 +557,8 @@ describe('Koa Session External Store', () => { it('should be mocked', done => { const app = App(); - app.use(function* () { - this.body = this.session; + app.use(async function(ctx) { + ctx.body = ctx.session; }); mm(app.context, 'session', { @@ -573,11 +573,10 @@ describe('Koa Session External Store', () => { .expect(200, done); }); }); - }); function App(options) { - const app = koa(); + const app = new Koa(); app.keys = [ 'a', 'b' ]; options = options || {}; options.store = store; From 331e5a6d627af1f2082a9d8bde43f0ebb228e042 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sun, 12 Mar 2017 13:29:40 +0800 Subject: [PATCH 065/154] docs: only support async function --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index da7e8ae..e4144be 100644 --- a/Readme.md +++ b/Readme.md @@ -106,7 +106,7 @@ app.use(convert(session(app))); - [Browser cookie always have length limit](http://browsercookielimits.squawky.net/). - You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be generator function or async function): + You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be async function): - `get(key)`: get session object by key - `set(key, sess, maxAge)`: set session object for key, with a `maxAge` (in ms) From 93c4ad29c275cbb879247c2e578c1697f7315678 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sun, 12 Mar 2017 13:30:18 +0800 Subject: [PATCH 066/154] docs: node>=7.6 --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index e4144be..07872a4 100644 --- a/Readme.md +++ b/Readme.md @@ -21,7 +21,7 @@ [david-url]: https://david-dm.org/koajs/session [iojs-image]: https://img.shields.io/badge/io.js-%3E=_1.0-yellow.svg?style=flat-square [iojs-url]: http://iojs.org/ -[node-image]: https://img.shields.io/badge/node.js-%3E=_0.12-green.svg?style=flat-square +[node-image]: https://img.shields.io/badge/node.js-%3E=_7.6-green.svg?style=flat-square [node-url]: http://nodejs.org/download/ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session From 11bbb66c73f6fb68f887183a4e860947c6a9b96f Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sun, 12 Mar 2017 13:31:05 +0800 Subject: [PATCH 067/154] Release 5.0.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index b33ced1..5e9a798 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.0.0 / 2017-03-12 +================== + + * feat: async/await support (#70) + 4.0.1 / 2017-03-01 ================== diff --git a/package.json b/package.json index bb24a87..ca161a4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "4.0.1", + "version": "5.0.0", "keywords": [ "koa", "middleware", From 325ef3480419ad228ab488c35478d325a43404fc Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 14 Mar 2017 10:44:20 +0800 Subject: [PATCH 068/154] docs: fix readme example --- Readme.md | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/Readme.md b/Readme.md index 07872a4..0367dab 100644 --- a/Readme.md +++ b/Readme.md @@ -39,13 +39,13 @@ $ npm install koa-session View counter example: ```js -var session = require('koa-session'); -var koa = require('koa'); -var app = koa(); +const session = require('koa-session'); +const Koa = require('koa'); +const app = new koa(); app.keys = ['some secret hurr']; -var CONFIG = { +const CONFIG = { key: 'koa:sess', /** (string) cookie key (default is koa:sess) */ maxAge: 86400000, /** (number) maxAge in ms (default is 1 days) */ overwrite: true, /** (boolean) can overwrite or not (default true) */ @@ -55,30 +55,18 @@ var CONFIG = { app.use(session(CONFIG, app)); // or if you prefer all default config, just use => app.use(session(app)); -app.use(function *(){ +app.use(ctx => { // ignore favicon - if (this.path === '/favicon.ico') return; + if (ctx.path === '/favicon.ico') return; - var n = this.session.views || 0; - this.session.views = ++n; - this.body = n + ' views'; -}) + let n = ctx.session.views || 0; + ctx.session.views = ++n; + ctx.body = n + ' views'; +}); app.listen(3000); console.log('listening on port 3000'); ``` -For Koa 2, use [koa-convert](https://github.com/gyson/koa-convert) to convert the session middleware : - -```js -const koa = require('koa'); -const session = require('koa-session') -const convert = require('koa-convert'); - -const app = new koa(); -app.use(convert(session(app))); - -// codes -``` ## API From 3adc5b6a930d19a0152a7dec832d9ef89a600342 Mon Sep 17 00:00:00 2001 From: haoxin Date: Wed, 15 Mar 2017 11:27:36 +0800 Subject: [PATCH 069/154] fix docs --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 0367dab..c214d91 100644 --- a/Readme.md +++ b/Readme.md @@ -41,7 +41,7 @@ $ npm install koa-session ```js const session = require('koa-session'); const Koa = require('koa'); -const app = new koa(); +const app = new Koa(); app.keys = ['some secret hurr']; From 777c8e836d74ea12f810c104eb4bbc61d2586365 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 3 May 2017 11:06:02 +0800 Subject: [PATCH 070/154] docs: fix readme --- .gitignore | 1 + Readme.md | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ba2a97b..659fedb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules coverage +*.log diff --git a/Readme.md b/Readme.md index c214d91..490cb0b 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple cookie-based session middleware for Koa. + Simple session middleware for Koa. default is cookie-based session and support external store. ## Installation @@ -123,6 +123,10 @@ if (this.session.isNew) { Set cookie's maxAge. +### Session#save() + + Save this session no matter whether it is populated. + ### Destroying a session To destroy a session simply set it to `null`: From abda418a1063ba157974eedc180f2621a5529db7 Mon Sep 17 00:00:00 2001 From: Joe Grayauskie Date: Thu, 1 Jun 2017 04:22:56 -0400 Subject: [PATCH 071/154] =?UTF-8?q?Create=20capability=20to=20create=20coo?= =?UTF-8?q?kies=20that=20expire=20when=20browser=20is=20close=E2=80=A6=20(?= =?UTF-8?q?#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Readme.md | 5 ++++- lib/context.js | 17 +++++++++------ test/store.test.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index 490cb0b..ebe3589 100644 --- a/Readme.md +++ b/Readme.md @@ -47,7 +47,10 @@ app.keys = ['some secret hurr']; const CONFIG = { key: 'koa:sess', /** (string) cookie key (default is koa:sess) */ - maxAge: 86400000, /** (number) maxAge in ms (default is 1 days) */ + /** (number || 'session') maxAge in ms (default is 1 days) */ + /** 'session' will result in a cookie that expires when session/browser is closed */ + /** Warning: If a session cookie is stolen, this cookie will never expire */ + maxAge: 86400000, overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ diff --git a/lib/context.js b/lib/context.js index 88cea73..28ca1c8 100644 --- a/lib/context.js +++ b/lib/context.js @@ -153,7 +153,7 @@ class ContextSession { valid(json) { if (!json) return false; - if (!json._expire || json._expire < Date.now()) { + if (json._expire && json._expire < Date.now()) { debug('expired session'); return false; } @@ -242,13 +242,18 @@ class ContextSession { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; - - const maxAge = opts.maxAge || ONE_DAY; - let json = this.session.toJSON(); // set expire for check - json._expire = maxAge + Date.now(); - json._maxAge = maxAge; + const maxAge = opts.maxAge ? opts.maxAge : ONE_DAY; + if (maxAge === 'session') { + // do not set _expire in json if maxAge is set to 'session' + // also delete maxAge from options + opts.maxAge = undefined; + } else { + // set expire for check + json._expire = maxAge + Date.now(); + json._maxAge = maxAge; + } // save to external store if (externalKey) { diff --git a/test/store.test.js b/test/store.test.js index 008a35f..3e96e84 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -356,6 +356,60 @@ describe('Koa Session External Store', () => { }); describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 'session' }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.body = 200; + return; + } + this.body = this.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + it('should use the default maxAge when improper string given', done => { + const app = App({ maxAge: 'not the right string' }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.body = 200; + return; + } + this.body = this.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + }); describe('and not expire', () => { it('should not expire the session', done => { const app = App({ maxAge: 100 }); From 89f9850fe07be03949df6864f9f958c9dd056754 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 1 Jun 2017 16:35:19 +0800 Subject: [PATCH 072/154] fix test case --- test/store.test.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/store.test.js b/test/store.test.js index 3e96e84..46712d7 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -360,13 +360,13 @@ describe('Koa Session External Store', () => { it('should not expire the session', done => { const app = App({ maxAge: 'session' }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.body = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; return; } - this.body = this.session.message; + ctx.body = ctx.session.message; }); const server = app.listen(); @@ -386,13 +386,13 @@ describe('Koa Session External Store', () => { it('should use the default maxAge when improper string given', done => { const app = App({ maxAge: 'not the right string' }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.body = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; return; } - this.body = this.session.message; + ctx.body = ctx.session.message; }); const server = app.listen(); From e763f6a42672510f71e6fe308b9c799990f173ce Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 1 Jun 2017 16:35:58 +0800 Subject: [PATCH 073/154] test: test on node 8 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4eb70eb..ec9e856 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,6 @@ sudo: false language: node_js node_js: - '7' + - '8' script: 'npm run test-travis' after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' From acda04c0ff35423df6b00f23821bfd64469737bf Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 1 Jun 2017 16:36:33 +0800 Subject: [PATCH 074/154] Release 5.1.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 5e9a798..4810926 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.1.0 / 2017-06-01 +================== + + * Create capability to create cookies that expire when browser is close… (#77) + 5.0.0 / 2017-03-12 ================== diff --git a/package.json b/package.json index ca161a4..7760292 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "5.0.0", + "version": "5.1.0", "keywords": [ "koa", "middleware", From bb1dbd66fec214277dcc48d2b5e659e5cae4e1e2 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 10 Jun 2017 10:04:30 -0400 Subject: [PATCH 075/154] Create Readme.md (#78) --- Readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index ebe3589..11aa5aa 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,9 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. default is cookie-based session and support external store. + Simple session middleware for Koa. default is cookie-based session and support external store. + + *Requires Node 7.6 or greater for async/await support* ## Installation From a5cefa8863ade64fb99420270a48e920ad1e408c Mon Sep 17 00:00:00 2001 From: Shawn Date: Thu, 15 Jun 2017 14:52:34 +0800 Subject: [PATCH 076/154] feat: support options.ContextStore (#81) --- Readme.md | 2 + index.js | 18 +- lib/context.js | 4 +- package.json | 1 + test/context_store.js | 25 ++ test/contextstore.test.js | 639 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 683 insertions(+), 6 deletions(-) create mode 100644 test/context_store.js create mode 100644 test/contextstore.test.js diff --git a/Readme.md b/Readme.md index 11aa5aa..931a782 100644 --- a/Readme.md +++ b/Readme.md @@ -108,6 +108,8 @@ console.log('listening on port 3000'); Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** + If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. + ### Session#isNew Returns __true__ if the session is new. diff --git a/index.js b/index.js index 47992fc..24a7d9d 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); +const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); const _CONTEXT_SESSION = Symbol('context#_contextSession'); @@ -78,10 +79,19 @@ function formatOpts(opts) { opts.decode = util.decode; } - if (opts.store) { - assert(typeof opts.store.get === 'function', 'store.get must be function'); - assert(typeof opts.store.set === 'function', 'store.set must be function'); - assert(typeof opts.store.destroy === 'function', 'store.destroy must be function'); + const store = opts.store; + if (store) { + assert(is.function(store.get), 'store.get must be function'); + assert(is.function(store.set), 'store.set must be function'); + assert(is.function(store.destroy), 'store.destroy must be function'); + } + + const ContextStore = opts.ContextStore; + if (ContextStore) { + assert(is.class(ContextStore), 'ContextStore must be a class'); + assert(is.function(ContextStore.prototype.get), 'ContextStore.prototype.get must be function'); + assert(is.function(ContextStore.prototype.set), 'ContextStore.prototype.set must be function'); + assert(is.function(ContextStore.prototype.destroy), 'ContextStore.prototype.destroy must be function'); } return opts; diff --git a/lib/context.js b/lib/context.js index 28ca1c8..fedf076 100644 --- a/lib/context.js +++ b/lib/context.js @@ -16,7 +16,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; this.opts = Object.assign({}, opts); - this.store = this.opts.store; + this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } /** @@ -177,7 +177,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.opts.store) this.externalKey = externalKey || uid.sync(24); + if (this.store) this.externalKey = externalKey || uid.sync(24); this.session = new Session(this.ctx, val); } diff --git a/package.json b/package.json index 7760292..023ab5d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "crc": "^3.4.4", "debug": "^2.2.0", + "is-type-of": "^1.0.0", "uid-safe": "^2.1.3" }, "engines": { diff --git a/test/context_store.js b/test/context_store.js new file mode 100644 index 0000000..8a053eb --- /dev/null +++ b/test/context_store.js @@ -0,0 +1,25 @@ +'use strict'; + +// this is a stupid nonsense example just to test + +const sessions = {}; + +class ContextStore { + constructor(ctx) { + this.ctx = ctx; + } + + async get(key) { + return sessions[key]; + } + + async set(key, value) { + sessions[key] = value; + } + + async destroy(key) { + sessions[key] = undefined; + } +} + +module.exports = ContextStore; diff --git a/test/contextstore.test.js b/test/contextstore.test.js new file mode 100644 index 0000000..acd98f8 --- /dev/null +++ b/test/contextstore.test.js @@ -0,0 +1,639 @@ +'use strict'; + +const Koa = require('koa'); +const request = require('supertest'); +const should = require('should'); +const mm = require('mm'); +const session = require('..'); +const ContextStore = require('./context_store'); + +describe('Koa Session External Context Store', () => { + let cookie; + + describe('when the session contains a ;', () => { + it('should still work', done => { + const app = App(); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect(204, (err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie']; + request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';', done); + }); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'greetings'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session; + ctx.body = 'greetings'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.body = ''; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, (err, res) => { + if (err) return done(err); + cookie = res.header['set-cookie'].join(';'); + done(); + }); + }); + + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, done); + }); + + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = null; + ctx.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('an object', () => { + it('should create a session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + + describe('anything else', () => { + it('should throw', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect(500, done); + }); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = ctx.session.inspect(); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200, done); + }); + }); + + describe('.length', () => { + it('should return session length', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.length); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('1') + .expect(200, done); + }); + }); + + describe('.populated', () => { + it('should return session populated', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.populated); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('true') + .expect(200, done); + }); + }); + + describe('.save()', () => { + it('should save session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.save(); + ctx.body = 'hello'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('hello') + .expect(200, done); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', done => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async function(ctx, next) { + try { + await next(); + } catch (err) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session({ ContextStore }, app)); + + app.use(async function(ctx, next) { + ctx.session.name = 'funny'; + await next(); + }); + + app.use(async function(ctx) { + ctx.throw(401); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(401, done); + }); + }); + + describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + it('should use the default maxAge when improper string given', done => { + const app = App({ maxAge: 'not the right string' }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + }); + describe('and not expire', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + }); + + describe('and expired', () => { + it('should expire the sess', done => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session.message || ''; + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + + setTimeout(() => { + request(server) + .get('/') + .set('cookie', cookie) + .expect('', done); + }, 200); + }); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', done => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + ctx.body = ctx.session.maxAge; + }); + + request(app.listen()) + .get('/') + .expect('100', done); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.session.maxAge = 100; + ctx.body = ctx.session.foo; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + + it('should save even session not change', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.maxAge = 100; + ctx.body = ctx.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + + it('should save when create session only with maxAge', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + }); + + describe('when store return empty', () => { + it('should create new Session', done => { + const app = App({ signed: false }); + + app.use(async function(ctx) { + ctx.body = String(ctx.session.isNew); + }); + + request(app.listen()) + .get('/') + .set('cookie', 'koa:sess=invalid-key') + .expect('true') + .expect(200, done); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', done => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + ContextStore, + }, app)); + app.use(async function(ctx) { + if (!ctx.session.foo) { + ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200, (err, res) => { + should.not.exist(err); + const data = res.body; + const cookies = res.headers['set-cookie'].join(';'); + cookies.should.containEql('koa:sess='); + + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data, err => { + should.not.exist(err); + + // should ignore uid:123 session and create a new session for uid:456 + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200, (err, res) => { + should.not.exist(err); + res.body.uid.should.equal('456'); + res.body.foo.should.not.equal(data.foo); + done(); + }); + }); + }); + }); + }); + + describe('ctx.session', () => { + after(mm.restore); + + it('should be mocked', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + mm(app.context, 'session', { + foo: 'bar', + }); + + request(app.listen()) + .get('/') + .expect({ + foo: 'bar', + }) + .expect(200, done); + }); + }); +}); + +function App(options) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options = options || {}; + options.ContextStore = ContextStore; + app.use(session(options, app)); + return app; +} From 50e1345572c9c87c6ea73211cf085a8ca046d874 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 15 Jun 2017 14:53:22 +0800 Subject: [PATCH 077/154] Release 5.2.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 4810926..3208994 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.2.0 / 2017-06-15 +================== + + * feat: support options.ContextStore (#81) + 5.1.0 / 2017-06-01 ================== diff --git a/package.json b/package.json index 023ab5d..587d340 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "5.1.0", + "version": "5.2.0", "keywords": [ "koa", "middleware", From e153c0be9c215c6ec0fab41590281f71a588401a Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Sat, 17 Jun 2017 00:22:47 +0800 Subject: [PATCH 078/154] feat: support rolling (#84) --- Readme.md | 6 ++++-- lib/context.js | 17 +++++++++++------ test/cookie.test.js | 40 ++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 931a782..2131312 100644 --- a/Readme.md +++ b/Readme.md @@ -56,7 +56,9 @@ const CONFIG = { overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ + rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. default is false **/ }; + app.use(session(CONFIG, app)); // or if you prefer all default config, just use => app.use(session(app)); @@ -101,8 +103,8 @@ console.log('listening on port 3000'); You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be async function): - - `get(key)`: get session object by key - - `set(key, sess, maxAge)`: set session object for key, with a `maxAge` (in ms) + - `get(key, maxAge, { rolling })`: get session object by key + - `set(key, sess, maxAge, { rolling, changed })`: set session object for key, with a `maxAge` (in ms) - `destroy(key)`: destroy session for key diff --git a/lib/context.js b/lib/context.js index fedf076..29a9dac 100644 --- a/lib/context.js +++ b/lib/context.js @@ -79,7 +79,7 @@ class ContextSession { return; } - const json = await this.store.get(externalKey); + const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling }); if (!this.valid(json)) { // create a new `externalKey` this.create(); @@ -203,19 +203,21 @@ class ContextSession { } // force save session when `session._requireSave` set + let changed = true; if (!session._requireSave) { const json = session.toJSON(); // do nothing if new and not populated if (!prevHash && !Object.keys(json).length) return; - // do nothing if not changed - if (prevHash === util.hash(json)) return; + changed = prevHash !== util.hash(json); + // do nothing if not changed and not in rolling mode + if (!this.opts.rolling && !changed) return; } if (typeof opts.beforeSave === 'function') { debug('before save'); opts.beforeSave(ctx, session); } - await this.save(); + await this.save(changed); } /** @@ -238,7 +240,7 @@ class ContextSession { * @api private */ - async save() { + async save(changed) { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; @@ -258,7 +260,10 @@ class ContextSession { // save to external store if (externalKey) { debug('save %j to external key %s', json, externalKey); - await this.store.set(externalKey, json, maxAge); + await this.store.set(externalKey, json, maxAge, { + changed, + rolling: opts.rolling, + }); this.ctx.cookies.set(key, externalKey, opts); return; } diff --git a/test/cookie.test.js b/test/cookie.test.js index b1fa1e5..dc15fb9 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -755,6 +755,46 @@ describe('Koa Session Cookie', () => { }); }); }); + + describe('when rolling set to true', () => { + let app; + before(() => { + app = App({ rolling: true }); + + app.use(function* () { + console.log(this.path); + if (this.path === '/set') this.session = { foo: 'bar' }; + this.body = this.session; + }); + }); + + it('should not send set-cookie when session not exists', () => { + return request(app.callback()) + .get('/') + .expect({}) + .expect(res => { + should.not.exist(res.headers['set-cookie']); + }); + }); + + it('should send set-cookie when session exists and not change', done => { + request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }) + .end((err, res) => { + should.not.exist(err); + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(res => { + res.headers['set-cookie'].should.have.length(2); + }) + .expect({ foo: 'bar' }, done); + }); + }); + }); }); function App(options) { diff --git a/test/store.test.js b/test/store.test.js index 46712d7..9e0bf77 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -627,6 +627,45 @@ describe('Koa Session External Store', () => { .expect(200, done); }); }); + + describe('when rolling set to true', () => { + let app; + before(() => { + app = App({ rolling: true }); + + app.use(async ctx => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', () => { + return request(app.callback()) + .get('/') + .expect({}) + .expect(res => { + should.not.exist(res.headers['set-cookie']); + }); + }); + + it('should send set-cookie when session exists and not change', done => { + request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }) + .end((err, res) => { + should.not.exist(err); + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(res => { + res.headers['set-cookie'].should.have.length(2); + }) + .expect({ foo: 'bar' }, done); + }); + }); + }); }); function App(options) { From d958bb542f856610c6e44c19f6ab3f248ec21a34 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sat, 17 Jun 2017 00:25:53 +0800 Subject: [PATCH 079/154] Release 5.3.0 --- History.md | 5 +++++ package.json | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 3208994..52710dd 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.3.0 / 2017-06-17 +================== + + * feat: support rolling (#84) + 5.2.0 / 2017-06-15 ================== diff --git a/package.json b/package.json index 587d340..b6a374e 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "koa-session", - "description": "Koa cookie session middleware", + "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.2.0", + "version": "5.3.0", "keywords": [ "koa", "middleware", From e82726d5b315c18df2a306a6cd77e6db3f017336 Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 3 Jul 2017 16:50:23 +0800 Subject: [PATCH 080/154] feat: opts.genid (#87) --- Readme.md | 2 ++ lib/context.js | 2 +- test/contextstore.test.js | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 2131312..6108fb9 100644 --- a/Readme.md +++ b/Readme.md @@ -110,6 +110,8 @@ console.log('listening on port 3000'); Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** + The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. + If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. ### Session#isNew diff --git a/lib/context.js b/lib/context.js index 29a9dac..f7664bf 100644 --- a/lib/context.js +++ b/lib/context.js @@ -177,7 +177,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || uid.sync(24); + if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx) || uid.sync(24); this.session = new Session(this.ctx, val); } diff --git a/test/contextstore.test.js b/test/contextstore.test.js index acd98f8..0905fae 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -91,6 +91,7 @@ describe('Koa Session External Context Store', () => { .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); + cookie.indexOf('_suffix').should.greaterThan(1); done(); }); }); @@ -634,6 +635,9 @@ function App(options) { app.keys = [ 'a', 'b' ]; options = options || {}; options.ContextStore = ContextStore; + options.genid = () => { + return Date.now() + '_suffix'; + }; app.use(session(options, app)); return app; } From fd8acf239c9db1d9ae6db8ccbe38831b06b6226a Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 3 Jul 2017 16:52:40 +0800 Subject: [PATCH 081/154] Release 5.4.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 52710dd..66ea28b 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.4.0 / 2017-07-03 +================== + + * feat: opts.genid (#87) + 5.3.0 / 2017-06-17 ================== diff --git a/package.json b/package.json index b6a374e..078bdb7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.3.0", + "version": "5.4.0", "keywords": [ "koa", "middleware", From ec88cfb095ddbfa9a0db465e3f9e459fb6f92bec Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Fri, 4 Aug 2017 10:53:30 +0800 Subject: [PATCH 082/154] feat: support options.prefix for external store (#93) --- Readme.md | 4 +++- index.js | 6 ++++++ lib/context.js | 3 +-- test/store.test.js | 29 +++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 6108fb9..f425b73 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. default is cookie-based session and support external store. + Simple session middleware for Koa. default is cookie-based session and support external store. *Requires Node 7.6 or greater for async/await support* @@ -112,6 +112,8 @@ console.log('listening on port 3000'); The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. + If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. + If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. ### Session#isNew diff --git a/index.js b/index.js index 24a7d9d..d7978c1 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); +const uid = require('uid-safe'); const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); @@ -94,6 +95,11 @@ function formatOpts(opts) { assert(is.function(ContextStore.prototype.destroy), 'ContextStore.prototype.destroy must be function'); } + if (!opts.genid) { + if (opts.prefix) opts.genid = () => opts.prefix + uid.sync(24); + else opts.genid = () => uid.sync(24); + } + return opts; } diff --git a/lib/context.js b/lib/context.js index f7664bf..f1dc702 100644 --- a/lib/context.js +++ b/lib/context.js @@ -2,7 +2,6 @@ const debug = require('debug')('koa-session:context'); const Session = require('./session'); -const uid = require('uid-safe'); const util = require('./util'); const ONE_DAY = 24 * 60 * 60 * 1000; @@ -177,7 +176,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx) || uid.sync(24); + if (this.store) this.externalKey = externalKey || this.opts.genid(); this.session = new Session(this.ctx, val); } diff --git a/test/store.test.js b/test/store.test.js index 9e0bf77..82996ac 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -666,6 +666,35 @@ describe('Koa Session External Store', () => { }); }); }); + + describe('when prefix present', () => { + it('should still work', done => { + const app = App({ prefix: 'sess:' }); + + app.use(async ctx => { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect(204, (err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie']; + cookie.join().should.match(/koa:sess=sess:/); + request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';', done); + }); + }); + }); }); function App(options) { From cb6d2c8448f21f6cf42efdb031798c9608f05602 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 4 Aug 2017 10:54:51 +0800 Subject: [PATCH 083/154] Release 5.5.0 --- History.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 66ea28b..2bbec70 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.5.0 / 2017-08-04 +================== + +**features** + * [[`ec88cfb`](http://github.com/koajs/session/commit/ec88cfb095ddbfa9a0db465e3f9e459fb6f92bec)] - feat: support options.prefix for external store (#93) (Yiyu He <>) + 5.4.0 / 2017-07-03 ================== diff --git a/package.json b/package.json index 078bdb7..c05e259 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.4.0", + "version": "5.5.0", "keywords": [ "koa", "middleware", @@ -39,4 +39,4 @@ "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" } -} +} \ No newline at end of file From 906277a3c9995ed4f07d2cee55e3020af0c75168 Mon Sep 17 00:00:00 2001 From: Nate Silva Date: Thu, 3 Aug 2017 19:58:10 -0700 Subject: [PATCH 084/154] docs: copyediting (#85) some minor spelling and grammar changes --- Readme.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index f425b73..bee6427 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. default is cookie-based session and support external store. + Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. *Requires Node 7.6 or greater for async/await support* @@ -95,20 +95,21 @@ console.log('listening on port 3000'); ### External Session Stores - Session will store in cookie by default, but it has some disadvantages: + The session is stored in a cookie by default, but it has some disadvantages: - - Session stored in client side unencrypted. - - [Browser cookie always have length limit](http://browsercookielimits.squawky.net/). + - Session is stored on client side unencrypted + - [Browser cookies always have length limits](http://browsercookielimits.squawky.net/) - You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be async function): + You can store the session content in external stores (Redis, MongoDB or other DBs) by passing `options.store` with three methods (these need to be async functions): - `get(key, maxAge, { rolling })`: get session object by key - `set(key, sess, maxAge, { rolling, changed })`: set session object for key, with a `maxAge` (in ms) - `destroy(key)`: destroy session for key - Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** + Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid uisng session as a cache, keep the session lean, and store it in a cookie if possible!** + The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. From c040b5997d35267a3a65becf91e327615ff17fa5 Mon Sep 17 00:00:00 2001 From: Runrioter Wung Date: Fri, 25 Aug 2017 00:25:19 +0800 Subject: [PATCH 085/154] chore: fix example bug and use syntactic sugar (#97) --- example.js | 4 ++-- index.js | 4 +--- package.json | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/example.js b/example.js index abf009d..ea47cce 100644 --- a/example.js +++ b/example.js @@ -1,7 +1,7 @@ var session = require('./'); -var koa = require('koa'); -var app = koa(); +var Koa = require('koa'); +var app = new Koa(); app.keys = ['some secret hurr']; diff --git a/index.js b/index.js index d7978c1..b095060 100644 --- a/index.js +++ b/index.js @@ -24,9 +24,7 @@ const _CONTEXT_SESSION = Symbol('context#_contextSession'); module.exports = function(opts, app) { // session(app[, opts]) if (opts && typeof opts.use === 'function') { - const tmp = app; - app = opts; - opts = tmp; + [ app, opts ] = [ opts, app ]; } // app required if (!app || typeof app.use !== 'function') { diff --git a/package.json b/package.json index c05e259..5091185 100644 --- a/package.json +++ b/package.json @@ -39,4 +39,4 @@ "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" } -} \ No newline at end of file +} From b976b10212f522b675711badb7ce1bc9a909d19d Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Fri, 17 Nov 2017 03:26:19 -0600 Subject: [PATCH 086/154] perf: no need to assign opts (#103) --- lib/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index f1dc702..c4622a1 100644 --- a/lib/context.js +++ b/lib/context.js @@ -14,7 +14,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; - this.opts = Object.assign({}, opts); + this.opts = opts || {}; this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } From 5e41b4726e4c996c4314588cc7b02d24b3943e10 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 17 Nov 2017 17:26:42 +0800 Subject: [PATCH 087/154] Release 5.5.1 --- History.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 2bbec70..c3d4749 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,12 @@ +5.5.1 / 2017-11-17 +================== + +**others** + * [[`b976b10`](http://github.com/koajs/session/commit/b976b10212f522b675711badb7ce1bc9a909d19d)] - perf: no need to assign opts (#103) (Yiyu He <>) + * [[`c040b59`](http://github.com/koajs/session/commit/c040b5997d35267a3a65becf91e327615ff17fa5)] - chore: fix example bug and use syntactic sugar (#97) (Runrioter Wung <>) + * [[`906277a`](http://github.com/koajs/session/commit/906277a3c9995ed4f07d2cee55e3020af0c75168)] - docs: copyediting (#85) (Nate Silva <>) + 5.5.0 / 2017-08-04 ================== diff --git a/package.json b/package.json index 5091185..d12da0b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.5.0", + "version": "5.5.1", "keywords": [ "koa", "middleware", From f00c1ef9857fec52e1aaf981ba9a8e837b3e7ffa Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Tue, 9 Jan 2018 17:31:01 +0800 Subject: [PATCH 088/154] feat: emit events when session invalid (#108) session key add Date.now() --- Readme.md | 12 ++++++++++-- index.js | 4 ++-- lib/context.js | 19 +++++++++++++------ package.json | 1 + test/store.test.js | 21 ++++++++++++++++++++- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Readme.md b/Readme.md index bee6427..3edb50d 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. + Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. *Requires Node 7.6 or greater for async/await support* @@ -111,12 +111,20 @@ console.log('listening on port 3000'); Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid uisng session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. + The way of generating external session id is controlled by the `options.genid`, which defaults to `Date.now() + '-' + uid.sync(24)`. If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. +### Events + +`koa-session` will emit event on `app` when session expired or invalid: + +- `session:missed`: can't get session value from external store. +- `session:invalid`: session value is invalid. +- `session:expired`: session value is expired. + ### Session#isNew Returns __true__ if the session is new. diff --git a/index.js b/index.js index b095060..ebedc97 100644 --- a/index.js +++ b/index.js @@ -94,8 +94,8 @@ function formatOpts(opts) { } if (!opts.genid) { - if (opts.prefix) opts.genid = () => opts.prefix + uid.sync(24); - else opts.genid = () => uid.sync(24); + if (opts.prefix) opts.genid = () => `${opts.prefix}${Date.now()}-${uid.sync(24)}`; + else opts.genid = () => `${Date.now()}-${uid.sync(24)}`; } return opts; diff --git a/lib/context.js b/lib/context.js index c4622a1..ea9cd74 100644 --- a/lib/context.js +++ b/lib/context.js @@ -14,6 +14,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; + this.app = ctx.app; this.opts = opts || {}; this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } @@ -79,7 +80,7 @@ class ContextSession { } const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling }); - if (!this.valid(json)) { + if (!this.valid(json, externalKey)) { // create a new `externalKey` this.create(); return; @@ -144,23 +145,29 @@ class ContextSession { /** * verify session(expired or ) - * @param {Object} json session object + * @param {Object} value session object + * @param {Object} key session externalKey(optional) * @return {Boolean} valid * @api private */ - valid(json) { - if (!json) return false; + valid(value, key) { + if (!value) { + this.app.emit('session:missed', { key, value }); + return false; + } - if (json._expire && json._expire < Date.now()) { + if (value._expire && value._expire < Date.now()) { debug('expired session'); + this.app.emit('session:expired', { key, value }); return false; } const valid = this.opts.valid; - if (typeof valid === 'function' && !valid(this.ctx, json)) { + if (typeof valid === 'function' && !valid(this.ctx, value)) { // valid session value fail, ignore this session debug('invalid session'); + this.app.emit('session:invalid', { key, value }); return false; } return true; diff --git a/package.json b/package.json index d12da0b..f6c0513 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "crc": "^3.4.4", "debug": "^2.2.0", "is-type-of": "^1.0.0", + "pedding": "^1.1.0", "uid-safe": "^2.1.3" }, "engines": { diff --git a/test/store.test.js b/test/store.test.js index 82996ac..d4e2f77 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -6,6 +6,8 @@ const should = require('should'); const mm = require('mm'); const session = require('..'); const store = require('./store'); +const pedding = require('pedding'); +const assert = require('assert'); describe('Koa Session External Store', () => { let cookie; @@ -442,8 +444,13 @@ describe('Koa Session External Store', () => { describe('and expired', () => { it('should expire the sess', done => { + done = pedding(done, 2); const app = App({ maxAge: 100 }); - + app.on('session:expired', args => { + assert(args.key.match(/^\d+-/)); + assert(args.value); + done(); + }); app.use(async function(ctx) { if (ctx.method === 'POST') { ctx.session.message = 'hi'; @@ -535,12 +542,18 @@ describe('Koa Session External Store', () => { describe('when store return empty', () => { it('should create new Session', done => { + done = pedding(done, 2); const app = App({ signed: false }); app.use(async function(ctx) { ctx.body = String(ctx.session.isNew); }); + app.on('session:missed', args => { + assert(args.key === 'invalid-key'); + done(); + }); + request(app.listen()) .get('/') .set('cookie', 'koa:sess=invalid-key') @@ -551,6 +564,7 @@ describe('Koa Session External Store', () => { describe('when valid and beforeSave set', () => { it('should ignore session when uid changed', done => { + done = pedding(done, 2); const app = new Koa(); app.keys = [ 'a', 'b' ]; @@ -573,6 +587,11 @@ describe('Koa Session External Store', () => { uid: ctx.cookies.get('uid'), }; }); + app.on('session:invalid', args => { + assert(args.key); + assert(args.value); + done(); + }); request(app.callback()) .get('/') From d1d4b95cc67240f61b33647925725cf8a1c592ec Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 9 Jan 2018 17:32:04 +0800 Subject: [PATCH 089/154] Release 5.6.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c3d4749..d207321 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.6.0 / 2018-01-09 +================== + +**features** + * [[`f00c1ef`](http://github.com/koajs/session/commit/f00c1ef9857fec52e1aaf981ba9a8e837b3e7ffa)] - feat: emit events when session invalid (#108) (Yiyu He <>) + 5.5.1 / 2017-11-17 ================== diff --git a/package.json b/package.json index f6c0513..70d301c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.5.1", + "version": "5.6.0", "keywords": [ "koa", "middleware", From a2401c85b486a87a4bf933e457b09088496735d7 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 9 Jan 2018 17:38:25 +0800 Subject: [PATCH 090/154] feat: emit event expose ctx --- lib/context.js | 9 +++++---- test/store.test.js | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/context.js b/lib/context.js index ea9cd74..4f3c899 100644 --- a/lib/context.js +++ b/lib/context.js @@ -152,22 +152,23 @@ class ContextSession { */ valid(value, key) { + const ctx = this.ctx; if (!value) { - this.app.emit('session:missed', { key, value }); + this.app.emit('session:missed', { key, value, ctx }); return false; } if (value._expire && value._expire < Date.now()) { debug('expired session'); - this.app.emit('session:expired', { key, value }); + this.app.emit('session:expired', { key, value, ctx }); return false; } const valid = this.opts.valid; - if (typeof valid === 'function' && !valid(this.ctx, value)) { + if (typeof valid === 'function' && !valid(ctx, value)) { // valid session value fail, ignore this session debug('invalid session'); - this.app.emit('session:invalid', { key, value }); + this.app.emit('session:invalid', { key, value, ctx }); return false; } return true; diff --git a/test/store.test.js b/test/store.test.js index d4e2f77..b582d4e 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -449,6 +449,7 @@ describe('Koa Session External Store', () => { app.on('session:expired', args => { assert(args.key.match(/^\d+-/)); assert(args.value); + assert(args.ctx); done(); }); app.use(async function(ctx) { @@ -551,6 +552,7 @@ describe('Koa Session External Store', () => { app.on('session:missed', args => { assert(args.key === 'invalid-key'); + assert(args.ctx); done(); }); @@ -590,6 +592,7 @@ describe('Koa Session External Store', () => { app.on('session:invalid', args => { assert(args.key); assert(args.value); + assert(args.ctx); done(); }); From 2027e75fbca1db444efa3c03b528f33fa3301952 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 9 Jan 2018 17:39:51 +0800 Subject: [PATCH 091/154] Release 5.7.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index d207321..c7bf0b0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.7.0 / 2018-01-09 +================== + +**features** + * [[`a2401c8`](http://github.com/koajs/session/commit/a2401c85b486a87a4bf933e457b09088496735d7)] - feat: emit event expose ctx (dead-horse <>) + 5.6.0 / 2018-01-09 ================== diff --git a/package.json b/package.json index 70d301c..3e2e70d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.6.0", + "version": "5.7.0", "keywords": [ "koa", "middleware", From 72fa5fec71a8fa3c4e8b75226b401e965d8d31c7 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 11 Jan 2018 23:43:55 +0800 Subject: [PATCH 092/154] fix: emit event in next tick --- lib/context.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index 4f3c899..77095ad 100644 --- a/lib/context.js +++ b/lib/context.js @@ -154,13 +154,13 @@ class ContextSession { valid(value, key) { const ctx = this.ctx; if (!value) { - this.app.emit('session:missed', { key, value, ctx }); + this.emit('missed', { key, value, ctx }); return false; } if (value._expire && value._expire < Date.now()) { debug('expired session'); - this.app.emit('session:expired', { key, value, ctx }); + this.emit('expired', { key, value, ctx }); return false; } @@ -168,12 +168,23 @@ class ContextSession { if (typeof valid === 'function' && !valid(ctx, value)) { // valid session value fail, ignore this session debug('invalid session'); - this.app.emit('session:invalid', { key, value, ctx }); + this.emit('invalid', { key, value, ctx }); return false; } return true; } + /** + * @param {String} event event name + * @param {Object} data event data + * @api private + */ + emit(event, data) { + setImmediate(() => { + this.app.emit(`session:${event}`, data); + }); + } + /** * create a new session and attach to ctx.sess * From 6260c6d8d2ca3ed9261e36133d2cf5957ebd31db Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 11 Jan 2018 23:45:26 +0800 Subject: [PATCH 093/154] Release 5.7.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c7bf0b0..7dd85a4 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.7.1 / 2018-01-11 +================== + +**fixes** + * [[`72fa5fe`](http://github.com/koajs/session/commit/72fa5fec71a8fa3c4e8b75226b401e965d8d31c7)] - fix: emit event in next tick (dead-horse <>) + 5.7.0 / 2018-01-09 ================== diff --git a/package.json b/package.json index 3e2e70d..674c0aa 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.7.0", + "version": "5.7.1", "keywords": [ "koa", "middleware", From bb5f4bf86da802cb37cd5e3a990b5bbcc4f6d144 Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Wed, 17 Jan 2018 15:35:29 +0800 Subject: [PATCH 094/154] feat: support opts.renew (#111) --- Readme.md | 3 ++- lib/context.js | 44 +++++++++++++++++++++++++++++---------- package.json | 1 + test/store.test.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/Readme.md b/Readme.md index 3edb50d..9b6720f 100644 --- a/Readme.md +++ b/Readme.md @@ -56,7 +56,8 @@ const CONFIG = { overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ - rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. default is false **/ + rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */ + renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ }; app.use(session(CONFIG, app)); diff --git a/lib/context.js b/lib/context.js index 77095ad..3e60839 100644 --- a/lib/context.js +++ b/lib/context.js @@ -207,7 +207,6 @@ class ContextSession { async commit() { const session = this.session; - const prevHash = this.prevHash; const opts = this.opts; const ctx = this.ctx; @@ -220,24 +219,47 @@ class ContextSession { return; } - // force save session when `session._requireSave` set - let changed = true; - if (!session._requireSave) { - const json = session.toJSON(); - // do nothing if new and not populated - if (!prevHash && !Object.keys(json).length) return; - changed = prevHash !== util.hash(json); - // do nothing if not changed and not in rolling mode - if (!this.opts.rolling && !changed) return; - } + const reason = this._shouldSaveSession(); + debug('should save session: %s', reason); + if (!reason) return; if (typeof opts.beforeSave === 'function') { debug('before save'); opts.beforeSave(ctx, session); } + const changed = reason === 'changed'; await this.save(changed); } + _shouldSaveSession() { + const prevHash = this.prevHash; + const session = this.session; + + // force save session when `session._requireSave` set + if (session._requireSave) return 'force'; + + // do nothing if new and not populated + const json = session.toJSON(); + if (!prevHash && !Object.keys(json).length) return ''; + + // save if session changed + const changed = prevHash !== util.hash(json); + if (changed) return 'changed'; + + // save if opts.rolling set + if (this.opts.rolling) return 'rolling'; + + // save if opts.renew and session will expired + if (this.opts.renew) { + const expire = session._expire; + // renew when session will expired in maxAge / 2 + if (!expire || expire - Date.now() > session.maxAge / 2) return ''; + return 'renew'; + } + + return ''; + } + /** * remove session * @api private diff --git a/package.json b/package.json index 674c0aa..3a61042 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "koa": "2", "mm": "^2.1.0", "mocha": "3", + "mz-modules": "^2.0.0", "should": "8", "supertest": "2" }, diff --git a/test/store.test.js b/test/store.test.js index b582d4e..9943ffb 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -8,6 +8,7 @@ const session = require('..'); const store = require('./store'); const pedding = require('pedding'); const assert = require('assert'); +const sleep = require('mz-modules/sleep'); describe('Koa Session External Store', () => { let cookie; @@ -717,6 +718,57 @@ describe('Koa Session External Store', () => { }); }); }); + + describe('when renew set to true', () => { + let app; + before(() => { + app = App({ renew: true, maxAge: 2000 }); + + app.use(async ctx => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', () => { + return request(app.callback()) + .get('/') + .expect({}) + .expect(res => { + should.not.exist(res.headers['set-cookie']); + }); + }); + + it('should send set-cookie when session near expire and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + await sleep(1200); + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + res.headers['set-cookie'].should.have.length(2); + }); + + it('should not send set-cookie when session not near expire and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + await sleep(500); + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + should.not.exist(res.headers['set-cookie']); + }); + }); }); function App(options) { From 9854d03429e67f4dbce8bd211f8f9db8a1f82f75 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 17 Jan 2018 15:36:11 +0800 Subject: [PATCH 095/154] Release 5.8.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 7dd85a4..91c8fcb 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.8.0 / 2018-01-17 +================== + +**features** + * [[`bb5f4bf`](http://github.com/koajs/session/commit/bb5f4bf86da802cb37cd5e3a990b5bbcc4f6d144)] - feat: support opts.renew (#111) (Yiyu He <>) + 5.7.1 / 2018-01-11 ================== diff --git a/package.json b/package.json index 3a61042..9060d2a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.7.1", + "version": "5.8.0", "keywords": [ "koa", "middleware", From bdb4fd45a7c247c94f0035585104b004e36ec725 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:36:02 +0800 Subject: [PATCH 096/154] fix: ensure store expired after cookie --- lib/context.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index 3e60839..df162ee 100644 --- a/lib/context.js +++ b/lib/context.js @@ -252,9 +252,9 @@ class ContextSession { // save if opts.renew and session will expired if (this.opts.renew) { const expire = session._expire; + const maxAge = session.maxAge; // renew when session will expired in maxAge / 2 - if (!expire || expire - Date.now() > session.maxAge / 2) return ''; - return 'renew'; + if (expire && maxAge && expire - Date.now() < maxAge / 2) return 'renew'; } return ''; @@ -286,7 +286,7 @@ class ContextSession { const externalKey = this.externalKey; let json = this.session.toJSON(); // set expire for check - const maxAge = opts.maxAge ? opts.maxAge : ONE_DAY; + let maxAge = opts.maxAge ? opts.maxAge : ONE_DAY; if (maxAge === 'session') { // do not set _expire in json if maxAge is set to 'session' // also delete maxAge from options @@ -300,6 +300,10 @@ class ContextSession { // save to external store if (externalKey) { debug('save %j to external key %s', json, externalKey); + if (typeof maxAge === 'number') { + // ensure store expired after cookie + maxAge += 10000; + } await this.store.set(externalKey, json, maxAge, { changed, rolling: opts.rolling, From 622a2d803ff0d42ff1620e3c576b089b3725a65f Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:37:43 +0800 Subject: [PATCH 097/154] Release 5.8.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 91c8fcb..9d87396 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.8.1 / 2018-01-17 +================== + +**fixes** + * [[`bdb4fd4`](http://github.com/koajs/session/commit/bdb4fd45a7c247c94f0035585104b004e36ec725)] - fix: ensure store expired after cookie (dead-horse <>) + 5.8.0 / 2018-01-17 ================== diff --git a/package.json b/package.json index 9060d2a..53fed2a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.0", + "version": "5.8.1", "keywords": [ "koa", "middleware", From c48e1e054566fe09c81ff50f530c6f230f07c7d5 Mon Sep 17 00:00:00 2001 From: Wellington Soares Date: Fri, 15 Jun 2018 23:22:41 -0300 Subject: [PATCH 098/154] Update Readme.md (#123) fixing a small typo --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 9b6720f..c35313a 100644 --- a/Readme.md +++ b/Readme.md @@ -109,7 +109,7 @@ console.log('listening on port 3000'); - `destroy(key)`: destroy session for key - Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid uisng session as a cache, keep the session lean, and store it in a cookie if possible!** + Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** The way of generating external session id is controlled by the `options.genid`, which defaults to `Date.now() + '-' + uid.sync(24)`. From c487944c22056fdd37433bdeab3d665dbd116744 Mon Sep 17 00:00:00 2001 From: Adriano Date: Wed, 11 Jul 2018 13:58:07 -0300 Subject: [PATCH 099/154] fix: Fixes a bug that reset the cookie expire date to the default (1 day) when using browser sessions (maxAge: 'session') (#117) --- lib/context.js | 1 + lib/session.js | 1 + test/contextstore.test.js | 43 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/lib/context.js b/lib/context.js index df162ee..ac80990 100644 --- a/lib/context.js +++ b/lib/context.js @@ -291,6 +291,7 @@ class ContextSession { // do not set _expire in json if maxAge is set to 'session' // also delete maxAge from options opts.maxAge = undefined; + json._session = true; } else { // set expire for check json._expire = maxAge + Date.now(); diff --git a/lib/session.js b/lib/session.js index b585f2a..61d1320 100644 --- a/lib/session.js +++ b/lib/session.js @@ -20,6 +20,7 @@ class Session { for (const k in obj) { // restore maxAge from store if (k === '_maxAge') this._ctx.sessionOptions.maxAge = obj._maxAge; + else if (k === '_session') this._ctx.sessionOptions.maxAge = 'session'; else this[k] = obj[k]; } } diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 0905fae..a190045 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -384,6 +384,49 @@ describe('Koa Session External Context Store', () => { .expect('hi', done); }); }); + it('should not expire the session after multiple session changes', done => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + ctx.session.count = (ctx.session.count || 0) + 1; + ctx.body = `hi ${ctx.session.count}`; + }); + const server = app.listen(); + + request(server) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect('hi 1') + .end((err, res) => { + if (err) return done(err); + let cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa:sess/) + .expect('hi 2') + .end((err, res) => { + if (err) return done(err); + cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa:sess/) + .expect('hi 3') + .end((err, res) => { + if (err) return done(err); + cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + + done(); + }); + }); + }); + }); it('should use the default maxAge when improper string given', done => { const app = App({ maxAge: 'not the right string' }); From 90506055366a31205b0895592eb00d43f8d9da28 Mon Sep 17 00:00:00 2001 From: Daniel Tseng Date: Thu, 12 Jul 2018 01:01:12 +0800 Subject: [PATCH 100/154] deps: Upgrade debug@^3.1.0 (#107) This version of debug addresses a minor ReDoS issue. See https://nodesecurity.io/advisories/534 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 53fed2a..3968273 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "license": "MIT", "dependencies": { "crc": "^3.4.4", - "debug": "^2.2.0", + "debug": "^3.1.0", "is-type-of": "^1.0.0", "pedding": "^1.1.0", "uid-safe": "^2.1.3" From d15a102b2f736ad4ebb41d6d19c54363466e3216 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 12 Jul 2018 01:04:10 +0800 Subject: [PATCH 101/154] Release 5.8.2 --- History.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 9d87396..4eec28f 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,14 @@ +5.8.2 / 2018-07-12 +================== + +**fixes** + * [[`c487944`](http://github.com/koajs/session/commit/c487944c22056fdd37433bdeab3d665dbd116744)] - fix: Fixes a bug that reset the cookie expire date to the default (1 day) when using browser sessions (maxAge: 'session') (#117) (Adriano <>) + +**others** + * [[`9050605`](http://github.com/koajs/session/commit/90506055366a31205b0895592eb00d43f8d9da28)] - deps: Upgrade debug@^3.1.0 (#107) (Daniel Tseng <>) + * [[`c48e1e0`](http://github.com/koajs/session/commit/c48e1e054566fe09c81ff50f530c6f230f07c7d5)] - Update Readme.md (#123) (Wellington Soares <>) + 5.8.1 / 2018-01-17 ================== diff --git a/package.json b/package.json index 3968273..dca32e4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.1", + "version": "5.8.2", "keywords": [ "koa", "middleware", From 95272ff912af8dd31ae9f038df9540d8b6c019d7 Mon Sep 17 00:00:00 2001 From: Maples7 Date: Wed, 1 Aug 2018 13:42:54 +0800 Subject: [PATCH 102/154] fix typo in README.md (#134) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index c35313a..f2ee958 100644 --- a/Readme.md +++ b/Readme.md @@ -116,7 +116,7 @@ console.log('listening on port 3000'); If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. - If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. + If your session store requires data or utilities from context, `opts.ContextStore` is also supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. ### Events From 6f1a41cf499f55532f0e7ce0de04d778a0466496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=96=E7=8C=A9?= Date: Wed, 22 Aug 2018 20:32:56 +0800 Subject: [PATCH 103/154] fix: session not works (#136) --- lib/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index ac80990..aba8e7d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -15,7 +15,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; this.app = ctx.app; - this.opts = opts || {}; + this.opts = Object.assign({}, opts); this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } From 2497dc550a3c8689fd0de4886f2ae5eebff5a490 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Aug 2018 20:38:36 +0800 Subject: [PATCH 104/154] Release 5.8.3 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 4eec28f..71026e1 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +5.8.3 / 2018-08-22 +================== + +**fixes** + * [[`6f1a41c`](http://github.com/koajs/session/commit/6f1a41cf499f55532f0e7ce0de04d778a0466496)] - fix: session not works (#136) (吖猩 <>) + +**others** + * [[`95272ff`](http://github.com/koajs/session/commit/95272ff912af8dd31ae9f038df9540d8b6c019d7)] - fix typo in README.md (#134) (Maples7 <>) + 5.8.2 / 2018-07-12 ================== diff --git a/package.json b/package.json index dca32e4..38e0c40 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.2", + "version": "5.8.3", "keywords": [ "koa", "middleware", From 724140076b65867b1a0cffee4f061971be8751c0 Mon Sep 17 00:00:00 2001 From: Jonas Galvez Date: Tue, 28 Aug 2018 12:20:44 -0300 Subject: [PATCH 105/154] feat: Add autoCommit option (#139) --- Readme.md | 5 ++++ index.js | 5 +++- lib/context.js | 2 +- lib/session.js | 16 +++++++++++-- test/contextstore.test.js | 48 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index f2ee958..efcd6e7 100644 --- a/Readme.md +++ b/Readme.md @@ -53,6 +53,7 @@ const CONFIG = { /** 'session' will result in a cookie that expires when session/browser is closed */ /** Warning: If a session cookie is stolen, this cookie will never expire */ maxAge: 86400000, + autoCommit: true, /** (boolean) automatically commit headers (default true) */ overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ @@ -150,6 +151,10 @@ if (this.session.isNew) { Save this session no matter whether it is populated. +### Session#manuallyCommit() + + Session headers are auto committed by default. Use this if `autoCommit` is set to `false`. + ### Destroying a session To destroy a session simply set it to `null`: diff --git a/index.js b/index.js index ebedc97..7cb9edc 100644 --- a/index.js +++ b/index.js @@ -42,7 +42,9 @@ module.exports = function(opts, app) { } catch (err) { throw err; } finally { - await sess.commit(); + if (opts.autoCommit) { + await sess.commit(); + } } }; }; @@ -67,6 +69,7 @@ function formatOpts(opts) { if (opts.overwrite == null) opts.overwrite = true; if (opts.httpOnly == null) opts.httpOnly = true; if (opts.signed == null) opts.signed = true; + if (opts.autoCommit == null) opts.autoCommit = true; debug('session options %j', opts); diff --git a/lib/context.js b/lib/context.js index aba8e7d..90c0c32 100644 --- a/lib/context.js +++ b/lib/context.js @@ -196,7 +196,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); if (this.store) this.externalKey = externalKey || this.opts.genid(); - this.session = new Session(this.ctx, val); + this.session = new Session(this, val); } /** diff --git a/lib/session.js b/lib/session.js index 61d1320..6961e3b 100644 --- a/lib/session.js +++ b/lib/session.js @@ -12,8 +12,9 @@ class Session { * @api private */ - constructor(ctx, obj) { - this._ctx = ctx; + constructor(sessionContext, obj) { + this._sessCtx = sessionContext; + this._ctx = sessionContext.ctx; if (!obj) { this.isNew = true; } else { @@ -111,6 +112,17 @@ class Session { save() { this._requireSave = true; } + + /** + * commit this session's headers if autoCommit is set to false + * + * @api public + */ + + async manuallyCommit() { + await this._sessCtx.commit(); + } + } module.exports = Session; diff --git a/test/contextstore.test.js b/test/contextstore.test.js index a190045..84f30b7 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -356,6 +356,54 @@ describe('Koa Session External Context Store', () => { }); }); + describe('when autoCommit is present', () => { + describe('and set to false', () => { + it('should not set headers if manuallyCommit() isn\'t called', done => { + const app = App({ autoCommit: false }); + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie']; + should.not.exist(cookie); + }) + .expect(200, done); + }); + it('should set headers if manuallyCommit() is called', done => { + const app = App({ autoCommit: false }); + app.use(async function(ctx, next) { + if (ctx.method === 'POST') { + ctx.session.message = 'dummy'; + } + await next(); + }); + app.use(async function(ctx) { + ctx.body = 200; + await ctx.session.manuallyCommit(); + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end(err => { + if (err) return done(err); + }) + .expect(200, done); + }); + }); + }); + describe('when maxAge present', () => { describe('and set to be a session cookie', () => { it('should not expire the session', done => { From 4ea5a63470b936642a18875997e1a627794d198d Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 28 Aug 2018 23:21:17 +0800 Subject: [PATCH 106/154] Release 5.9.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 71026e1..f593854 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.9.0 / 2018-08-28 +================== + +**features** + * [[`7241400`](http://github.com/koajs/session/commit/724140076b65867b1a0cffee4f061971be8751c0)] - feat: Add autoCommit option (#139) (Jonas Galvez <>) + 5.8.3 / 2018-08-22 ================== diff --git a/package.json b/package.json index 38e0c40..11bc5ec 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.3", + "version": "5.9.0", "keywords": [ "koa", "middleware", From 81906f7724ef009dc14686f4990af35c716f6db9 Mon Sep 17 00:00:00 2001 From: Tree Xie Date: Mon, 29 Oct 2018 00:13:04 +0800 Subject: [PATCH 107/154] feat: support options.externalKey #88 (#149) --- Readme.md | 7 +++++ index.js | 6 +++++ lib/context.js | 17 +++++++++--- test/externalkey.test.js | 56 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 test/externalkey.test.js diff --git a/Readme.md b/Readme.md index efcd6e7..6a00431 100644 --- a/Readme.md +++ b/Readme.md @@ -127,6 +127,13 @@ console.log('listening on port 3000'); - `session:invalid`: session value is invalid. - `session:expired`: session value is expired. +### Custom External Key + +External key is used the cookie by default, but you can use `options.externalKey` to customize your own external key methods. `options.externalKey` with two methods: + +- `get(ctx)`: get the external key +- `set(ctx, value)`: set the external key + ### Session#isNew Returns __true__ if the session is new. diff --git a/index.js b/index.js index 7cb9edc..606ab13 100644 --- a/index.js +++ b/index.js @@ -88,6 +88,12 @@ function formatOpts(opts) { assert(is.function(store.destroy), 'store.destroy must be function'); } + const externalKey = opts.externalKey; + if (externalKey) { + assert(is.function(externalKey.get), 'externalKey.get must be function'); + assert(is.function(externalKey.set), 'externalKey.set must be function'); + } + const ContextStore = opts.ContextStore; if (ContextStore) { assert(is.class(ContextStore), 'ContextStore must be a class'); diff --git a/lib/context.js b/lib/context.js index 90c0c32..410996b 100644 --- a/lib/context.js +++ b/lib/context.js @@ -70,8 +70,15 @@ class ContextSession { const ctx = this.ctx; const opts = this.opts; - const externalKey = ctx.cookies.get(opts.key, opts); - debug('get external key from cookie %s', externalKey); + let externalKey; + if (opts.externalKey) { + externalKey = opts.externalKey.get(ctx); + debug('get external key from custom %s', externalKey); + } else { + externalKey = ctx.cookies.get(opts.key, opts); + debug('get external key from cookie %s', externalKey); + } + if (!externalKey) { // create a new `externalKey` @@ -309,7 +316,11 @@ class ContextSession { changed, rolling: opts.rolling, }); - this.ctx.cookies.set(key, externalKey, opts); + if (opts.externalKey) { + opts.externalKey.set(this.ctx, externalKey); + } else { + this.ctx.cookies.set(key, externalKey, opts); + } return; } diff --git a/test/externalkey.test.js b/test/externalkey.test.js new file mode 100644 index 0000000..c3625cb --- /dev/null +++ b/test/externalkey.test.js @@ -0,0 +1,56 @@ +'use strict'; +const Koa = require('koa'); +const request = require('supertest'); +const assert = require('assert'); +const session = require('..'); +const store = require('./store'); +const TOKEN_KEY = 'User-Token'; +describe('Koa Session External Key', () => { + describe('when the external key set/get is invalid', () => { + it('should throw a error', () => { + try { + new App({ + externalKey: {}, + }); + } catch (err) { + assert.equal(err.message, 'externalKey.get must be function'); + } + }); + }); + describe('custom get/set external key', () => { + it('should still work', done => { + const app = App(); + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + const server = app.listen(); + request(server) + .post('/') + .expect(204, (err, res) => { + if (err) return done(err); + const token = res.get(TOKEN_KEY); + request(server) + .get('/') + .set(TOKEN_KEY, token) + .expect(';', done); + }); + }); + }); +}); +function App(options) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options = options || {}; + options.store = store; + options.externalKey = options.externalKey || { + get: ctx => ctx.get(TOKEN_KEY), + set: (ctx, value) => ctx.set(TOKEN_KEY, value), + }; + app.use(session(options, app)); + return app; +} From 21fab9e5ac09cb182e3adfc52aee876b18861b24 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 29 Oct 2018 00:13:31 +0800 Subject: [PATCH 108/154] Release 5.10.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index f593854..ee7a4a0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.10.0 / 2018-10-29 +================== + +**features** + * [[`81906f7`](http://github.com/koajs/session/commit/81906f7724ef009dc14686f4990af35c716f6db9)] - feat: support options.externalKey #88 (#149) (Tree Xie <>) + 5.9.0 / 2018-08-28 ================== diff --git a/package.json b/package.json index 11bc5ec..d33ca05 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.9.0", + "version": "5.10.0", "keywords": [ "koa", "middleware", From 89c048adc5a64b6c12c87047b766ac34be10af77 Mon Sep 17 00:00:00 2001 From: Vitaliy Zaytsev Date: Tue, 11 Dec 2018 13:03:24 +0600 Subject: [PATCH 109/154] fix: moved "pedding" package to dev dependencies (#155) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d33ca05..319b103 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "mm": "^2.1.0", "mocha": "3", "mz-modules": "^2.0.0", + "pedding": "^1.1.0", "should": "8", "supertest": "2" }, @@ -29,7 +30,6 @@ "crc": "^3.4.4", "debug": "^3.1.0", "is-type-of": "^1.0.0", - "pedding": "^1.1.0", "uid-safe": "^2.1.3" }, "engines": { From 5f12f7019b4fbb3ce1d495c1c7fb8a234ae16818 Mon Sep 17 00:00:00 2001 From: killa Date: Tue, 18 Dec 2018 11:30:01 +0800 Subject: [PATCH 110/154] feat: allow init multi session middleware (#159) --- index.js | 3 +++ lib/util.js | 4 ++-- test/cookie.test.js | 13 +++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 606ab13..f2db549 100644 --- a/index.js +++ b/index.js @@ -120,6 +120,9 @@ function formatOpts(opts) { */ function extendContext(context, opts) { + if (context.hasOwnProperty(CONTEXT_SESSION)) { + return; + } Object.defineProperties(context, { [CONTEXT_SESSION]: { get() { diff --git a/lib/util.js b/lib/util.js index 2bdd7b8..759197c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -13,7 +13,7 @@ module.exports = { */ decode(string) { - const body = new Buffer(string, 'base64').toString('utf8'); + const body = Buffer.from(string, 'base64').toString('utf8'); const json = JSON.parse(body); return json; }, @@ -28,7 +28,7 @@ module.exports = { encode(body) { body = JSON.stringify(body); - return new Buffer(body).toString('base64'); + return Buffer.from(body).toString('base64'); }, hash(sess) { diff --git a/test/cookie.test.js b/test/cookie.test.js index dc15fb9..bf0e569 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -1,5 +1,6 @@ 'use strict'; +const assert = require('assert'); const Koa = require('koa'); const request = require('supertest'); const should = require('should'); @@ -795,6 +796,18 @@ describe('Koa Session Cookie', () => { }); }); }); + + describe('init multi session middleware', () => { + it('should work', () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + const s1 = session({}, app); + const s2 = session({}, app); + assert(s1); + assert(s2); + }); + }); }); function App(options) { From e66fbd16bb05d5a4b8001ceb155c3f7440490407 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 18 Dec 2018 11:36:23 +0800 Subject: [PATCH 111/154] Release 5.10.1 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index ee7a4a0..22f0972 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +5.10.1 / 2018-12-18 +================== + +**features** + * [[`5f12f70`](http://github.com/koajs/session/commit/5f12f7019b4fbb3ce1d495c1c7fb8a234ae16818)] - feat: allow init multi session middleware (#159) (killa <>) + +**fixes** + * [[`89c048a`](http://github.com/koajs/session/commit/89c048adc5a64b6c12c87047b766ac34be10af77)] - fix: moved "pedding" package to dev dependencies (#155) (Vitaliy Zaytsev <>) + 5.10.0 / 2018-10-29 ================== diff --git a/package.json b/package.json index 319b103..7563660 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.10.0", + "version": "5.10.1", "keywords": [ "koa", "middleware", From 1600aabdfa6a86973e3fab9f4064c3ed82b10604 Mon Sep 17 00:00:00 2001 From: Vitaliy Zaytsev Date: Tue, 18 Dec 2018 12:11:54 +0600 Subject: [PATCH 112/154] test: changed "ctx.session is mockable" tests names to more appropriate (#158) --- test/contextstore.test.js | 2 +- test/store.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 84f30b7..1d3058e 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -700,7 +700,7 @@ describe('Koa Session External Context Store', () => { describe('ctx.session', () => { after(mm.restore); - it('should be mocked', done => { + it('can be mocked', done => { const app = App(); app.use(async function(ctx) { diff --git a/test/store.test.js b/test/store.test.js index 9943ffb..a5e8147 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -631,7 +631,7 @@ describe('Koa Session External Store', () => { describe('ctx.session', () => { after(mm.restore); - it('should be mocked', done => { + it('can be mocked', done => { const app = App(); app.use(async function(ctx) { From 23ad8718a9a392c0c563893a10b5ca9f6fd70ebe Mon Sep 17 00:00:00 2001 From: Douglas Wade Date: Wed, 19 Dec 2018 18:50:31 -0800 Subject: [PATCH 113/154] deps: Fix security vulnerabilities from npm audit (#163) --- package-lock.json | 2001 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 10 +- 2 files changed, 2006 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..01bbb9f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2001 @@ +{ + "name": "koa-session", + "version": "5.10.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": 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" + } + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "brace-expansion": { + "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" + } + }, + "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 + }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "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.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": 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=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "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 + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "cookies": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", + "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "keygrip": "~1.0.3" + } + }, + "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=" + }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "d": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "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" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "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=", + "dev": true + }, + "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 + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "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", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", + "dev": true + }, + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "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=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-config-egg": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", + "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "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=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "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 + }, + "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", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "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" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-assert": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.0.tgz", + "integrity": "sha512-tPVv62a6l3BbQoM/N5qo969l0OFxqpnQzNUPeYfTP6Spo4zkgWeDBD1D5thI7sDLg7jCCihXTLB0X8UtdyAy8A==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.1" + } + }, + "http-errors": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", + "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "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" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "is-class-hotfix": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", + "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", + "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-type-of": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", + "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", + "requires": { + "core-util-is": "^1.0.2", + "is-class-hotfix": "~0.0.6", + "isstream": "~0.1.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "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": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "keygrip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", + "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", + "dev": true + }, + "ko-sleep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", + "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "koa": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.6.2.tgz", + "integrity": "sha512-KdnBFhTgh9ysMMoYe4J4fLvaKjT7mF3nRYV8MjxLzx6qywFNeptqi4xevyUltg1fZl2CFJ+HeLXuCGx07Yvl/A==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.7.1", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "koa-is-json": "^1.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "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 + }, + "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.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "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" + } + }, + "mm": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/mm/-/mm-2.4.1.tgz", + "integrity": "sha512-NXWPSHcaTtC8GqcJ5w1FvX28ieWwEobrsfngBugrbyhAEg710i2W+vZuMuIBW6VnFt0l4b9WsjvqMytvmc3HmQ==", + "dev": true, + "requires": { + "is-type-of": "^1.0.0", + "ko-sleep": "^1.0.2", + "muk-prop": "^1.0.0", + "thenify": "^3.2.1" + } + }, + "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": { + "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 + }, + "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" + } + }, + "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" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "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" + } + } + } + }, + "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==" + }, + "muk-prop": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", + "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "mz-modules": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", + "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", + "dev": true, + "requires": { + "glob": "^7.1.2", + "ko-sleep": "^1.0.3", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "rimraf": "^2.6.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "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" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "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=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "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" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "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": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pedding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", + "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", + "dev": true + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "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 + }, + "progress": { + "version": "1.1.8", + "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "qs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", + "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==", + "dev": true + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "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" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", + "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "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 + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "should": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", + "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", + "dev": true, + "requires": { + "should-equal": "0.8.0", + "should-format": "0.3.2", + "should-type": "0.2.0" + } + }, + "should-equal": { + "version": "0.8.0", + "resolved": "http://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", + "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-format": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", + "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-type": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", + "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "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": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 7563660..ff25fb3 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "istanbul": "0", "koa": "2", "mm": "^2.1.0", - "mocha": "3", + "mocha": "^5.2.0", "mz-modules": "^2.0.0", "pedding": "^1.1.0", "should": "8", - "supertest": "2" + "supertest": "^3.3.0" }, "license": "MIT", "dependencies": { @@ -36,9 +36,9 @@ "node": ">=7.6" }, "scripts": { - "test": "npm run lint && NODE_ENV=test mocha --require should --reporter spec test/*.test.js", - "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", - "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", + "test": "npm run lint && NODE_ENV=test mocha --exit --require should --reporter spec test/*.test.js", + "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --exit --require should test/*.test.js", + "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", "lint": "eslint lib test index.js" } } From c2b4259ccef6095cad2f3ff51968b21cea993d13 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Mon, 29 Apr 2019 16:46:57 +0800 Subject: [PATCH 114/154] fix: remove package-lock.json --- package-lock.json | 2001 --------------------------------------------- 1 file changed, 2001 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 01bbb9f..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2001 +0,0 @@ -{ - "name": "koa-session", - "version": "5.10.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": 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" - } - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true, - "optional": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" - }, - "brace-expansion": { - "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" - } - }, - "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 - }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-content-type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", - "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", - "dev": true, - "requires": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "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.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": 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=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "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 - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "cookies": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", - "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "keygrip": "~1.0.3" - } - }, - "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=" - }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "requires": { - "buffer": "^5.1.0" - } - }, - "d": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "^0.10.9" - } - }, - "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" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "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=", - "dev": true - }, - "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 - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "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", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-inject": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", - "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", - "dev": true - }, - "es5-ext": { - "version": "0.10.46", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", - "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "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=", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.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" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-config-egg": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", - "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "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=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - } - }, - "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 - }, - "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", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "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" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true - } - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "http-assert": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.0.tgz", - "integrity": "sha512-tPVv62a6l3BbQoM/N5qo969l0OFxqpnQzNUPeYfTP6Spo4zkgWeDBD1D5thI7sDLg7jCCihXTLB0X8UtdyAy8A==", - "dev": true, - "requires": { - "deep-equal": "~1.0.1", - "http-errors": "~1.7.1" - } - }, - "http-errors": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", - "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "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" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "inquirer": { - "version": "0.12.0", - "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "is-class-hotfix": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", - "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-generator-function": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", - "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", - "dev": true - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", - "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-type-of": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", - "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", - "requires": { - "core-util-is": "^1.0.2", - "is-class-hotfix": "~0.0.6", - "isstream": "~0.1.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "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": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "keygrip": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", - "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", - "dev": true - }, - "ko-sleep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", - "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "koa": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.6.2.tgz", - "integrity": "sha512-KdnBFhTgh9ysMMoYe4J4fLvaKjT7mF3nRYV8MjxLzx6qywFNeptqi4xevyUltg1fZl2CFJ+HeLXuCGx07Yvl/A==", - "dev": true, - "requires": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.7.1", - "debug": "~3.1.0", - "delegates": "^1.0.0", - "depd": "^1.1.2", - "destroy": "^1.0.4", - "error-inject": "^1.0.0", - "escape-html": "^1.0.3", - "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", - "koa-compose": "^4.1.0", - "koa-convert": "^1.2.0", - "koa-is-json": "^1.0.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", - "vary": "^1.1.2" - }, - "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" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", - "dev": true - }, - "koa-convert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", - "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", - "dev": true, - "requires": { - "co": "^4.6.0", - "koa-compose": "^3.0.0" - }, - "dependencies": { - "koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "dev": true, - "requires": { - "any-promise": "^1.1.0" - } - } - } - }, - "koa-is-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", - "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "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 - }, - "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.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "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" - } - }, - "mm": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/mm/-/mm-2.4.1.tgz", - "integrity": "sha512-NXWPSHcaTtC8GqcJ5w1FvX28ieWwEobrsfngBugrbyhAEg710i2W+vZuMuIBW6VnFt0l4b9WsjvqMytvmc3HmQ==", - "dev": true, - "requires": { - "is-type-of": "^1.0.0", - "ko-sleep": "^1.0.2", - "muk-prop": "^1.0.0", - "thenify": "^3.2.1" - } - }, - "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": { - "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 - }, - "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" - } - }, - "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" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "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" - } - } - } - }, - "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==" - }, - "muk-prop": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", - "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "mz-modules": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", - "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", - "dev": true, - "requires": { - "glob": "^7.1.2", - "ko-sleep": "^1.0.3", - "mkdirp": "^0.5.1", - "pump": "^3.0.0", - "rimraf": "^2.6.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "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" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", - "dev": true - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "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=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "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" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "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": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "pedding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", - "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", - "dev": true - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "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 - }, - "progress": { - "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "qs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", - "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==", - "dev": true - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" - }, - "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" - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", - "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "^1.3.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "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 - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "should": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", - "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", - "dev": true, - "requires": { - "should-equal": "0.8.0", - "should-format": "0.3.2", - "should-type": "0.2.0" - } - }, - "should-equal": { - "version": "0.8.0", - "resolved": "http://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", - "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-format": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", - "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-type": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", - "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "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": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "3.8.3", - "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "through": { - "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "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": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "ylru": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", - "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", - "dev": true - } - } -} From b79134d6854173bf46d6703e79636a58f9282e15 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Mon, 29 Apr 2019 17:27:05 +0800 Subject: [PATCH 115/154] feat: make sure session id is global unique (#170) --- .travis.yml | 2 ++ Readme.md | 4 ++-- index.js | 6 +++--- package.json | 4 +++- test/genid_bench.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 2 +- 6 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 test/genid_bench.js diff --git a/.travis.yml b/.travis.yml index ec9e856..80b9e4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,7 @@ language: node_js node_js: - '7' - '8' + - '10' + - '12' script: 'npm run test-travis' after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' diff --git a/Readme.md b/Readme.md index 6a00431..337a177 100644 --- a/Readme.md +++ b/Readme.md @@ -113,7 +113,7 @@ console.log('listening on port 3000'); Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid`, which defaults to `Date.now() + '-' + uid.sync(24)`. + The way of generating external session id is controlled by the `options.genid`, which defaults to `uuid.v4()`. If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. @@ -127,7 +127,7 @@ console.log('listening on port 3000'); - `session:invalid`: session value is invalid. - `session:expired`: session value is expired. -### Custom External Key +### Custom External Key External key is used the cookie by default, but you can use `options.externalKey` to customize your own external key methods. `options.externalKey` with two methods: diff --git a/index.js b/index.js index f2db549..63dca92 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); -const uid = require('uid-safe'); +const uuid = require('uuid/v4'); const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); @@ -103,8 +103,8 @@ function formatOpts(opts) { } if (!opts.genid) { - if (opts.prefix) opts.genid = () => `${opts.prefix}${Date.now()}-${uid.sync(24)}`; - else opts.genid = () => `${Date.now()}-${uid.sync(24)}`; + if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid()}`; + else opts.genid = uuid; } return opts; diff --git a/package.json b/package.json index ff25fb3..9f701d4 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "lib" ], "devDependencies": { + "benchmark": "^2.1.4", "eslint": "3", "eslint-config-egg": "3", "istanbul": "0", @@ -22,6 +23,7 @@ "mocha": "^5.2.0", "mz-modules": "^2.0.0", "pedding": "^1.1.0", + "uid-safe": "^2.1.3", "should": "8", "supertest": "^3.3.0" }, @@ -30,7 +32,7 @@ "crc": "^3.4.4", "debug": "^3.1.0", "is-type-of": "^1.0.0", - "uid-safe": "^2.1.3" + "uuid": "^3.3.2" }, "engines": { "node": ">=7.6" diff --git a/test/genid_bench.js b/test/genid_bench.js new file mode 100644 index 0000000..8ff2900 --- /dev/null +++ b/test/genid_bench.js @@ -0,0 +1,45 @@ +'use strict'; + +const Benchmark = require('benchmark'); +const uuid = require('uuid'); +const uid = require('uid-safe'); + +const suite = new Benchmark.Suite(); + +const genidByUid = () => `${Date.now()}-${uid.sync(24)}`; +const genidByUuidV1 = () => uuid.v1(); +const genidByUuidV4 = () => uuid.v4(); + +console.log('genidByUid() => %s', genidByUid()); +console.log('genidByUuidV1() => %s', genidByUuidV1()); +console.log('genidByUuidV4() => %s', genidByUuidV4()); + +// add tests +suite +.add('uid()', function() { + genidByUid(); +}) +.add('genidByUuidV1()', function() { + genidByUuidV1(); +}) +.add('genidByUuidV4()', function() { + genidByUuidV4(); +}) +// add listeners +.on('cycle', function(event) { + console.log(String(event.target)); +}) +.on('complete', function() { + console.log('Fastest is ' + this.filter('fastest').map('name')); +}) +// run async +.run({ async: true }); + +// genidByUid() => 1556529339180-DRnQyEqlYjGr_Zq_42fHpdFMlBfVlAoG +// genidByUuidV1() => 5af3b830-6a5f-11e9-91fb-abc918efca3d +// genidByUuidV4() => 27088fa8-8436-4c8b-aae7-76ba316db9e3 +// +// uid() x 260,850 ops/sec ±1.50% (84 runs sampled) +// genidByUuidV1() x 1,181,483 ops/sec ±0.93% (86 runs sampled) +// genidByUuidV4() x 301,840 ops/sec ±1.40% (83 runs sampled) +// Fastest is genidByUuidV1() diff --git a/test/store.test.js b/test/store.test.js index a5e8147..7f9587c 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -448,7 +448,7 @@ describe('Koa Session External Store', () => { done = pedding(done, 2); const app = App({ maxAge: 100 }); app.on('session:expired', args => { - assert(args.key.match(/^\d+-/)); + assert(args.key.match(/^\w+-/)); assert(args.value); assert(args.ctx); done(); From 5ca903f7f9e3c2fd901f62d7e41da391e4cff258 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Mon, 29 Apr 2019 17:31:05 +0800 Subject: [PATCH 116/154] Release 5.11.0 --- History.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 22f0972..e498d77 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,17 @@ +5.11.0 / 2019-04-29 +================== + +**features** + * [[`b79134d`](http://github.com/koajs/session/commit/b79134d6854173bf46d6703e79636a58f9282e15)] - feat: make sure session id is global unique (#170) (fengmk2 <>) + +**fixes** + * [[`c2b4259`](http://github.com/koajs/session/commit/c2b4259ccef6095cad2f3ff51968b21cea993d13)] - fix: remove package-lock.json (fengmk2 <>) + +**others** + * [[`23ad871`](http://github.com/koajs/session/commit/23ad8718a9a392c0c563893a10b5ca9f6fd70ebe)] - deps: Fix security vulnerabilities from npm audit (#163) (Douglas Wade <>) + * [[`1600aab`](http://github.com/koajs/session/commit/1600aabdfa6a86973e3fab9f4064c3ed82b10604)] - test: changed "ctx.session is mockable" tests names to more appropriate (#158) (Vitaliy Zaytsev <>) + 5.10.1 / 2018-12-18 ================== diff --git a/package.json b/package.json index 9f701d4..45ba845 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.10.1", + "version": "5.11.0", "keywords": [ "koa", "middleware", From 39ca830a99ae7fcab2bd499a1f2a87de53fd1944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=9D=BE?= <1733458402@qq.com> Date: Fri, 17 May 2019 13:04:17 +0800 Subject: [PATCH 117/154] =?UTF-8?q?feat:=20add=20the=20parameter=20"ctx"?= =?UTF-8?q?=20to=20the=20function=20"genid"=20so=20can=20get=20the=20?= =?UTF-8?q?=E2=80=A6=20(#173)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/context.js | 2 +- test/contextstore.test.js | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index 410996b..b9d444b 100644 --- a/lib/context.js +++ b/lib/context.js @@ -202,7 +202,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || this.opts.genid(); + if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx); this.session = new Session(this, val); } diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 1d3058e..57cfb44 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -96,6 +96,26 @@ describe('Koa Session External Context Store', () => { }); }); + it('should pass sid to middleware', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.state.sid.indexOf('_suffix').should.greaterThan(1); + ctx.body = ''; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, (err, res) => { + if (err) return done(err); + cookie = res.header['set-cookie'].join(';'); + cookie.indexOf('_suffix').should.greaterThan(1); + done(); + }); + }); + it('should not Set-Cookie', done => { const app = App(); @@ -726,8 +746,10 @@ function App(options) { app.keys = [ 'a', 'b' ]; options = options || {}; options.ContextStore = ContextStore; - options.genid = () => { - return Date.now() + '_suffix'; + options.genid = ctx => { + const sid = Date.now() + '_suffix'; + ctx.state.sid = sid; + return sid; }; app.use(session(options, app)); return app; From 3d57a443c7e0050d4066c871bf8da2656cda99f1 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 17 May 2019 13:05:06 +0800 Subject: [PATCH 118/154] docs: add genid(ctx) in readme --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 337a177..c327c2e 100644 --- a/Readme.md +++ b/Readme.md @@ -113,9 +113,9 @@ console.log('listening on port 3000'); Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid`, which defaults to `uuid.v4()`. + The way of generating external session id is controlled by the `options.genid(ctx)`, which defaults to `uuid.v4()`. - If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. + If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid(ctx)` present. If your session store requires data or utilities from context, `opts.ContextStore` is also supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. From a4dcdc467a1e1672256207304a8e9ceafce97f70 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 17 May 2019 13:05:18 +0800 Subject: [PATCH 119/154] Release 5.12.0 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index e498d77..76fd3f6 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +5.12.0 / 2019-05-17 +================== + +**features** + * [[`39ca830`](http://github.com/koajs/session/commit/39ca830a99ae7fcab2bd499a1f2a87de53fd1944)] - feat: add the parameter "ctx" to the function "genid" so can get the … (#173) (松松 <<1733458402@qq.com>>) + +**others** + * [[`3d57a44`](http://github.com/koajs/session/commit/3d57a443c7e0050d4066c871bf8da2656cda99f1)] - docs: add genid(ctx) in readme (dead-horse <>) + 5.11.0 / 2019-04-29 ================== diff --git a/package.json b/package.json index 45ba845..4a38ac9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.11.0", + "version": "5.12.0", "keywords": [ "koa", "middleware", From 77968e3ff6fb5d4f4a36665474ccd992fed689ec Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 00:02:43 +0800 Subject: [PATCH 120/154] fix: ensure ctx.session always has value --- lib/context.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index b9d444b..ac3a546 100644 --- a/lib/context.js +++ b/lib/context.js @@ -34,7 +34,9 @@ class ContextSession { if (session === false) return null; // cookie session store - if (!this.store) this.initFromCookie(); + if (this.session) return this.session; + // create an empty session or init from cookie + this.store ? this.create() : this.initFromCookie(); return this.session; } From bdf51c3cce5d49d2255b9b4a499587a0026f7309 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 00:04:43 +0800 Subject: [PATCH 121/154] Release 5.12.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 76fd3f6..df11f48 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.12.1 / 2019-07-10 +================== + +**fixes** + * [[`77968e3`](http://github.com/koajs/session/commit/77968e3ff6fb5d4f4a36665474ccd992fed689ec)] - fix: ensure ctx.session always has value (dead-horse <>) + 5.12.0 / 2019-05-17 ================== diff --git a/package.json b/package.json index 4a38ac9..a1a072a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.0", + "version": "5.12.1", "keywords": [ "koa", "middleware", From c23bab4023b95c65be46b4eeaf089608ddaa738e Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 02:00:50 +0800 Subject: [PATCH 122/154] fix: remvoe unused code --- lib/context.js | 2 -- test/store.test.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/context.js b/lib/context.js index ac3a546..c0437b4 100644 --- a/lib/context.js +++ b/lib/context.js @@ -33,8 +33,6 @@ class ContextSession { // unset if (session === false) return null; - // cookie session store - if (this.session) return this.session; // create an empty session or init from cookie this.store ? this.create() : this.initFromCookie(); return this.session; diff --git a/test/store.test.js b/test/store.test.js index 7f9587c..f2f0765 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -769,6 +769,42 @@ describe('Koa Session External Store', () => { should.not.exist(res.headers['set-cookie']); }); }); + + describe('when get session before middleware', () => { + it('should return empty session', async () => { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + const options = {}; + options.store = store; + app.use(async (ctx, next) => { + // will not take effect + ctx.session.should.be.ok(); + ctx.session.foo = '123'; + await next(); + }); + app.use(session(options, app)); + app.use(async ctx => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + + let res = await request(app.callback()) + .get('/') + .expect({}); + + res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + await sleep(1200); + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + }); + }); }); function App(options) { From 10bb12246699101a0c87a2f3e2e09b1a79e10e33 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 02:02:02 +0800 Subject: [PATCH 123/154] Release 5.12.2 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index df11f48..679f32a 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.12.2 / 2019-07-10 +================== + +**fixes** + * [[`c23bab4`](http://github.com/koajs/session/commit/c23bab4023b95c65be46b4eeaf089608ddaa738e)] - fix: remvoe unused code (dead-horse <>) + 5.12.1 / 2019-07-10 ================== diff --git a/package.json b/package.json index a1a072a..1395265 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.1", + "version": "5.12.2", "keywords": [ "koa", "middleware", From 909d93fc6b74c6e29b0e83f555f1fc4002a6a108 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 23 Aug 2019 14:12:31 +1000 Subject: [PATCH 124/154] fix: correctly expire cookies for nullified sessions Changes: - On destroy, set 'maxAge' to be false - On destroy, set the 'expires' flag to be UNIXTIME epoch, which the 'Cookie' module relies on --- lib/context.js | 8 +++++++- lib/util.js | 2 ++ test/cookie.test.js | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index c0437b4..1bb41a8 100644 --- a/lib/context.js +++ b/lib/context.js @@ -4,6 +4,7 @@ const debug = require('debug')('koa-session:context'); const Session = require('./session'); const util = require('./util'); +const COOKIE_EXP_DATE = new Date(util.CookieDateEpoch); const ONE_DAY = 24 * 60 * 60 * 1000; class ContextSession { @@ -273,7 +274,12 @@ class ContextSession { */ async remove() { - const opts = this.opts; + // Override the default options so that we can properly expire the session cookies + const opts = Object.assign({}, this.opts, { + expires: COOKIE_EXP_DATE, + maxAge: false, + }); + const ctx = this.ctx; const key = opts.key; const externalKey = this.externalKey; diff --git a/lib/util.js b/lib/util.js index 759197c..b3a1a28 100644 --- a/lib/util.js +++ b/lib/util.js @@ -34,4 +34,6 @@ module.exports = { hash(sess) { return crc(JSON.stringify(sess)); }, + + CookieDateEpoch: 'Thu, 01 Jan 1970 00:00:00 GMT', }; diff --git a/test/cookie.test.js b/test/cookie.test.js index bf0e569..1f14d2a 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -249,6 +249,47 @@ describe('Koa Session Cookie', () => { }); }); + describe('after session set to null with signed cookie', () => { + it('should return expired cookies', done => { + const app = App({ + signed: true, + }); + + app.use(async function(ctx) { + ctx.session.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa:sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200, done); + }); + }); + + describe('after session set to null without signed cookie', () => { + it('should return expired cookies', done => { + const app = App({ + signed: false, + }); + + app.use(async function(ctx) { + ctx.session.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200, done); + }); + }); + describe('when get session after set to null', () => { it('should return null', done => { const app = App(); From f81d7130ff4fd44ad97bd0b807034bd46964db8a Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 23 Aug 2019 13:05:22 +0800 Subject: [PATCH 125/154] Release 5.12.3 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 679f32a..c1cba58 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.12.3 / 2019-08-23 +================== + +**fixes** + * [[`909d93f`](http://github.com/koajs/session/commit/909d93fc6b74c6e29b0e83f555f1fc4002a6a108)] - fix: correctly expire cookies for nullified sessions (Justin <>) + 5.12.2 / 2019-07-10 ================== diff --git a/package.json b/package.json index 1395265..afa488b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.2", + "version": "5.12.3", "keywords": [ "koa", "middleware", From cb09a09cfa4767610d7cc7282a0de2a3a651c6ae Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 1 Feb 2020 19:50:29 +0800 Subject: [PATCH 126/154] feat: support session cookie sameSite options (#193) --- Readme.md | 1 + index.js | 1 + test/cookie.test.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/Readme.md b/Readme.md index c327c2e..60028d8 100644 --- a/Readme.md +++ b/Readme.md @@ -59,6 +59,7 @@ const CONFIG = { signed: true, /** (boolean) signed or not (default true) */ rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ + sameSite: '', /** (string) session cookie sameSite options (default '', don't set it) */ }; app.use(session(CONFIG, app)); diff --git a/index.js b/index.js index 63dca92..d6bbcea 100644 --- a/index.js +++ b/index.js @@ -68,6 +68,7 @@ function formatOpts(opts) { // defaults if (opts.overwrite == null) opts.overwrite = true; if (opts.httpOnly == null) opts.httpOnly = true; + if (opts.sameSite == null) opts.sameSite = ''; if (opts.signed == null) opts.signed = true; if (opts.autoCommit == null) opts.autoCommit = true; diff --git a/test/cookie.test.js b/test/cookie.test.js index 1f14d2a..cd71825 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -94,6 +94,8 @@ describe('Koa Session Cookie', () => { .expect(204, (err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie']; + // samesite is not set + assert(!cookie.join(';').includes('samesite')); request(server) .get('/') .set('Cookie', cookie.join(';')) @@ -244,6 +246,11 @@ describe('Koa Session Cookie', () => { .get('/') .set('Cookie', cookie) .expect('Set-Cookie', /koa:sess/) + .expect(res => { + const cookie = res.headers['set-cookie']; + // samesite is not set + assert(!cookie.join(';').includes('samesite')); + }) .expect(200, done); }); }); @@ -694,6 +701,44 @@ describe('Koa Session Cookie', () => { }); }); + describe('options.sameSite', () => { + it('should return opt.sameSite=none', done => { + const app = App({ sameSite: 'none' }); + + app.use(async function(ctx) { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + request(app.listen()) + .get('/') + .expect(res => { + const cookie = res.headers['set-cookie'].join('|'); + assert(cookie.includes('path=/; samesite=none; httponly')); + }) + .expect('bar') + .expect(200, done); + }); + + it('should return opt.sameSite=lax', done => { + const app = App({ sameSite: 'lax' }); + + app.use(async function(ctx) { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + request(app.listen()) + .get('/') + .expect(res => { + const cookie = res.headers['set-cookie'].join('|'); + assert(cookie.includes('path=/; samesite=lax; httponly')); + }) + .expect('bar') + .expect(200, done); + }); + }); + describe('when valid and beforeSave set', () => { it('should ignore session when uid changed', done => { const app = new Koa(); From aabe6eba77f472895aa22511d5fa9d2e01f0bd02 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sat, 1 Feb 2020 19:51:52 +0800 Subject: [PATCH 127/154] Release 5.13.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c1cba58..171c66b 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.13.0 / 2020-02-01 +================== + +**features** + * [[`cb09a09`](http://github.com/koajs/session/commit/cb09a09cfa4767610d7cc7282a0de2a3a651c6ae)] - feat: support session cookie sameSite options (#193) (fengmk2 <>) + 5.12.3 / 2019-08-23 ================== diff --git a/package.json b/package.json index afa488b..79703e5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.3", + "version": "5.13.0", "keywords": [ "koa", "middleware", From ecd1f5edaa6ff1e77cc461d1107432b394ce21d2 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 1 Feb 2020 20:21:59 +0800 Subject: [PATCH 128/154] fix: don't set any value to sameSite by default (#194) --- Readme.md | 2 +- index.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 60028d8..844a32b 100644 --- a/Readme.md +++ b/Readme.md @@ -59,7 +59,7 @@ const CONFIG = { signed: true, /** (boolean) signed or not (default true) */ rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ - sameSite: '', /** (string) session cookie sameSite options (default '', don't set it) */ + sameSite: null, /** (string) session cookie sameSite options (default null, don't set it) */ }; app.use(session(CONFIG, app)); diff --git a/index.js b/index.js index d6bbcea..e4f3052 100644 --- a/index.js +++ b/index.js @@ -68,7 +68,8 @@ function formatOpts(opts) { // defaults if (opts.overwrite == null) opts.overwrite = true; if (opts.httpOnly == null) opts.httpOnly = true; - if (opts.sameSite == null) opts.sameSite = ''; + // delete null sameSite config + if (opts.sameSite == null) delete opts.sameSite; if (opts.signed == null) opts.signed = true; if (opts.autoCommit == null) opts.autoCommit = true; From a46d043820c71293aadddd0573b8e7f2fc7ff18d Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sat, 1 Feb 2020 20:22:24 +0800 Subject: [PATCH 129/154] Release 5.13.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 171c66b..37f76a6 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.13.1 / 2020-02-01 +================== + +**fixes** + * [[`ecd1f5e`](http://github.com/koajs/session/commit/ecd1f5edaa6ff1e77cc461d1107432b394ce21d2)] - fix: don't set any value to sameSite by default (#194) (fengmk2 <>) + 5.13.0 / 2020-02-01 ================== diff --git a/package.json b/package.json index 79703e5..3528393 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.13.0", + "version": "5.13.1", "keywords": [ "koa", "middleware", From 17b32801145af597f29c0bc367d460129c80d233 Mon Sep 17 00:00:00 2001 From: Dominic Egginton Date: Sat, 18 Apr 2020 14:11:18 +0100 Subject: [PATCH 130/154] add secure flag to readme exmaple (#196) --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 844a32b..80b60ba 100644 --- a/Readme.md +++ b/Readme.md @@ -59,6 +59,7 @@ const CONFIG = { signed: true, /** (boolean) signed or not (default true) */ rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ + secure: true, /** (boolean) secure cookie*/ sameSite: null, /** (string) session cookie sameSite options (default null, don't set it) */ }; From 23f7f52a9bd1e21094d7cfa42fc7b9178be546d4 Mon Sep 17 00:00:00 2001 From: Dominic Egginton Date: Fri, 24 Apr 2020 05:11:33 +0100 Subject: [PATCH 131/154] chore: disabled package-lock.json (#198) --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file From 7fca3deda6cb78524992adfd1f520a6e664d52b0 Mon Sep 17 00:00:00 2001 From: Zac Anger Date: Tue, 28 Apr 2020 16:15:54 +0000 Subject: [PATCH 132/154] [BREAKING CHANGE] fix: RFC6265 default cookie name (#197) --- History.md | 6 + Readme.md | 4 +- index.js | 4 +- package-lock.json | 2076 +++++++++++++++++++++++++++++++++++++ test/contextstore.test.js | 40 +- test/cookie.test.js | 40 +- test/store.test.js | 32 +- 7 files changed, 2142 insertions(+), 60 deletions(-) create mode 100644 package-lock.json diff --git a/History.md b/History.md index 37f76a6..40a77ce 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ +6.0.0 / 2020-04-26 +================== + +**fixes** + * [[`d34fc8e`](https://github.com/koajs/session/commit/d34fc8e0395bd3dc0c8cceda4374039a4d414060)] - fix: RFC6265 compliant default cookie name (#197) (zacanger <>) + * [BREAKING CHANGE]: Default cookie is now `koa.sess` rather than `koa:sess` 5.13.1 / 2020-02-01 ================== diff --git a/Readme.md b/Readme.md index 80b60ba..fb5664f 100644 --- a/Readme.md +++ b/Readme.md @@ -48,7 +48,7 @@ const app = new Koa(); app.keys = ['some secret hurr']; const CONFIG = { - key: 'koa:sess', /** (string) cookie key (default is koa:sess) */ + key: 'koa.sess', /** (string) cookie key (default is koa.sess) */ /** (number || 'session') maxAge in ms (default is 1 days) */ /** 'session' will result in a cookie that expires when session/browser is closed */ /** Warning: If a session cookie is stolen, this cookie will never expire */ @@ -84,7 +84,7 @@ console.log('listening on port 3000'); ### Options The cookie name is controlled by the `key` option, which defaults - to "koa:sess". All other options are passed to `ctx.cookies.get()` and + to "koa.sess". All other options are passed to `ctx.cookies.get()` and `ctx.cookies.set()` allowing you to control security, domain, path, and signing among other settings. diff --git a/index.js b/index.js index e4f3052..816a15a 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ const _CONTEXT_SESSION = Symbol('context#_contextSession'); /** * Initialize session middleware with `opts`: * - * - `key` session cookie name ["koa:sess"] + * - `key` session cookie name ["koa.sess"] * - all other options are passed as cookie options * * @param {Object} [opts] @@ -60,7 +60,7 @@ module.exports = function(opts, app) { function formatOpts(opts) { opts = opts || {}; // key - opts.key = opts.key || 'koa:sess'; + opts.key = opts.key || 'koa.sess'; // back-compat maxage if (!('maxAge' in opts)) opts.maxAge = opts.maxage; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d60aff9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2076 @@ +{ + "name": "koa-session", + "version": "5.13.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, + "brace-expansion": { + "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" + } + }, + "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 + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "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=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "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 + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dev": true, + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "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=" + }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "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" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "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=", + "dev": true + }, + "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 + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "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", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "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 + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", + "dev": true + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "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=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-config-egg": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", + "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "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=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true + }, + "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", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "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.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "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" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "is-class-hotfix": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", + "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", + "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-type-of": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", + "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", + "requires": { + "core-util-is": "^1.0.2", + "is-class-hotfix": "~0.0.6", + "isstream": "~0.1.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "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": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "requires": { + "tsscmp": "1.0.6" + } + }, + "ko-sleep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", + "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "koa": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.11.0.tgz", + "integrity": "sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "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.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dev": true, + "requires": { + "mime-db": "1.43.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": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mm": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/mm/-/mm-2.5.0.tgz", + "integrity": "sha512-ilm+lGEBNm7Cw45um9ax0tbApiNwQV3PY6Yk1ol+wtA8c98hHuJqTgmdKB6rYQJTUC2QrhBfoWwN+/766ZlrYA==", + "dev": true, + "requires": { + "is-type-of": "^1.0.0", + "ko-sleep": "^1.0.2", + "muk-prop": "^1.0.0", + "thenify": "^3.2.1" + } + }, + "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": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "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" + } + }, + "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" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "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" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "muk-prop": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", + "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "mz-modules": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", + "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", + "dev": true, + "requires": { + "glob": "^7.1.2", + "ko-sleep": "^1.0.3", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "rimraf": "^2.6.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "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" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "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=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pedding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", + "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", + "dev": true + }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", + "dev": true + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", + "dev": true + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "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 + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "should": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", + "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", + "dev": true, + "requires": { + "should-equal": "0.8.0", + "should-format": "0.3.2", + "should-type": "0.2.0" + } + }, + "should-equal": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", + "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-format": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", + "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-type": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", + "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "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=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "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": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "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.4.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", + "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", + "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.3" + } + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dev": true, + "requires": { + "random-bytes": "~1.0.0" + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true + } + } +} diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 57cfb44..cf71eb5 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -87,7 +87,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -107,7 +107,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -200,7 +200,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') .set('Cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -218,7 +218,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -253,7 +253,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -285,7 +285,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect({ foo: 'bar' }) .expect(200, done); }); @@ -302,7 +302,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('1') .expect(200, done); }); @@ -319,7 +319,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('true') .expect(200, done); }); @@ -336,7 +336,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('hello') .expect(200, done); }); @@ -371,7 +371,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(401, done); }); }); @@ -415,7 +415,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end(err => { if (err) return done(err); }) @@ -441,7 +441,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -463,7 +463,7 @@ describe('Koa Session External Context Store', () => { request(server) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect('hi 1') .end((err, res) => { if (err) return done(err); @@ -473,7 +473,7 @@ describe('Koa Session External Context Store', () => { request(server) .get('/') .set('cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect('hi 2') .end((err, res) => { if (err) return done(err); @@ -483,7 +483,7 @@ describe('Koa Session External Context Store', () => { request(server) .get('/') .set('cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect('hi 3') .end((err, res) => { if (err) return done(err); @@ -510,7 +510,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -539,7 +539,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -570,7 +570,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -655,7 +655,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-key') + .set('cookie', 'koa.sess=invalid-key') .expect('true') .expect(200, done); }); @@ -693,7 +693,7 @@ describe('Koa Session External Context Store', () => { should.not.exist(err); const data = res.body; const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') diff --git a/test/cookie.test.js b/test/cookie.test.js index cd71825..568d2b1 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -153,7 +153,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -245,7 +245,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') .set('Cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(res => { const cookie = res.headers['set-cookie']; // samesite is not set @@ -270,8 +270,8 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('Set-Cookie', /koa:sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa\.sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) .expect('true') .expect(200, done); }); @@ -291,7 +291,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) .expect('true') .expect(200, done); }); @@ -309,7 +309,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=;/) + .expect('Set-Cookie', /koa\.sess=;/) .expect('true') .expect(200, done); }); @@ -326,7 +326,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-session;') + .set('cookie', 'koa.sess=invalid-session;') .expect('true') .expect(200, done); }); @@ -347,8 +347,8 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-session;') - .expect('Set-Cookie', /koa:sess=;/) + .set('cookie', 'koa.sess=invalid-session;') + .expect('Set-Cookie', /koa\.sess=;/) .expect(500, done); }); }); @@ -390,7 +390,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect({ foo: 'bar' }) .expect(200, done); }); @@ -407,7 +407,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('1') .expect(200, done); }); @@ -424,7 +424,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('true') .expect(200, done); }); @@ -441,7 +441,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('hello') .expect(200, done); }); @@ -460,7 +460,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -495,7 +495,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -543,7 +543,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(401, done); }); }); @@ -566,7 +566,7 @@ describe('Koa Session Cookie', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -597,7 +597,7 @@ describe('Koa Session Cookie', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -691,7 +691,7 @@ describe('Koa Session Cookie', () => { .expect(200, (err, res) => { should.not.exist(err); const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') @@ -770,7 +770,7 @@ describe('Koa Session Cookie', () => { should.not.exist(err); const data = res.body; const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') diff --git a/test/store.test.js b/test/store.test.js index f2f0765..d1bbf93 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -90,7 +90,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -182,7 +182,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') .set('Cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -200,7 +200,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -235,7 +235,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -267,7 +267,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect({ foo: 'bar' }) .expect(200, done); }); @@ -284,7 +284,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('1') .expect(200, done); }); @@ -301,7 +301,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('true') .expect(200, done); }); @@ -318,7 +318,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('hello') .expect(200, done); }); @@ -353,7 +353,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(401, done); }); }); @@ -375,7 +375,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -401,7 +401,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -430,7 +430,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -467,7 +467,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -559,7 +559,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-key') + .set('cookie', 'koa.sess=invalid-key') .expect('true') .expect(200, done); }); @@ -604,7 +604,7 @@ describe('Koa Session External Store', () => { should.not.exist(err); const data = res.body; const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') @@ -710,7 +710,7 @@ describe('Koa Session External Store', () => { .expect(204, (err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie']; - cookie.join().should.match(/koa:sess=sess:/); + cookie.join().should.match(/koa\.sess=sess:/); request(server) .get('/') .set('Cookie', cookie.join(';')) From 6c9df069b0ee9da5410e0ff1e652c8c1a274f07f Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 29 Apr 2020 00:16:52 +0800 Subject: [PATCH 133/154] chore: remove package-lock --- .npmrc | 2 +- package-lock.json | 2076 --------------------------------------------- 2 files changed, 1 insertion(+), 2077 deletions(-) delete mode 100644 package-lock.json diff --git a/.npmrc b/.npmrc index 9cf9495..43c97e7 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -package-lock=false \ No newline at end of file +package-lock=false diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index d60aff9..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2076 +0,0 @@ -{ - "name": "koa-session", - "version": "5.13.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true, - "optional": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, - "benchmark": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", - "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", - "dev": true, - "requires": { - "lodash": "^4.17.4", - "platform": "^1.3.3" - } - }, - "brace-expansion": { - "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" - } - }, - "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 - }, - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-content-type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", - "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", - "dev": true, - "requires": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "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=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "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 - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "cookies": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", - "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", - "dev": true, - "requires": { - "depd": "~2.0.0", - "keygrip": "~1.1.0" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "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=" - }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "requires": { - "buffer": "^5.1.0" - } - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "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" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "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=", - "dev": true - }, - "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 - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "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", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "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 - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-inject": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", - "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", - "dev": true - }, - "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - }, - "dependencies": { - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - } - } - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "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=", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.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" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-config-egg": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", - "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dev": true, - "requires": { - "type": "^2.0.0" - }, - "dependencies": { - "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "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=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - } - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "dev": true - }, - "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", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "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.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "http-assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", - "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", - "dev": true, - "requires": { - "deep-equal": "~1.0.1", - "http-errors": "~1.7.2" - } - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "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" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "is-class-hotfix": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", - "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-generator-function": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", - "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", - "dev": true - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", - "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-type-of": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", - "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", - "requires": { - "core-util-is": "^1.0.2", - "is-class-hotfix": "~0.0.6", - "isstream": "~0.1.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "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": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "keygrip": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", - "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", - "dev": true, - "requires": { - "tsscmp": "1.0.6" - } - }, - "ko-sleep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", - "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "koa": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.11.0.tgz", - "integrity": "sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==", - "dev": true, - "requires": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.8.0", - "debug": "~3.1.0", - "delegates": "^1.0.0", - "depd": "^1.1.2", - "destroy": "^1.0.4", - "encodeurl": "^1.0.2", - "error-inject": "^1.0.0", - "escape-html": "^1.0.3", - "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", - "koa-compose": "^4.1.0", - "koa-convert": "^1.2.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", - "vary": "^1.1.2" - }, - "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" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", - "dev": true - }, - "koa-convert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", - "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", - "dev": true, - "requires": { - "co": "^4.6.0", - "koa-compose": "^3.0.0" - }, - "dependencies": { - "koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "dev": true, - "requires": { - "any-promise": "^1.1.0" - } - } - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "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.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, - "requires": { - "mime-db": "1.43.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": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mm": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/mm/-/mm-2.5.0.tgz", - "integrity": "sha512-ilm+lGEBNm7Cw45um9ax0tbApiNwQV3PY6Yk1ol+wtA8c98hHuJqTgmdKB6rYQJTUC2QrhBfoWwN+/766ZlrYA==", - "dev": true, - "requires": { - "is-type-of": "^1.0.0", - "ko-sleep": "^1.0.2", - "muk-prop": "^1.0.0", - "thenify": "^3.2.1" - } - }, - "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": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "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" - } - }, - "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" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "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" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "muk-prop": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", - "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "mz-modules": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", - "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", - "dev": true, - "requires": { - "glob": "^7.1.2", - "ko-sleep": "^1.0.3", - "mkdirp": "^0.5.1", - "pump": "^3.0.0", - "rimraf": "^2.6.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "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" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", - "dev": true - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "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=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "pedding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", - "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", - "dev": true - }, - "platform": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", - "dev": true - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "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" - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "^1.3.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "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 - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "should": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", - "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", - "dev": true, - "requires": { - "should-equal": "0.8.0", - "should-format": "0.3.2", - "should-type": "0.2.0" - } - }, - "should-equal": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", - "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-format": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", - "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-type": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", - "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "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=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "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": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "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.4.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", - "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", - "dev": true, - "requires": { - "methods": "^1.1.2", - "superagent": "^3.8.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tsscmp": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", - "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", - "dev": true - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", - "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.3" - } - }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dev": true, - "requires": { - "random-bytes": "~1.0.0" - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "ylru": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", - "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", - "dev": true - } - } -} From e630f7386f269d233f64e270c0eecd1eda91312b Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 29 Apr 2020 00:23:20 +0800 Subject: [PATCH 134/154] docs: add migrate from 5.x to 6.x --- Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Readme.md b/Readme.md index fb5664f..35bae2d 100644 --- a/Readme.md +++ b/Readme.md @@ -36,6 +36,10 @@ $ npm install koa-session ``` +## Notice + +6.x changed the default cookie key from `koa:sess` to `koa.sess` to ensure `set-cookie` value valid with HTTP spec.[see issue](https://github.com/koajs/session/issues/28). If you want to be compatible with the previous version, you can manually set `config.key` to `koa:sess`. + ## Example View counter example: From 136b9034c374b9a51f05973d79e1a8d9a2c83b90 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 29 Apr 2020 00:25:28 +0800 Subject: [PATCH 135/154] Release 6.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3528393..f05b363 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.13.1", + "version": "6.0.0", "keywords": [ "koa", "middleware", From f76559568bb7f6321cab3f44ae759521deca3dd1 Mon Sep 17 00:00:00 2001 From: Dominic Egginton Date: Tue, 28 Apr 2020 17:26:12 +0100 Subject: [PATCH 136/154] chore: Create LICENSE File (#195) --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eaf38ab --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2014-present Koajs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 32e352665f2adbcda34d1d990bb6c5d875c0b625 Mon Sep 17 00:00:00 2001 From: Ngorror Date: Thu, 8 Oct 2020 11:35:20 +0200 Subject: [PATCH 137/154] feat: add context to external store .get() and .set() options params (#201) --- Readme.md | 6 +-- lib/context.js | 5 ++- test/store_with_ctx.js | 23 +++++++++++ test/store_with_ctx.test.js | 81 +++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 test/store_with_ctx.js create mode 100644 test/store_with_ctx.test.js diff --git a/Readme.md b/Readme.md index 35bae2d..ee59469 100644 --- a/Readme.md +++ b/Readme.md @@ -111,9 +111,9 @@ console.log('listening on port 3000'); You can store the session content in external stores (Redis, MongoDB or other DBs) by passing `options.store` with three methods (these need to be async functions): - - `get(key, maxAge, { rolling })`: get session object by key - - `set(key, sess, maxAge, { rolling, changed })`: set session object for key, with a `maxAge` (in ms) - - `destroy(key)`: destroy session for key + - `get(key, maxAge, { rolling, ctx })`: get session object by key + - `set(key, sess, maxAge, { rolling, changed, ctx })`: set session object for key, with a `maxAge` (in ms) + - `destroy(key, {ctx})`: destroy session for key Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** diff --git a/lib/context.js b/lib/context.js index 1bb41a8..ccb8d5c 100644 --- a/lib/context.js +++ b/lib/context.js @@ -87,7 +87,7 @@ class ContextSession { return; } - const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling }); + const json = await this.store.get(externalKey, opts.maxAge, { ctx, rolling: opts.rolling }); if (!this.valid(json, externalKey)) { // create a new `externalKey` this.create(); @@ -284,7 +284,7 @@ class ContextSession { const key = opts.key; const externalKey = this.externalKey; - if (externalKey) await this.store.destroy(externalKey); + if (externalKey) await this.store.destroy(externalKey, { ctx }); ctx.cookies.set(key, '', opts); } @@ -320,6 +320,7 @@ class ContextSession { } await this.store.set(externalKey, json, maxAge, { changed, + ctx: this.ctx, rolling: opts.rolling, }); if (opts.externalKey) { diff --git a/test/store_with_ctx.js b/test/store_with_ctx.js new file mode 100644 index 0000000..2ebd234 --- /dev/null +++ b/test/store_with_ctx.js @@ -0,0 +1,23 @@ +'use strict'; + +const sessions = {}; + +module.exports = { + async get(key, maxAge, options) { + // check access to options.ctx + options.ctx.state.test = 'get'; + return sessions[key]; + }, + + async set(key, sess, maxAge, options) { + // check access to options.ctx + options.ctx.state.test = 'set'; + sessions[key] = sess; + }, + + async destroy(key, options) { + // check access to options.ctx + options.ctx.state.test = 'destroyed'; + sessions[key] = undefined; + }, +}; diff --git a/test/store_with_ctx.test.js b/test/store_with_ctx.test.js new file mode 100644 index 0000000..21fe43a --- /dev/null +++ b/test/store_with_ctx.test.js @@ -0,0 +1,81 @@ +'use strict'; + +const Koa = require('koa'); +const request = require('supertest'); +const session = require('..'); +const store = require('./store_with_ctx'); + +describe('Koa Session External Store methods can acceess Koa context', () => { + let cookie; + + describe('new session', () => { + describe('when not accessed', () => { + it('should not set ctx.state.test variable', async () => { + const app = App(); + + request(app.callback()) + .get('/') + .expect('undefined'); + }); + }); + + describe('when populated', () => { + it('should set ctx.state.test variable', done => { + const app = App(); + + app.use(async ctx => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + }); + + request(app.listen()) + .get('/set') + .expect(200, (err, res) => { + if (err) return done(err); + cookie = res.header['set-cookie'].join(';'); + res.text.should.equal('set'); + done(); + }); + }); + }); + + describe('when accessed', () => { + it('should access ctx.state.test variable', async () => { + const app = App(); + + request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('get'); + }); + }); + + describe('session destroyed', () => { + it('should access ctx.state.test variable', async () => { + const app = App(); + + app.use(async ctx => { + if (ctx.path === '/destroy') ctx.session = null; + }); + + request(app.callback()) + .get('/destroy') + .set('Cookie', cookie) + .expect('destroyed'); + }); + }); + }); +}); + +function App(options) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options = options || {}; + options.store = store; + app.use(async (ctx, next) => { + await next(); + ctx.body = ctx.state.test === undefined ? 'undefined' : ctx.state.test; + }); + + app.use(session(options, app)); + return app; +} From bbd9d238778e4bb8f686f20cd3454a6a9e8115bb Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 8 Oct 2020 17:40:06 +0800 Subject: [PATCH 138/154] Release 6.1.0 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 40a77ce..1744063 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,12 @@ + +6.1.0 / 2020-10-08 +================== + +**features** + * [[`32e3526`](http://github.com/koajs/session/commit/32e352665f2adbcda34d1d990bb6c5d875c0b625)] - feat: add context to external store .get() and .set() options params (#201) (Ngorror <>) + +**others** + * [[`f765595`](http://github.com/koajs/session/commit/f76559568bb7f6321cab3f44ae759521deca3dd1)] - chore: Create LICENSE File (#195) (Dominic Egginton <>) 6.0.0 / 2020-04-26 ================== diff --git a/package.json b/package.json index f05b363..81a3fa6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.0.0", + "version": "6.1.0", "keywords": [ "koa", "middleware", From 7cde341db1691ee6885eb1b11ff3a3a3632fd5ce Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Tue, 30 Mar 2021 18:27:14 +0800 Subject: [PATCH 139/154] feat: add session.externalKey (#207) --- Readme.md | 4 ++++ lib/context.js | 2 +- lib/session.js | 15 +++++++++++++-- test/contextstore.test.js | 4 +++- test/cookie.test.js | 4 +++- test/externalkey.test.js | 3 +++ test/store.test.js | 4 +++- 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index ee59469..b5f0561 100644 --- a/Readme.md +++ b/Readme.md @@ -160,6 +160,10 @@ if (this.session.isNew) { Set cookie's maxAge. +### Session#externalKey + + Get session external key, only exist when external session store present. + ### Session#save() Save this session no matter whether it is populated. diff --git a/lib/context.js b/lib/context.js index ccb8d5c..95681a0 100644 --- a/lib/context.js +++ b/lib/context.js @@ -204,7 +204,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx); - this.session = new Session(this, val); + this.session = new Session(this, val, this.externalKey); } /** diff --git a/lib/session.js b/lib/session.js index 6961e3b..c9019af 100644 --- a/lib/session.js +++ b/lib/session.js @@ -4,6 +4,8 @@ * Session model. */ +const inspect = Symbol.for('nodejs.util.inspect.custom'); + class Session { /** * Session constructor @@ -12,9 +14,10 @@ class Session { * @api private */ - constructor(sessionContext, obj) { + constructor(sessionContext, obj, externalKey) { this._sessCtx = sessionContext; this._ctx = sessionContext.ctx; + this._externalKey = externalKey; if (!obj) { this.isNew = true; } else { @@ -52,7 +55,7 @@ class Session { * @api public */ - inspect() { + [inspect]() { return this.toJSON(); } @@ -103,6 +106,14 @@ class Session { this._requireSave = true; } + /** + * get session external key + * only exist if opts.store present + */ + get externalKey() { + return this._externalKey; + } + /** * save this session no matter whether it is populated * diff --git a/test/contextstore.test.js b/test/contextstore.test.js index cf71eb5..b31fada 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -7,6 +7,8 @@ const mm = require('mm'); const session = require('..'); const ContextStore = require('./context_store'); +const inspect = Symbol.for('nodejs.util.inspect.custom'); + describe('Koa Session External Context Store', () => { let cookie; @@ -280,7 +282,7 @@ describe('Koa Session External Context Store', () => { app.use(async function(ctx) { ctx.session.foo = 'bar'; - ctx.body = ctx.session.inspect(); + ctx.body = ctx.session[inspect](); }); request(app.listen()) diff --git a/test/cookie.test.js b/test/cookie.test.js index 568d2b1..f3fe914 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -6,6 +6,8 @@ const request = require('supertest'); const should = require('should'); const session = require('..'); +const inspect = Symbol.for('nodejs.util.inspect.custom'); + describe('Koa Session Cookie', () => { let cookie; @@ -385,7 +387,7 @@ describe('Koa Session Cookie', () => { app.use(async function(ctx) { ctx.session.foo = 'bar'; - ctx.body = ctx.session.inspect(); + ctx.body = ctx.session[inspect](); }); request(app.listen()) diff --git a/test/externalkey.test.js b/test/externalkey.test.js index c3625cb..3dfd8c1 100644 --- a/test/externalkey.test.js +++ b/test/externalkey.test.js @@ -5,6 +5,7 @@ const assert = require('assert'); const session = require('..'); const store = require('./store'); const TOKEN_KEY = 'User-Token'; + describe('Koa Session External Key', () => { describe('when the external key set/get is invalid', () => { it('should throw a error', () => { @@ -24,8 +25,10 @@ describe('Koa Session External Key', () => { if (ctx.method === 'POST') { ctx.session.string = ';'; ctx.status = 204; + assert(ctx.session.externalKey); } else { ctx.body = ctx.session.string; + assert(ctx.session.externalKey === ctx.get(TOKEN_KEY)); } }); const server = app.listen(); diff --git a/test/store.test.js b/test/store.test.js index d1bbf93..caf5375 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -10,6 +10,8 @@ const pedding = require('pedding'); const assert = require('assert'); const sleep = require('mz-modules/sleep'); +const inspect = Symbol.for('nodejs.util.inspect.custom'); + describe('Koa Session External Store', () => { let cookie; @@ -262,7 +264,7 @@ describe('Koa Session External Store', () => { app.use(async function(ctx) { ctx.session.foo = 'bar'; - ctx.body = ctx.session.inspect(); + ctx.body = ctx.session[inspect](); }); request(app.listen()) From 07ca4d7653e864841326117c90e435a90633a0d7 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 30 Mar 2021 18:27:33 +0800 Subject: [PATCH 140/154] Release 6.2.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 1744063..a5b4e51 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +6.2.0 / 2021-03-30 +================== + +**features** + * [[`7cde341`](http://github.com/koajs/session/commit/7cde341db1691ee6885eb1b11ff3a3a3632fd5ce)] - feat: add session.externalKey (#207) (Yiyu He <>) + 6.1.0 / 2020-10-08 ================== diff --git a/package.json b/package.json index 81a3fa6..6feecb9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.1.0", + "version": "6.2.0", "keywords": [ "koa", "middleware", From 878669ee2c734e3d9902d83f57e21d2113178b79 Mon Sep 17 00:00:00 2001 From: zhennann Date: Tue, 3 Jan 2023 18:00:54 +0800 Subject: [PATCH 141/154] feat: update uuid to v8 (#218) --- index.js | 6 +++--- package.json | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 816a15a..4d149aa 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); -const uuid = require('uuid/v4'); +const uuid = require('uuid'); const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); @@ -105,8 +105,8 @@ function formatOpts(opts) { } if (!opts.genid) { - if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid()}`; - else opts.genid = uuid; + if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid.v4()}`; + else opts.genid = uuid.v4; } return opts; diff --git a/package.json b/package.json index 6feecb9..87249f9 100644 --- a/package.json +++ b/package.json @@ -23,16 +23,16 @@ "mocha": "^5.2.0", "mz-modules": "^2.0.0", "pedding": "^1.1.0", - "uid-safe": "^2.1.3", "should": "8", - "supertest": "^3.3.0" + "supertest": "^3.3.0", + "uid-safe": "^2.1.3" }, "license": "MIT", "dependencies": { - "crc": "^3.4.4", - "debug": "^3.1.0", - "is-type-of": "^1.0.0", - "uuid": "^3.3.2" + "crc": "^4.0.0", + "debug": "^4.3.3", + "is-type-of": "^1.2.1", + "uuid": "^8.3.2" }, "engines": { "node": ">=7.6" From df2d28ffb177272739964eb5a503f93db870aa28 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:14:21 +0800 Subject: [PATCH 142/154] test: run ci on GitHub Action (#222) --- .github/workflows/nodejs.yml | 27 +++++++++++++++++++++++++++ .travis.yml | 9 --------- Readme.md | 21 ++------------------- package.json | 4 ++-- 4 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/nodejs.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..a46f3e4 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,27 @@ +name: Node.js CI + +on: + push: + branch: master + pull_request: + branch: master + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [8, 10, 12, 14, 16, 18] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run lint + - run: npm run ci + - run: npx codecov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 80b9e4a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false -language: node_js -node_js: - - '7' - - '8' - - '10' - - '12' -script: 'npm run test-travis' -after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' diff --git a/Readme.md b/Readme.md index b5f0561..03275e1 100644 --- a/Readme.md +++ b/Readme.md @@ -1,34 +1,17 @@ # koa-session [![NPM version][npm-image]][npm-url] -[![build status][travis-image]][travis-url] -[![Test coverage][coveralls-image]][coveralls-url] -[![Gittip][gittip-image]][gittip-url] -[![David deps][david-image]][david-url] -[![iojs version][iojs-image]][iojs-url] -[![node version][node-image]][node-url] +[![Node.js CI](https://github.com/koajs/session/actions/workflows/nodejs.yml/badge.svg)](https://github.com/koajs/session/actions/workflows/nodejs.yml) [![npm download][download-image]][download-url] [npm-image]: https://img.shields.io/npm/v/koa-session.svg?style=flat-square [npm-url]: https://npmjs.org/package/koa-session -[travis-image]: https://img.shields.io/travis/koajs/session.svg?style=flat-square -[travis-url]: https://travis-ci.org/koajs/session -[coveralls-image]: https://img.shields.io/coveralls/koajs/session.svg?style=flat-square -[coveralls-url]: https://coveralls.io/r/koajs/session?branch=master -[gittip-image]: https://img.shields.io/gittip/fengmk2.svg?style=flat-square -[gittip-url]: https://www.gittip.com/fengmk2/ -[david-image]: https://img.shields.io/david/koajs/session.svg?style=flat-square -[david-url]: https://david-dm.org/koajs/session -[iojs-image]: https://img.shields.io/badge/io.js-%3E=_1.0-yellow.svg?style=flat-square -[iojs-url]: http://iojs.org/ -[node-image]: https://img.shields.io/badge/node.js-%3E=_7.6-green.svg?style=flat-square -[node-url]: http://nodejs.org/download/ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. - *Requires Node 7.6 or greater for async/await support* + *Requires Node 8.0.0 or greater for async/await support* ## Installation diff --git a/package.json b/package.json index 87249f9..3797976 100644 --- a/package.json +++ b/package.json @@ -35,12 +35,12 @@ "uuid": "^8.3.2" }, "engines": { - "node": ">=7.6" + "node": ">=8.0.0" }, "scripts": { "test": "npm run lint && NODE_ENV=test mocha --exit --require should --reporter spec test/*.test.js", "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --exit --require should test/*.test.js", - "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", + "ci": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", "lint": "eslint lib test index.js" } } From 75f5706cc540e6573707ad1fb5e104dac98f44a4 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:15:23 +0800 Subject: [PATCH 143/154] Release 6.3.0 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index a5b4e51..c06a9c0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +6.3.0 / 2023-01-03 +================== + +**features** + * [[`878669e`](http://github.com/koajs/session/commit/878669ee2c734e3d9902d83f57e21d2113178b79)] - feat: update uuid to v8 (#218) (zhennann <>) + +**others** + * [[`df2d28f`](http://github.com/koajs/session/commit/df2d28ffb177272739964eb5a503f93db870aa28)] - test: run ci on GitHub Action (#222) (fengmk2 <>) + 6.2.0 / 2021-03-30 ================== diff --git a/package.json b/package.json index 3797976..abf0ef6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.2.0", + "version": "6.3.0", "keywords": [ "koa", "middleware", From e2de39e6acaf5eef9c8660cbd864ecccaa2b60d0 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:32:35 +0800 Subject: [PATCH 144/154] fix: keep crc v3 (#223) avoid peerDependencies WARNING https://github.com/koajs/session/pull/218/files#r1060445443 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index abf0ef6..fe91994 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "license": "MIT", "dependencies": { - "crc": "^4.0.0", + "crc": "^3.8.0", "debug": "^4.3.3", "is-type-of": "^1.2.1", "uuid": "^8.3.2" From 6b7043156471d0a7fe91666fe909baec21743590 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:33:05 +0800 Subject: [PATCH 145/154] Release 6.3.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c06a9c0..00f139c 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +6.3.1 / 2023-01-03 +================== + +**fixes** + * [[`e2de39e`](http://github.com/koajs/session/commit/e2de39e6acaf5eef9c8660cbd864ecccaa2b60d0)] - fix: keep crc v3 (#223) (fengmk2 <>) + 6.3.0 / 2023-01-03 ================== diff --git a/package.json b/package.json index fe91994..747493e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.3.0", + "version": "6.3.1", "keywords": [ "koa", "middleware", From 4cd3bef4fc1900b847d2e133e3b2599a711f1aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Sat, 4 Feb 2023 15:53:32 +0100 Subject: [PATCH 146/154] feat: Add Session.regenerate() method (#221) And make both save() and regenerate() compatible with koa-passport closes https://github.com/koajs/session/pull/74 --- lib/context.js | 12 +++++++----- lib/session.js | 32 ++++++++++++++++++++++++++++---- test/cookie.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 9 deletions(-) diff --git a/lib/context.js b/lib/context.js index 95681a0..d3efbec 100644 --- a/lib/context.js +++ b/lib/context.js @@ -213,7 +213,7 @@ class ContextSession { * @api public */ - async commit() { + async commit({ save = false, regenerate = false } = {}) { const session = this.session; const opts = this.opts; const ctx = this.ctx; @@ -226,8 +226,13 @@ class ContextSession { await this.remove(); return; } + if (regenerate) { + await this.remove(); + if (this.store) this.externalKey = opts.genid && opts.genid(ctx); + } - const reason = this._shouldSaveSession(); + // force save session when `session._requireSave` set + const reason = save || regenerate || session._requireSave ? 'force' : this._shouldSaveSession(); debug('should save session: %s', reason); if (!reason) return; @@ -243,9 +248,6 @@ class ContextSession { const prevHash = this.prevHash; const session = this.session; - // force save session when `session._requireSave` set - if (session._requireSave) return 'force'; - // do nothing if new and not populated const json = session.toJSON(); if (!prevHash && !Object.keys(json).length) return ''; diff --git a/lib/session.js b/lib/session.js index c9019af..202acc1 100644 --- a/lib/session.js +++ b/lib/session.js @@ -117,11 +117,23 @@ class Session { /** * save this session no matter whether it is populated * + * @param {Function} callback the optional function to call after saving the session * @api public */ - save() { - this._requireSave = true; + save(callback) { + return this.commit({ save: true }, callback); + } + + /** + * regenerate this session + * + * @param {Function} callback the optional function to call after regenerating the session + * @api public + */ + + regenerate(callback) { + return this.commit({ regenerate: true }, callback); } /** @@ -130,10 +142,22 @@ class Session { * @api public */ - async manuallyCommit() { - await this._sessCtx.commit(); + manuallyCommit() { + return this.commit(); } + commit(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + const promise = this._sessCtx.commit(options); + if (callback) { + promise.then(() => callback(), callback); + } else { + return promise; + } + } } module.exports = Session; diff --git a/test/cookie.test.js b/test/cookie.test.js index f3fe914..69b0683 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -674,6 +674,48 @@ describe('Koa Session Cookie', () => { }); }); + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', done => { + const app = new App(); + const message = 'hi'; + app.use(async function(ctx, next) { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async function(ctx, next) { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session.regenerate(); + } + await next(); + }); + + app.use(async function(ctx) { + ctx.session.message.should.equal(message); + ctx.body = ''; + }); + let koaSession = null; + request(app.callback()) + .get('/') + .expect(200, (err, res) => { + should.not.exist(err); + koaSession = res.headers['set-cookie'][0]; + koaSession.should.containEql('koa.sess='); + request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200, (err, res) => { + should.not.exist(err); + const cookies = res.headers['set-cookie'][0]; + cookies.should.containEql('koa.sess='); + cookies.should.not.equal(koaSession); + done(); + }); + }); + }); + }); + describe('when get session before enter session middleware', () => { it('should work', done => { const app = new Koa(); diff --git a/test/store.test.js b/test/store.test.js index caf5375..be3a5fe 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -544,6 +544,48 @@ describe('Koa Session External Store', () => { }); }); + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', done => { + const app = new App(); + const message = 'hi'; + app.use(async function(ctx, next) { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async function(ctx, next) { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session.regenerate(); + } + await next(); + }); + + app.use(async function(ctx) { + ctx.session.message.should.equal(message); + ctx.body = ''; + }); + let koaSession = null; + request(app.callback()) + .get('/') + .expect(200, (err, res) => { + should.not.exist(err); + koaSession = res.headers['set-cookie'][0]; + koaSession.should.containEql('koa.sess='); + request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200, (err, res) => { + should.not.exist(err); + const cookies = res.headers['set-cookie'][0]; + cookies.should.containEql('koa.sess='); + cookies.should.not.equal(koaSession); + done(); + }); + }); + }); + }); + describe('when store return empty', () => { it('should create new Session', done => { done = pedding(done, 2); From 98630aaf6587b199969490f8503641554aeee917 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 4 Feb 2023 22:56:54 +0800 Subject: [PATCH 147/154] Release 6.4.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 00f139c..1e86323 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +6.4.0 / 2023-02-04 +================== + +**features** + * [[`4cd3bef`](http://github.com/koajs/session/commit/4cd3bef4fc1900b847d2e133e3b2599a711f1aea)] - feat: Add Session.regenerate() method (#221) (Jürg Lehni <>) + 6.3.1 / 2023-01-03 ================== diff --git a/package.json b/package.json index 747493e..a61dc55 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.3.1", + "version": "6.4.0", "keywords": [ "koa", "middleware", From 575864c6ae7504da15e72e8112a99757f3eee188 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 19 Jan 2025 15:31:25 +0800 Subject: [PATCH 148/154] feat: support cjs and esm both by tshy (#228) BREAKING CHANGE: drop Node.js < 18.19.0 support --- .eslintrc | 8 +- .github/workflows/nodejs.yml | 32 +- .github/workflows/release.yml | 13 + .gitignore | 3 + History.md => CHANGELOG.md | 0 Readme.md | 80 +- example.cjs | 20 + example.js | 18 - index.js | 151 --- lib/session.js | 163 --- lib/util.js | 39 - package.json | 83 +- lib/context.js => src/context.ts | 212 ++-- src/index.ts | 305 ++++++ src/session.ts | 139 +++ src/util.ts | 26 + test/context_store.js | 25 - test/context_store.ts | 22 + test/contextstore.test.js | 758 -------------- test/contextstore.test.ts | 677 +++++++++++++ test/cookie.test.js | 948 ------------------ test/cookie.test.ts | 900 +++++++++++++++++ test/externalkey.test.js | 59 -- test/externalkey.test.ts | 60 ++ test/genid_bench.js | 45 - test/store.js | 17 - test/store.test.js | 861 ---------------- test/store.test.ts | 800 +++++++++++++++ test/store.ts | 15 + ...ith_ctx.test.js => store_with_ctx.test.ts} | 65 +- test/{store_with_ctx.js => store_with_ctx.ts} | 12 +- tsconfig.json | 10 + 32 files changed, 3258 insertions(+), 3308 deletions(-) create mode 100644 .github/workflows/release.yml rename History.md => CHANGELOG.md (100%) create mode 100644 example.cjs delete mode 100644 example.js delete mode 100644 index.js delete mode 100644 lib/session.js delete mode 100644 lib/util.js rename lib/context.js => src/context.ts (56%) create mode 100644 src/index.ts create mode 100644 src/session.ts create mode 100644 src/util.ts delete mode 100644 test/context_store.js create mode 100644 test/context_store.ts delete mode 100644 test/contextstore.test.js create mode 100644 test/contextstore.test.ts delete mode 100644 test/cookie.test.js create mode 100644 test/cookie.test.ts delete mode 100644 test/externalkey.test.js create mode 100644 test/externalkey.test.ts delete mode 100644 test/genid_bench.js delete mode 100644 test/store.js delete mode 100644 test/store.test.js create mode 100644 test/store.test.ts create mode 100644 test/store.ts rename test/{store_with_ctx.test.js => store_with_ctx.test.ts} (59%) rename test/{store_with_ctx.js => store_with_ctx.ts} (55%) create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc index 7364c13..9bcdb46 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { - "extends": "eslint-config-egg", - "parserOptions": { - "ecmaVersion": 2017 - } + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index a46f3e4..c70132d 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,27 +1,17 @@ -name: Node.js CI +name: CI on: push: - branch: master + branches: [ master ] pull_request: - branch: master + branches: [ master ] jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [8, 10, 12, 14, 16, 18] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm run lint - - run: npm run ci - - run: npx codecov + Job: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-test.yml@master + with: + os: 'ubuntu-latest, macos-latest, windows-latest' + version: '18.19.0, 18, 20, 22' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..035a626 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,13 @@ +name: Release + +on: + push: + branches: [ master ] + +jobs: + release: + name: Node.js + uses: koajs/github-actions/.github/workflows/node-release.yml@master + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} diff --git a/.gitignore b/.gitignore index 659fedb..388e6c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules coverage *.log +.tshy* +.eslintcache +dist diff --git a/History.md b/CHANGELOG.md similarity index 100% rename from History.md rename to CHANGELOG.md diff --git a/Readme.md b/Readme.md index 03275e1..34ee099 100644 --- a/Readme.md +++ b/Readme.md @@ -2,34 +2,45 @@ [![NPM version][npm-image]][npm-url] [![Node.js CI](https://github.com/koajs/session/actions/workflows/nodejs.yml/badge.svg)](https://github.com/koajs/session/actions/workflows/nodejs.yml) +[![Test coverage][codecov-image]][codecov-url] +[![Known Vulnerabilities][snyk-image]][snyk-url] [![npm download][download-image]][download-url] +[![Node.js Version](https://img.shields.io/node/v/koajs/session.svg?style=flat)](https://nodejs.org/en/download/) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) [npm-image]: https://img.shields.io/npm/v/koa-session.svg?style=flat-square [npm-url]: https://npmjs.org/package/koa-session +[codecov-image]: https://codecov.io/gh/koajs/session/branch/master/graph/badge.svg +[codecov-url]: https://codecov.io/gh/koajs/session +[snyk-image]: https://snyk.io/test/npm/koa-session/badge.svg?style=flat-square +[snyk-url]: https://snyk.io/test/npm/koa-session [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. - - *Requires Node 8.0.0 or greater for async/await support* +Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. ## Installation -```js -$ npm install koa-session +```bash +npm install koa-session ``` ## Notice -6.x changed the default cookie key from `koa:sess` to `koa.sess` to ensure `set-cookie` value valid with HTTP spec.[see issue](https://github.com/koajs/session/issues/28). If you want to be compatible with the previous version, you can manually set `config.key` to `koa:sess`. +7.x has a breaking change: drop Node.js < 18.19.0 support. And it support CommonJS and ESM both. + +6.x changed the default cookie key from `koa:sess` to `koa.sess` to ensure `set-cookie` value valid with HTTP spec. +[See issue](https://github.com/koajs/session/issues/28). +If you want to be compatible with the previous version, you can manually set `config.key` to `koa:sess`. ## Example - View counter example: +View counter example: ```js -const session = require('koa-session'); -const Koa = require('koa'); +import Koa from 'koa'; +import session from 'koa-session'; + const app = new Koa(); app.keys = ['some secret hurr']; @@ -70,38 +81,35 @@ console.log('listening on port 3000'); ### Options - The cookie name is controlled by the `key` option, which defaults - to "koa.sess". All other options are passed to `ctx.cookies.get()` and - `ctx.cookies.set()` allowing you to control security, domain, path, - and signing among other settings. +The cookie name is controlled by the `key` option, which defaults +to "koa.sess". All other options are passed to `ctx.cookies.get()` and +`ctx.cookies.set()` allowing you to control security, domain, path, +and signing among other settings. #### Custom `encode/decode` Support - Use `options.encode` and `options.decode` to customize your own encode/decode methods. +Use `options.encode` and `options.decode` to customize your own encode/decode methods. ### Hooks - - `valid()`: valid session value before use it - - `beforeSave()`: hook before save session +- `valid()`: valid session value before use it +- `beforeSave()`: hook before save session ### External Session Stores - The session is stored in a cookie by default, but it has some disadvantages: - - - Session is stored on client side unencrypted - - [Browser cookies always have length limits](http://browsercookielimits.squawky.net/) +The session is stored in a cookie by default, but it has some disadvantages: +- Session is stored on client side unencrypted +- [Browser cookies always have length limits](http://browsercookielimits.squawky.net/) You can store the session content in external stores (Redis, MongoDB or other DBs) by passing `options.store` with three methods (these need to be async functions): - - `get(key, maxAge, { rolling, ctx })`: get session object by key - - `set(key, sess, maxAge, { rolling, changed, ctx })`: set session object for key, with a `maxAge` (in ms) - - `destroy(key, {ctx})`: destroy session for key - +- `get(key, maxAge, { rolling, ctx })`: get session object by key +- `set(key, sess, maxAge, { rolling, changed, ctx })`: set session object for key, with a `maxAge` (in ms) +- `destroy(key, {ctx})`: destroy session for key Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid(ctx)`, which defaults to `uuid.v4()`. If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid(ctx)` present. @@ -125,7 +133,7 @@ External key is used the cookie by default, but you can use `options.externalKey ### Session#isNew - Returns __true__ if the session is new. +Returns **true** if the session is new. ```js if (this.session.isNew) { @@ -137,27 +145,27 @@ if (this.session.isNew) { ### Session#maxAge - Get cookie's maxAge. +Get cookie's maxAge. ### Session#maxAge= - Set cookie's maxAge. +Set cookie's maxAge. ### Session#externalKey - Get session external key, only exist when external session store present. +Get session external key, only exist when external session store present. ### Session#save() - Save this session no matter whether it is populated. +Save this session no matter whether it is populated. ### Session#manuallyCommit() - Session headers are auto committed by default. Use this if `autoCommit` is set to `false`. +Session headers are auto committed by default. Use this if `autoCommit` is set to `false`. ### Destroying a session - To destroy a session simply set it to `null`: +To destroy a session simply set it to `null`: ```js this.session = null; @@ -165,4 +173,10 @@ this.session = null; ## License - MIT +[MIT](LICENSE) + +## Contributors + +[![Contributors](https://contrib.rocks/image?repo=koajs/session)](https://github.com/koajs/session/graphs/contributors) + +Made with [contributors-img](https://contrib.rocks). diff --git a/example.cjs b/example.cjs new file mode 100644 index 0000000..f238ff5 --- /dev/null +++ b/example.cjs @@ -0,0 +1,20 @@ + +const Koa = require('koa'); +const { createSession } = require('./'); + +const app = new Koa(); + +app.keys = [ 'some secret hurr' ]; + +app.use(createSession(app)); + +app.use(async (ctx, next) => { + if (ctx.path === '/favicon.ico') return next(); + + let n = ctx.session.views || 0; + ctx.session.views = ++n; + ctx.body = n + ' views'; +}); + +app.listen(3000); +console.log('listening on port http://localhost:3000'); diff --git a/example.js b/example.js deleted file mode 100644 index ea47cce..0000000 --- a/example.js +++ /dev/null @@ -1,18 +0,0 @@ - -var session = require('./'); -var Koa = require('koa'); -var app = new Koa(); - -app.keys = ['some secret hurr']; - -app.use(session(app)); - -app.use(function* (next){ - if ('/favicon.ico' == this.path) return; - var n = this.session.views || 0; - this.session.views = ++n; - this.body = n + ' views'; -}); - -app.listen(3000); -console.log('listening on port 3000'); diff --git a/index.js b/index.js deleted file mode 100644 index 4d149aa..0000000 --- a/index.js +++ /dev/null @@ -1,151 +0,0 @@ -'use strict'; - -const debug = require('debug')('koa-session'); -const ContextSession = require('./lib/context'); -const util = require('./lib/util'); -const assert = require('assert'); -const uuid = require('uuid'); -const is = require('is-type-of'); - -const CONTEXT_SESSION = Symbol('context#contextSession'); -const _CONTEXT_SESSION = Symbol('context#_contextSession'); - -/** - * Initialize session middleware with `opts`: - * - * - `key` session cookie name ["koa.sess"] - * - all other options are passed as cookie options - * - * @param {Object} [opts] - * @param {Application} app, koa application instance - * @api public - */ - -module.exports = function(opts, app) { - // session(app[, opts]) - if (opts && typeof opts.use === 'function') { - [ app, opts ] = [ opts, app ]; - } - // app required - if (!app || typeof app.use !== 'function') { - throw new TypeError('app instance required: `session(opts, app)`'); - } - - opts = formatOpts(opts); - extendContext(app.context, opts); - - return async function session(ctx, next) { - const sess = ctx[CONTEXT_SESSION]; - if (sess.store) await sess.initFromExternal(); - try { - await next(); - } catch (err) { - throw err; - } finally { - if (opts.autoCommit) { - await sess.commit(); - } - } - }; -}; - -/** - * format and check session options - * @param {Object} opts session options - * @return {Object} new session options - * - * @api private - */ - -function formatOpts(opts) { - opts = opts || {}; - // key - opts.key = opts.key || 'koa.sess'; - - // back-compat maxage - if (!('maxAge' in opts)) opts.maxAge = opts.maxage; - - // defaults - if (opts.overwrite == null) opts.overwrite = true; - if (opts.httpOnly == null) opts.httpOnly = true; - // delete null sameSite config - if (opts.sameSite == null) delete opts.sameSite; - if (opts.signed == null) opts.signed = true; - if (opts.autoCommit == null) opts.autoCommit = true; - - debug('session options %j', opts); - - // setup encoding/decoding - if (typeof opts.encode !== 'function') { - opts.encode = util.encode; - } - if (typeof opts.decode !== 'function') { - opts.decode = util.decode; - } - - const store = opts.store; - if (store) { - assert(is.function(store.get), 'store.get must be function'); - assert(is.function(store.set), 'store.set must be function'); - assert(is.function(store.destroy), 'store.destroy must be function'); - } - - const externalKey = opts.externalKey; - if (externalKey) { - assert(is.function(externalKey.get), 'externalKey.get must be function'); - assert(is.function(externalKey.set), 'externalKey.set must be function'); - } - - const ContextStore = opts.ContextStore; - if (ContextStore) { - assert(is.class(ContextStore), 'ContextStore must be a class'); - assert(is.function(ContextStore.prototype.get), 'ContextStore.prototype.get must be function'); - assert(is.function(ContextStore.prototype.set), 'ContextStore.prototype.set must be function'); - assert(is.function(ContextStore.prototype.destroy), 'ContextStore.prototype.destroy must be function'); - } - - if (!opts.genid) { - if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid.v4()}`; - else opts.genid = uuid.v4; - } - - return opts; -} - -/** - * extend context prototype, add session properties - * - * @param {Object} context koa's context prototype - * @param {Object} opts session options - * - * @api private - */ - -function extendContext(context, opts) { - if (context.hasOwnProperty(CONTEXT_SESSION)) { - return; - } - Object.defineProperties(context, { - [CONTEXT_SESSION]: { - get() { - if (this[_CONTEXT_SESSION]) return this[_CONTEXT_SESSION]; - this[_CONTEXT_SESSION] = new ContextSession(this, opts); - return this[_CONTEXT_SESSION]; - }, - }, - session: { - get() { - return this[CONTEXT_SESSION].get(); - }, - set(val) { - this[CONTEXT_SESSION].set(val); - }, - configurable: true, - }, - sessionOptions: { - get() { - return this[CONTEXT_SESSION].opts; - }, - }, - }); -} diff --git a/lib/session.js b/lib/session.js deleted file mode 100644 index 202acc1..0000000 --- a/lib/session.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; - -/** - * Session model. - */ - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -class Session { - /** - * Session constructor - * @param {Context} ctx - * @param {Object} obj - * @api private - */ - - constructor(sessionContext, obj, externalKey) { - this._sessCtx = sessionContext; - this._ctx = sessionContext.ctx; - this._externalKey = externalKey; - if (!obj) { - this.isNew = true; - } else { - for (const k in obj) { - // restore maxAge from store - if (k === '_maxAge') this._ctx.sessionOptions.maxAge = obj._maxAge; - else if (k === '_session') this._ctx.sessionOptions.maxAge = 'session'; - else this[k] = obj[k]; - } - } - } - - /** - * JSON representation of the session. - * - * @return {Object} - * @api public - */ - - toJSON() { - const obj = {}; - - Object.keys(this).forEach(key => { - if (key === 'isNew') return; - if (key[0] === '_') return; - obj[key] = this[key]; - }); - - return obj; - } - - /** - * - * alias to `toJSON` - * @api public - */ - - [inspect]() { - return this.toJSON(); - } - - /** - * Return how many values there are in the session object. - * Used to see if it's "populated". - * - * @return {Number} - * @api public - */ - - get length() { - return Object.keys(this.toJSON()).length; - } - - /** - * populated flag, which is just a boolean alias of .length. - * - * @return {Boolean} - * @api public - */ - - get populated() { - return !!this.length; - } - - /** - * get session maxAge - * - * @return {Number} - * @api public - */ - - get maxAge() { - return this._ctx.sessionOptions.maxAge; - } - - /** - * set session maxAge - * - * @param {Number} - * @api public - */ - - set maxAge(val) { - this._ctx.sessionOptions.maxAge = val; - // maxAge changed, must save to cookie and store - this._requireSave = true; - } - - /** - * get session external key - * only exist if opts.store present - */ - get externalKey() { - return this._externalKey; - } - - /** - * save this session no matter whether it is populated - * - * @param {Function} callback the optional function to call after saving the session - * @api public - */ - - save(callback) { - return this.commit({ save: true }, callback); - } - - /** - * regenerate this session - * - * @param {Function} callback the optional function to call after regenerating the session - * @api public - */ - - regenerate(callback) { - return this.commit({ regenerate: true }, callback); - } - - /** - * commit this session's headers if autoCommit is set to false - * - * @api public - */ - - manuallyCommit() { - return this.commit(); - } - - commit(options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - const promise = this._sessCtx.commit(options); - if (callback) { - promise.then(() => callback(), callback); - } else { - return promise; - } - } -} - -module.exports = Session; diff --git a/lib/util.js b/lib/util.js deleted file mode 100644 index b3a1a28..0000000 --- a/lib/util.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const crc = require('crc').crc32; - -module.exports = { - - /** - * Decode the base64 cookie value to an object. - * - * @param {String} string - * @return {Object} - * @api private - */ - - decode(string) { - const body = Buffer.from(string, 'base64').toString('utf8'); - const json = JSON.parse(body); - return json; - }, - - /** - * Encode an object into a base64-encoded JSON string. - * - * @param {Object} body - * @return {String} - * @api private - */ - - encode(body) { - body = JSON.stringify(body); - return Buffer.from(body).toString('base64'); - }, - - hash(sess) { - return crc(JSON.stringify(sess)); - }, - - CookieDateEpoch: 'Thu, 01 Jan 1970 00:00:00 GMT', -}; diff --git a/package.json b/package.json index a61dc55..725d6c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "name": "koa-session", "description": "Koa cookie session middleware with external store support", - "repository": "koajs/session", + "repository": { + "type": "git", + "url": "git@github.com:koajs/session.git" + }, "version": "6.4.0", "keywords": [ "koa", @@ -9,38 +12,70 @@ "session", "cookie" ], - "files": [ - "index.js", - "lib" - ], "devDependencies": { - "benchmark": "^2.1.4", - "eslint": "3", - "eslint-config-egg": "3", - "istanbul": "0", + "@arethetypeswrong/cli": "^0.17.1", + "@eggjs/bin": "7", + "@eggjs/supertest": "8", + "@eggjs/tsconfig": "1", + "@types/crc": "^3.8.3", + "@types/koa": "^2.15.0", + "@types/mocha": "10", + "@types/node": "22", + "eslint": "8", + "eslint-config-egg": "14", "koa": "2", - "mm": "^2.1.0", - "mocha": "^5.2.0", - "mz-modules": "^2.0.0", - "pedding": "^1.1.0", - "should": "8", - "supertest": "^3.3.0", - "uid-safe": "^2.1.3" + "mm": "4", + "rimraf": "6", + "tshy": "3", + "tshy-after": "1", + "typescript": "5" }, "license": "MIT", "dependencies": { "crc": "^3.8.0", "debug": "^4.3.3", - "is-type-of": "^1.2.1", - "uuid": "^8.3.2" + "is-type-of": "^2.2.0", + "uuid": "^8.3.2", + "zod": "^3.24.1" }, "engines": { - "node": ">=8.0.0" + "node": ">= 18.19.0" }, "scripts": { - "test": "npm run lint && NODE_ENV=test mocha --exit --require should --reporter spec test/*.test.js", - "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --exit --require should test/*.test.js", - "ci": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", - "lint": "eslint lib test index.js" - } + "lint": "eslint --cache src test --ext .ts", + "pretest": "npm run clean && npm run lint -- --fix", + "test": "egg-bin test", + "preci": "npm run clean && npm run lint", + "ci": "egg-bin cov", + "postci": "npm run prepublishOnly && npm run clean", + "clean": "rimraf dist", + "prepublishOnly": "tshy && tshy-after && attw --pack" + }, + "type": "module", + "tshy": { + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js" } diff --git a/lib/context.js b/src/context.ts similarity index 56% rename from lib/context.js rename to src/context.ts index d3efbec..c31a751 100644 --- a/lib/context.js +++ b/src/context.ts @@ -1,52 +1,52 @@ -'use strict'; +import { debuglog } from 'node:util'; +import { Session } from './session.js'; +import util from './util.js'; +import type { SessionOptions } from './index.js'; -const debug = require('debug')('koa-session:context'); -const Session = require('./session'); -const util = require('./util'); +const debug = debuglog('koa-session:context'); const COOKIE_EXP_DATE = new Date(util.CookieDateEpoch); const ONE_DAY = 24 * 60 * 60 * 1000; -class ContextSession { +export class ContextSession { + ctx: any; + app: any; + opts: SessionOptions; + store: SessionOptions['store']; + session: Session | false; + externalKey?: string; + prevHash?: number; + /** * context session constructor - * @api public */ - - constructor(ctx, opts) { + constructor(ctx: any, opts: SessionOptions) { this.ctx = ctx; this.app = ctx.app; - this.opts = Object.assign({}, opts); + this.opts = { ...opts }; this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } /** * internal logic of `ctx.session` * @return {Session} session object - * - * @api public */ - - get() { - const session = this.session; + get(): Session | null { // already retrieved - if (session) return session; + if (this.session) return this.session; // unset - if (session === false) return null; + if (this.session === false) return null; // create an empty session or init from cookie this.store ? this.create() : this.initFromCookie(); - return this.session; + return this.session as Session; } /** * internal logic of `ctx.session=` * @param {Object} val session object - * - * @api public */ - - set(val) { + set(val: Record | null) { if (val === null) { this.session = false; return; @@ -63,7 +63,7 @@ class ContextSession { * init session from external store * will be called in the front of session middleware * - * @api public + * @public */ async initFromExternal() { @@ -87,23 +87,23 @@ class ContextSession { return; } - const json = await this.store.get(externalKey, opts.maxAge, { ctx, rolling: opts.rolling }); - if (!this.valid(json, externalKey)) { + const sessionData = await this.store!.get(externalKey, opts.maxAge as number, { ctx, rolling: opts.rolling }); + if (!this.valid(sessionData, externalKey)) { + debug('invalid session data, create a new session'); // create a new `externalKey` this.create(); return; } // create with original `externalKey` - this.create(json, externalKey); - this.prevHash = util.hash(this.session.toJSON()); + this.create(sessionData, externalKey); + this.prevHash = util.hash((this.session as Session).toJSON()); } /** * init session from cookie - * @api private + * @private */ - initFromCookie() { debug('init from cookie'); const ctx = this.ctx; @@ -115,68 +115,67 @@ class ContextSession { return; } - let json; - debug('parse %s', cookie); + let sessionData: Record; + debug('parse cookie: %j', cookie); try { - json = opts.decode(cookie); - } catch (err) { + sessionData = opts.decode(cookie); + } catch (err: unknown) { // backwards compatibility: // create a new session if parsing fails. - // new Buffer(string, 'base64') does not seem to crash + // `Buffer.from(string, 'base64')` does not seem to crash // when `string` is not base64-encoded. // but `JSON.parse(string)` will crash. debug('decode %j error: %s', cookie, err); - if (!(err instanceof SyntaxError)) { + if (err instanceof Error && !(err instanceof SyntaxError)) { // clean this cookie to ensure next request won't throw again ctx.cookies.set(opts.key, '', opts); - // ctx.onerror will unset all headers, and set those specified in err - err.headers = { + // `ctx.onerror` will unset all headers, and set those specified in err + Reflect.set(err, 'headers', { 'set-cookie': ctx.response.get('set-cookie'), - }; + }); throw err; } this.create(); return; } - debug('parsed %j', json); - - if (!this.valid(json)) { + debug('parsed session data: %j', sessionData); + if (!this.valid(sessionData)) { + // create a new session if the session data is invalid this.create(); + debug('invalid session data, create a new session'); return; } // support access `ctx.session` before session middleware - this.create(json); - this.prevHash = util.hash(this.session.toJSON()); + this.create(sessionData); + this.prevHash = util.hash((this.session as Session).toJSON()); } /** - * verify session(expired or ) - * @param {Object} value session object - * @param {Object} key session externalKey(optional) - * @return {Boolean} valid - * @api private + * verify session(expired or custom verification) + * @param {Object} sessionData session data + * @param {Object} [key] session externalKey(optional) + * @private */ - - valid(value, key) { + protected valid(sessionData: Record, key?: string) { const ctx = this.ctx; - if (!value) { - this.emit('missed', { key, value, ctx }); + if (!sessionData) { + this.emit('missed', { key, value: sessionData, ctx }); return false; } - if (value._expire && value._expire < Date.now()) { + if (typeof sessionData._expire === 'number' && sessionData._expire < Date.now()) { debug('expired session'); - this.emit('expired', { key, value, ctx }); + this.emit('expired', { key, value: sessionData, ctx }); return false; } const valid = this.opts.valid; - if (typeof valid === 'function' && !valid(ctx, value)) { + if (typeof valid === 'function' && !valid(ctx, sessionData)) { // valid session value fail, ignore this session debug('invalid session'); - this.emit('invalid', { key, value, ctx }); + this.emit('invalid', { key, value: sessionData, ctx }); return false; } return true; @@ -185,9 +184,9 @@ class ContextSession { /** * @param {String} event event name * @param {Object} data event data - * @api private + * @private */ - emit(event, data) { + emit(event: string, data: unknown) { setImmediate(() => { this.app.emit(`session:${event}`, data); }); @@ -196,45 +195,49 @@ class ContextSession { /** * create a new session and attach to ctx.sess * - * @param {Object} [val] session data + * @param {Object} [sessionData] session data * @param {String} [externalKey] session external key - * @api private */ - - create(val, externalKey) { - debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx); - this.session = new Session(this, val, this.externalKey); + protected create(sessionData?: Record, externalKey?: string) { + debug('create session with data: %j, externalKey: %s', sessionData, externalKey); + if (this.store) { + this.externalKey = externalKey ?? this.opts.genid?.(this.ctx); + } + this.session = new Session(this, sessionData, this.externalKey); } /** * Commit the session changes or removal. - * - * @api public */ - async commit({ save = false, regenerate = false } = {}) { const session = this.session; const opts = this.opts; const ctx = this.ctx; // not accessed - if (undefined === session) return; + if (session === undefined) { + return; + } // removed if (session === false) { await this.remove(); return; } + if (regenerate) { await this.remove(); - if (this.store) this.externalKey = opts.genid && opts.genid(ctx); + if (this.store) { + this.externalKey = opts.genid?.(ctx); + } } // force save session when `session._requireSave` set const reason = save || regenerate || session._requireSave ? 'force' : this._shouldSaveSession(); - debug('should save session: %s', reason); - if (!reason) return; + debug('should save session: %j', reason); + if (!reason) { + return; + } if (typeof opts.beforeSave === 'function') { debug('before save'); @@ -246,81 +249,90 @@ class ContextSession { _shouldSaveSession() { const prevHash = this.prevHash; - const session = this.session; + const session = this.session as Session; // do nothing if new and not populated - const json = session.toJSON(); - if (!prevHash && !Object.keys(json).length) return ''; + const sessionData = session.toJSON(); + if (!prevHash && !Object.keys(sessionData).length) { + return ''; + } // save if session changed - const changed = prevHash !== util.hash(json); - if (changed) return 'changed'; + const changed = prevHash !== util.hash(sessionData); + if (changed) { + return 'changed'; + } // save if opts.rolling set - if (this.opts.rolling) return 'rolling'; + if (this.opts.rolling) { + return 'rolling'; + } // save if opts.renew and session will expired if (this.opts.renew) { const expire = session._expire; const maxAge = session.maxAge; // renew when session will expired in maxAge / 2 - if (expire && maxAge && expire - Date.now() < maxAge / 2) return 'renew'; + if (expire && maxAge && expire - Date.now() < maxAge / 2) { + return 'renew'; + } } + // don't save return ''; } /** * remove session - * @api private + * @private */ - async remove() { // Override the default options so that we can properly expire the session cookies - const opts = Object.assign({}, this.opts, { + const opts = { + ...this.opts, expires: COOKIE_EXP_DATE, maxAge: false, - }); - + }; const ctx = this.ctx; const key = opts.key; const externalKey = this.externalKey; - if (externalKey) await this.store.destroy(externalKey, { ctx }); + if (externalKey) { + await this.store!.destroy(externalKey, { ctx }); + } ctx.cookies.set(key, '', opts); } /** * save session - * @api private + * @private */ - - async save(changed) { + async save(changed: boolean) { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; - let json = this.session.toJSON(); + const sessionData = (this.session as Session).toJSON(); // set expire for check let maxAge = opts.maxAge ? opts.maxAge : ONE_DAY; if (maxAge === 'session') { // do not set _expire in json if maxAge is set to 'session' // also delete maxAge from options opts.maxAge = undefined; - json._session = true; + sessionData._session = true; } else { // set expire for check - json._expire = maxAge + Date.now(); - json._maxAge = maxAge; + sessionData._expire = maxAge + Date.now(); + sessionData._maxAge = maxAge; } // save to external store if (externalKey) { - debug('save %j to external key %s', json, externalKey); + debug('save %j to external key %s', sessionData, externalKey); if (typeof maxAge === 'number') { // ensure store expired after cookie maxAge += 10000; } - await this.store.set(externalKey, json, maxAge, { + await this.store!.set(externalKey, sessionData, maxAge as number, { changed, ctx: this.ctx, rolling: opts.rolling, @@ -333,13 +345,11 @@ class ContextSession { return; } - // save to cookie - debug('save %j to cookie', json); - json = opts.encode(json); - debug('save %s', json); - - this.ctx.cookies.set(key, json, opts); + // save to cookie with base64 encode string + debug('save session data %j to cookie', sessionData); + const base64String = opts.encode(sessionData); + debug('save session data json base64 format: %s to cookie key: %s with options: %j', + base64String, key, opts); + this.ctx.cookies.set(key, base64String, opts); } } - -module.exports = ContextSession; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0652ffe --- /dev/null +++ b/src/index.ts @@ -0,0 +1,305 @@ +import assert from 'node:assert'; +import { debuglog } from 'node:util'; +import { randomUUID } from 'node:crypto'; +import { isClass } from 'is-type-of'; +import z from 'zod'; +import { ContextSession } from './context.js'; +import util from './util.js'; + +const debug = debuglog('koa-session'); + +const GET_CONTEXT_SESSION = Symbol('get contextSession'); +const CONTEXT_SESSION_INSTANCE = Symbol('contextSession instance'); + +const SessionOptionsSchema = z.object({ + /** + * cookie key + * Default is `koa.sess` + */ + key: z.string().default('koa.sess'), + /** + * maxAge in ms + * Default is `86400000`, one day + * If set to 'session' will result in a cookie that expires when session/browser is closed + * + * Warning: If a session cookie is stolen, this cookie will never expire + */ + maxAge: z.union([ z.number(), z.literal('session') ]).optional(), + /** + * automatically commit headers + * Default is `true` + */ + autoCommit: z.boolean().default(true), + /** + * cookie value can overwrite or not + * Default is `true` + */ + overwrite: z.boolean().default(true), + /** + * httpOnly or not + * Default is `true` + */ + httpOnly: z.boolean().default(true), + /** + * signed or not + * Default is `true` + */ + signed: z.boolean().default(true), + /** + * Force a session identifier cookie to be set on every response. + * The expiration is reset to the original `maxAge`, resetting the expiration countdown. + * Default is `false` + */ + rolling: z.boolean().default(false), + /** + * renew session when session is nearly expired, so we can always keep user logged in. + * Default is `false` + */ + renew: z.boolean().default(false), + /** + * secure cookie + * Default is `undefined`, will be set to `true` if the connection is over HTTPS, otherwise `false`. + */ + secure: z.boolean().optional(), + /** + * session cookie sameSite options + * Default is `undefined`, meaning don't set it + */ + sameSite: z.string().optional(), + /** + * External key is used the cookie by default, + * but you can use `options.externalKey` to customize your own external key methods. + */ + externalKey: z.object({ + /** + * get the external key + * `(ctx) => string` + */ + get: z.function() + .args(z.any()) + .returns(z.string()), + /** + * set the external key + * `(ctx, key) => void` + */ + set: z.function() + .args(z.any(), z.string()) + .returns(z.void()), + }).optional(), + /** + * session storage is dependent on your external store + */ + store: z.object({ + /** + * get session data by key + * `(key, maxAge, { rolling, ctx }) => sessionData | Promise` + */ + get: z.function() + .args(z.string(), z.number(), z.object({ rolling: z.boolean(), ctx: z.any() })) + .returns(z.promise(z.any())), + /** + * set session data for key, with a `maxAge` (in ms) + * `(key, sess, maxAge, { rolling, changed, ctx }) => void | Promise` + */ + set: z.function() + .args(z.string(), z.any(), z.number(), z.object({ rolling: z.boolean(), changed: z.boolean(), ctx: z.any() })) + .returns(z.promise(z.void())), + /** + * destroy session data for key + * `(key, { ctx })=> void | Promise` + */ + destroy: z.function() + .args(z.string(), z.object({ ctx: z.any() })) + .returns(z.promise(z.void())), + }).optional(), + /** + * If your session store requires data or utilities from context, `opts.ContextStore` is also supported. + * `ContextStore` must be a class which claims three instance methods demonstrated above. + * `new ContextStore(ctx)` will be executed on every request. + */ + ContextStore: z.any().optional(), + encode: z.function() + .args(z.any()) + .returns(z.string()) + .optional() + .default(() => util.encode), + decode: z.function() + .args(z.string()) + .returns(z.any()) + .default(() => util.decode), + /** + * If you want to generate a new session id, you can use `genid` option to customize it. + * Default is a function that uses `randomUUID()`. + * `(ctx) => string` + */ + genid: z.function() + .args(z.any()) + .returns(z.string()) + .optional(), + /** + * If you want to prefix the session id, you can use `prefix` option to customize it. + * It will not work if `options.genid(ctx)` present. + */ + prefix: z.string().optional(), + /** + * valid session value before use it + * `(ctx, sessionData) => boolean` + */ + valid: z.function() + .args(z.any(), z.any()) + .returns(z.any()) + .optional(), + /** + * hook before save session + * `(ctx, sessionModel) => void` + */ + beforeSave: z.function() + .args(z.any(), z.any()) + .returns(z.void()) + .optional(), +}); + +const DEFAULT_SESSION_OPTIONS = SessionOptionsSchema.parse({}); + +export type SessionOptions = z.infer; +export type CreateSessionOptions = Partial; + +type Middleware = (ctx: any, next: any) => Promise; + +/** + * Initialize session middleware with `opts`: + * + * - `key` session cookie name ["koa.sess"] + * - all other options are passed as cookie options + * + * @param {Object} [opts] session options + * @param {Application} app koa application instance + * @public + */ +export function createSession(opts: CreateSessionOptions, app: any): Middleware; +export function createSession(app: any, opts?: CreateSessionOptions): Middleware; +export function createSession(opts: CreateSessionOptions | any, app: any): Middleware { + // session(app[, opts]) + if (opts && 'use' in opts && typeof opts.use === 'function') { + [ app, opts ] = [ opts, app ]; + } + // app required + if (typeof app?.use !== 'function') { + throw new TypeError('app instance required: `session(opts, app)`'); + } + + // back-compat maxage + if (opts && !('maxAge' in opts) && 'maxage' in opts) { + Reflect.set(opts, 'maxAge', Reflect.get(opts, 'maxage')); + if (process.env.NODE_ENV !== 'production') { + console.warn('DeprecationWarning: `maxage` option has been renamed to `maxAge`'); + } + } + let options = { + ...DEFAULT_SESSION_OPTIONS, + ...opts, + }; + SessionOptionsSchema.parse(options); + options = formatOptions(options); + extendContext(app.context, options); + + return async function session(ctx: any, next: any) { + const sess = ctx[GET_CONTEXT_SESSION]; + if (sess.store) { + await sess.initFromExternal(); + } + try { + await next(); + } catch (err) { + throw err; + } finally { + if (options.autoCommit) { + await sess.commit(); + } + } + }; +} + +// Usage: `import session from 'koa-session'` +export default createSession; + +/** + * format and check session options + */ +function formatOptions(opts: SessionOptions) { + // defaults + if (opts.overwrite == null) opts.overwrite = true; + if (opts.httpOnly == null) opts.httpOnly = true; + // delete null sameSite config + if (opts.sameSite == null) delete opts.sameSite; + if (opts.signed == null) opts.signed = true; + if (opts.autoCommit == null) opts.autoCommit = true; + + debug('session options %j', opts); + const store = opts.store; + if (store) { + assert(typeof store.get === 'function', 'store.get must be function'); + assert(typeof store.set === 'function', 'store.set must be function'); + assert(typeof store.destroy === 'function', 'store.destroy must be function'); + } + + const externalKey = opts.externalKey; + if (externalKey) { + assert(typeof externalKey.get === 'function', 'externalKey.get must be function'); + assert(typeof externalKey.set === 'function', 'externalKey.set must be function'); + } + + const ContextStore = opts.ContextStore; + if (ContextStore) { + assert(isClass(ContextStore), 'ContextStore must be a class'); + assert(typeof ContextStore.prototype.get === 'function', 'ContextStore.prototype.get must be function'); + assert(typeof ContextStore.prototype.set === 'function', 'ContextStore.prototype.set must be function'); + assert(typeof ContextStore.prototype.destroy === 'function', 'ContextStore.prototype.destroy must be function'); + } + + if (!opts.genid) { + if (opts.prefix) { + opts.genid = () => `${opts.prefix}${randomUUID()}`; + } else { + opts.genid = () => randomUUID(); + } + } + return opts; +} + +/** + * extend context prototype, add session properties + * + * @param {Object} context koa's context prototype + * @param {Object} opts session options + */ +function extendContext(context: object, opts: SessionOptions) { + if (context.hasOwnProperty(GET_CONTEXT_SESSION)) { + return; + } + Object.defineProperties(context, { + [GET_CONTEXT_SESSION]: { + get() { + if (this[CONTEXT_SESSION_INSTANCE]) { + return this[CONTEXT_SESSION_INSTANCE]; + } + this[CONTEXT_SESSION_INSTANCE] = new ContextSession(this, opts); + return this[CONTEXT_SESSION_INSTANCE]; + }, + }, + session: { + get() { + return this[GET_CONTEXT_SESSION].get(); + }, + set(val) { + this[GET_CONTEXT_SESSION].set(val); + }, + configurable: true, + }, + sessionOptions: { + get() { + return this[GET_CONTEXT_SESSION].opts; + }, + }, + }); +} diff --git a/src/session.ts b/src/session.ts new file mode 100644 index 0000000..024b553 --- /dev/null +++ b/src/session.ts @@ -0,0 +1,139 @@ +import { inspect } from 'node:util'; +import type { ContextSession } from './context.js'; + +type Callback = (err?: Error) => void; + +/** + * Session model + */ +export class Session { + #sessCtx: ContextSession; + #ctx: any; + #externalKey?: string; + isNew = false; + _requireSave = false; + // session expire time, will be set from sessionData + _expire?: number; + + constructor(sessionContext: ContextSession, sessionData?: Record, externalKey?: string) { + this.#sessCtx = sessionContext; + this.#ctx = sessionContext.ctx; + this.#externalKey = externalKey; + if (!sessionData) { + this.isNew = true; + } else { + for (const k in sessionData) { + // restore maxAge from store + if (k === '_maxAge') { + this.#ctx.sessionOptions.maxAge = sessionData._maxAge; + } else if (k === '_session') { + // set maxAge to 'session' if it's a session lifetime + this.#ctx.sessionOptions.maxAge = 'session'; + } else { + Reflect.set(this, k, sessionData[k]); + } + } + } + } + + /** + * JSON representation of the session. + */ + toJSON() { + const obj: Record = {}; + for (const key in this) { + if (key === 'isNew') continue; + // skip private stuff + if (key[0] === '_') continue; + const value = this[key]; + // skip functions + if (typeof value === 'function') continue; + obj[key] = value; + } + return obj; + } + + /** + * alias to `toJSON` + */ + [inspect.custom]() { + return this.toJSON(); + } + + /** + * Return how many values there are in the session object. + * Used to see if it's "populated". + */ + get length() { + return Object.keys(this.toJSON()).length; + } + + /** + * populated flag, which is just a boolean alias of .length. + */ + get populated() { + return !!this.length; + } + + /** + * get session maxAge + */ + get maxAge(): number { + return this.#ctx.sessionOptions.maxAge; + } + + /** + * set session maxAge + */ + set maxAge(val: number) { + this.#ctx.sessionOptions.maxAge = val; + // maxAge changed, must save to cookie and store + this._requireSave = true; + } + + /** + * get session external key + * only exist if opts.store present + */ + get externalKey() { + return this.#externalKey; + } + + /** + * save this session no matter whether it is populated + * + * @param {Function} [callback] the optional function to call after saving the session + */ + save(callback?: Callback) { + return this.commit({ save: true }, callback); + } + + /** + * regenerate this session + * + * @param {Function} [callback] the optional function to call after regenerating the session + */ + regenerate(callback?: Callback) { + return this.commit({ regenerate: true }, callback); + } + + /** + * commit this session's headers if autoCommit is set to false + */ + manuallyCommit() { + return this.commit(); + } + + commit(options?: any, callback?: Callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + const promise = this.#sessCtx.commit(options); + if (callback) { + promise.then(() => callback(), callback); + } else { + return promise; + } + } +} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..39f0806 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,26 @@ +import crc from 'crc'; + +export default { + /** + * Decode the base64 cookie value to an object + * @private + */ + decode(base64String: string): Record { + const body = Buffer.from(base64String, 'base64').toString('utf8'); + const json = JSON.parse(body); + return json; + }, + + /** + * Encode an object into a base64-encoded JSON string + */ + encode(data: Record) { + return Buffer.from(JSON.stringify(data)).toString('base64'); + }, + + hash(data: Record) { + return crc.crc32(JSON.stringify(data)); + }, + + CookieDateEpoch: 'Thu, 01 Jan 1970 00:00:00 GMT', +}; diff --git a/test/context_store.js b/test/context_store.js deleted file mode 100644 index 8a053eb..0000000 --- a/test/context_store.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -// this is a stupid nonsense example just to test - -const sessions = {}; - -class ContextStore { - constructor(ctx) { - this.ctx = ctx; - } - - async get(key) { - return sessions[key]; - } - - async set(key, value) { - sessions[key] = value; - } - - async destroy(key) { - sessions[key] = undefined; - } -} - -module.exports = ContextStore; diff --git a/test/context_store.ts b/test/context_store.ts new file mode 100644 index 0000000..fb15761 --- /dev/null +++ b/test/context_store.ts @@ -0,0 +1,22 @@ +// this is a stupid nonsense example just to test + +const sessions: Record = {}; + +export default class ContextStore { + ctx: any; + constructor(ctx: any) { + this.ctx = ctx; + } + + async get(key: string) { + return sessions[key]; + } + + async set(key: string, value: unknown) { + sessions[key] = value; + } + + async destroy(key: string) { + sessions[key] = undefined; + } +} diff --git a/test/contextstore.test.js b/test/contextstore.test.js deleted file mode 100644 index b31fada..0000000 --- a/test/contextstore.test.js +++ /dev/null @@ -1,758 +0,0 @@ -'use strict'; - -const Koa = require('koa'); -const request = require('supertest'); -const should = require('should'); -const mm = require('mm'); -const session = require('..'); -const ContextStore = require('./context_store'); - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -describe('Koa Session External Context Store', () => { - let cookie; - - describe('when the session contains a ;', () => { - it('should still work', done => { - const app = App(); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('new session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and not populated', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session; - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when populated', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - cookie.indexOf('_suffix').should.greaterThan(1); - done(); - }); - }); - - it('should pass sid to middleware', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.state.sid.indexOf('_suffix').should.greaterThan(1); - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - cookie.indexOf('_suffix').should.greaterThan(1); - done(); - }); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - }); - - describe('saved session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed but not changed', () => { - it('should be the same session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, done); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and changed', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.money = '$$$'; - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - }); - - describe('when session is', () => { - describe('null', () => { - it('should expire the session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = null; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('an empty object', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = {}; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('an object', () => { - it('should create a session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { message: 'hello' }; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('anything else', () => { - it('should throw', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - }); - - describe('session', () => { - describe('.inspect()', () => { - it('should return session content', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = ctx.session[inspect](); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect({ foo: 'bar' }) - .expect(200, done); - }); - }); - - describe('.length', () => { - it('should return session length', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.length); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('1') - .expect(200, done); - }); - }); - - describe('.populated', () => { - it('should return session populated', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.populated); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('.save()', () => { - it('should save session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.save(); - ctx.body = 'hello'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('hello') - .expect(200, done); - }); - }); - }); - - describe('when an error is thrown downstream and caught upstream', () => { - it('should still save the session', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - - app.use(async function(ctx, next) { - try { - await next(); - } catch (err) { - ctx.status = err.status; - ctx.body = err.message; - } - }); - - app.use(session({ ContextStore }, app)); - - app.use(async function(ctx, next) { - ctx.session.name = 'funny'; - await next(); - }); - - app.use(async function(ctx) { - ctx.throw(401); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(401, done); - }); - }); - - describe('when autoCommit is present', () => { - describe('and set to false', () => { - it('should not set headers if manuallyCommit() isn\'t called', done => { - const app = App({ autoCommit: false }); - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - should.not.exist(cookie); - }) - .expect(200, done); - }); - it('should set headers if manuallyCommit() is called', done => { - const app = App({ autoCommit: false }); - app.use(async function(ctx, next) { - if (ctx.method === 'POST') { - ctx.session.message = 'dummy'; - } - await next(); - }); - app.use(async function(ctx) { - ctx.body = 200; - await ctx.session.manuallyCommit(); - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end(err => { - if (err) return done(err); - }) - .expect(200, done); - }); - }); - }); - - describe('when maxAge present', () => { - describe('and set to be a session cookie', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 'session' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - it('should not expire the session after multiple session changes', done => { - const app = App({ maxAge: 'session' }); - - app.use(async function(ctx) { - ctx.session.count = (ctx.session.count || 0) + 1; - ctx.body = `hi ${ctx.session.count}`; - }); - const server = app.listen(); - - request(server) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect('hi 1') - .end((err, res) => { - if (err) return done(err); - let cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect('hi 2') - .end((err, res) => { - if (err) return done(err); - cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect('hi 3') - .end((err, res) => { - if (err) return done(err); - cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - - done(); - }); - }); - }); - }); - it('should use the default maxAge when improper string given', done => { - const app = App({ maxAge: 'not the right string' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - describe('and not expire', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - - describe('and expired', () => { - it('should expire the sess', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.status = 200; - return; - } - - ctx.body = ctx.session.message || ''; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - setTimeout(() => { - request(server) - .get('/') - .set('cookie', cookie) - .expect('', done); - }, 200); - }); - }); - }); - }); - - describe('ctx.session.maxAge', () => { - it('should return opt.maxAge', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - ctx.body = ctx.session.maxAge; - }); - - request(app.listen()) - .get('/') - .expect('100', done); - }); - }); - - describe('ctx.session.maxAge=', () => { - it('should set sessionOptions.maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.session.maxAge = 100; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save even session not change', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.maxAge = 100; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save when create session only with maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { maxAge: 100 }; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - }); - - describe('when store return empty', () => { - it('should create new Session', done => { - const app = App({ signed: false }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-key') - .expect('true') - .expect(200, done); - }); - }); - - describe('when valid and beforeSave set', () => { - it('should ignore session when uid changed', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({ - valid(ctx, sess) { - return ctx.cookies.get('uid') === sess.uid; - }, - beforeSave(ctx, sess) { - sess.uid = ctx.cookies.get('uid'); - }, - ContextStore, - }, app)); - app.use(async function(ctx) { - if (!ctx.session.foo) { - ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); - } - - ctx.body = { - foo: ctx.session.foo, - uid: ctx.cookies.get('uid'), - }; - }); - - request(app.callback()) - .get('/') - .set('Cookie', 'uid=123') - .expect(200, (err, res) => { - should.not.exist(err); - const data = res.body; - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=123') - .expect(200) - .expect(data, err => { - should.not.exist(err); - - // should ignore uid:123 session and create a new session for uid:456 - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=456') - .expect(200, (err, res) => { - should.not.exist(err); - res.body.uid.should.equal('456'); - res.body.foo.should.not.equal(data.foo); - done(); - }); - }); - }); - }); - }); - - describe('ctx.session', () => { - after(mm.restore); - - it('can be mocked', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - mm(app.context, 'session', { - foo: 'bar', - }); - - request(app.listen()) - .get('/') - .expect({ - foo: 'bar', - }) - .expect(200, done); - }); - }); -}); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.ContextStore = ContextStore; - options.genid = ctx => { - const sid = Date.now() + '_suffix'; - ctx.state.sid = sid; - return sid; - }; - app.use(session(options, app)); - return app; -} diff --git a/test/contextstore.test.ts b/test/contextstore.test.ts new file mode 100644 index 0000000..e77f295 --- /dev/null +++ b/test/contextstore.test.ts @@ -0,0 +1,677 @@ +import { strict as assert } from 'node:assert'; +import { scheduler } from 'node:timers/promises'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import { mm } from 'mm'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import ContextStore from './context_store.js'; + +const inspect = Symbol.for('nodejs.util.inspect.custom'); + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.ContextStore = ContextStore; + options.genid = ctx => { + const sid = Date.now() + '_suffix'; + ctx.state.sid = sid; + return sid; + }; + app.use(session(options, app)); + return app; +} + +describe('Koa Session External Context Store', () => { + let cookie: string; + + describe('when the session contains a ;', () => { + it('should still work', async () => { + const app = App(); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + + const res = await request(app.callback()) + .post('/') + .expect(204); + const cookie = res.get('Set-Cookie')!; + assert(cookie, 'should have set cookie'); + await request(app.callback()) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session; + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.match(cookie, /\d+_suffix/); + }); + + it('should pass sid to middleware', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + assert.match(ctx.state.sid, /\d+_suffix/); + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + + cookie = res.get('Set-Cookie')!.join(';'); + assert.match(cookie, /\d+_suffix/); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', async () => { + const app = App(); + + app.use(async function(ctx) { + assert.equal(ctx.session.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + assert.equal(ctx.session.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = null; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('an object', () => { + it('should create a session', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('anything else', () => { + it('should throw', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = ctx.session[inspect](); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200); + }); + }); + + describe('.length', () => { + it('should return session length', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.length); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('1') + .expect(200); + }); + }); + + describe('.populated', () => { + it('should return session populated', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.populated); + }); + + await request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('true') + .expect(200); + }); + }); + + describe('.save()', () => { + it('should save session', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.save(); + ctx.body = 'hello'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('hello') + .expect(200); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async function(ctx, next) { + try { + await next(); + } catch (err: any) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session({ ContextStore }, app)); + + app.use(async function(ctx, next) { + ctx.session.name = 'funny'; + await next(); + }); + + app.use(async function(ctx) { + ctx.throw(401); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(401); + }); + }); + + describe('when autoCommit is present', () => { + describe('and set to false', () => { + it('should not set headers if manuallyCommit() isn\'t called', async () => { + const app = App({ autoCommit: false }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const res = await request(app.callback()) + .post('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + + it('should set headers if manuallyCommit() is called', async () => { + const app = App({ autoCommit: false }); + app.use(async function(ctx, next) { + if (ctx.method === 'POST') { + ctx.session.message = 'dummy'; + } + await next(); + }); + app.use(async function(ctx) { + ctx.body = 200; + await ctx.session.manuallyCommit(); + }); + + await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + }); + + describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const res = await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + + const cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + + it('should not expire the session after multiple session changes', async () => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + ctx.session.count = (ctx.session.count || 0) + 1; + ctx.body = `hi ${ctx.session.count}`; + }); + + let res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect('hi 1') + .expect(200); + let cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect('hi 2') + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect('hi 3') + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + }); + + it('should throw error when the maxAge improper string given', () => { + assert.throws(() => { + App({ maxAge: 'not the right string' } as any); + }, /Invalid input/); + }); + }); + + describe('and not expire', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const res = await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + const cookie = res.get('Set-Cookie')!.join(';'); + await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and expired', () => { + it('should expire the sess', async () => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session.message || ''; + }); + + const res = await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + const cookie = res.get('Set-Cookie')!.join(';'); + await scheduler.wait(200); + await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(''); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', async () => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + ctx.body = ctx.session.maxAge; + }); + + await request(app.callback()) + .get('/') + .expect('100'); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.session.maxAge = 100; + ctx.body = ctx.session.foo; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save even session not change', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.maxAge = 100; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save when create session only with maxAge', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + }); + + describe('when store return empty', () => { + it('should create new Session', async () => { + const app = App({ signed: false }); + + app.use(async function(ctx) { + ctx.body = String(ctx.session.isNew); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-key') + .expect('true') + .expect(200); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + ContextStore, + }, app)); + + app.use(async function(ctx) { + if (!ctx.session.foo) { + ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + let res = await request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200); + + const data = res.body; + const cookies = res.get('Set-Cookie')!.join(';'); + assert.match(cookies, /koa\.sess=/); + + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data); + + // should ignore uid:123 session and create a new session for uid:456 + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200); + assert.equal(res.body.uid, '456'); + assert.notEqual(res.body.foo, data.foo); + }); + }); + + describe('ctx.session', () => { + after(mm.restore); + + it('can be mocked', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + mm(app.context, 'session', { + foo: 'bar', + }); + + await request(app.callback()) + .get('/') + .expect({ + foo: 'bar', + }) + .expect(200); + }); + }); +}); diff --git a/test/cookie.test.js b/test/cookie.test.js deleted file mode 100644 index 69b0683..0000000 --- a/test/cookie.test.js +++ /dev/null @@ -1,948 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const Koa = require('koa'); -const request = require('supertest'); -const should = require('should'); -const session = require('..'); - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -describe('Koa Session Cookie', () => { - let cookie; - - describe('when options.signed = true', () => { - describe('when app.keys are set', () => { - it('should work', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({}, app)); - - app.use(async function(ctx) { - ctx.session.message = 'hi'; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, done); - }); - }); - - describe('when app.keys are not set', () => { - it('should throw and clean this cookie', done => { - const app = new Koa(); - - app.use(session(app)); - - app.use(async function(ctx) { - ctx.session.message = 'hi'; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - - describe('when app not set', () => { - it('should throw', () => { - const app = new Koa(); - (function() { - app.use(session()); - }).should.throw('app instance required: `session(opts, app)`'); - }); - }); - }); - - describe('when options.signed = false', () => { - describe('when app.keys are not set', () => { - it('should work', done => { - const app = new Koa(); - - app.use(session({ signed: false }, app)); - - app.use(async function(ctx) { - ctx.session.message = 'hi'; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, done); - }); - }); - }); - - describe('when the session contains a ;', () => { - it('should still work', done => { - const app = App(); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - // samesite is not set - assert(!cookie.join(';').includes('samesite')); - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('new session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and not populated', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session; - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when populated', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - done(); - }); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - }); - - describe('saved session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed but not changed', () => { - it('should be the same session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, done); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and changed', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.money = '$$$'; - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect(res => { - const cookie = res.headers['set-cookie']; - // samesite is not set - assert(!cookie.join(';').includes('samesite')); - }) - .expect(200, done); - }); - }); - }); - - describe('after session set to null with signed cookie', () => { - it('should return expired cookies', done => { - const app = App({ - signed: true, - }); - - app.use(async function(ctx) { - ctx.session.hello = {}; - ctx.session = null; - ctx.body = String(ctx.session === null); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('Set-Cookie', /koa\.sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('true') - .expect(200, done); - }); - }); - - describe('after session set to null without signed cookie', () => { - it('should return expired cookies', done => { - const app = App({ - signed: false, - }); - - app.use(async function(ctx) { - ctx.session.hello = {}; - ctx.session = null; - ctx.body = String(ctx.session === null); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('true') - .expect(200, done); - }); - }); - - describe('when get session after set to null', () => { - it('should return null', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.hello = {}; - ctx.session = null; - ctx.body = String(ctx.session === null); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('when decode session', () => { - describe('SyntaxError', () => { - it('should create new session', done => { - const app = App({ signed: false }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-session;') - .expect('true') - .expect(200, done); - }); - }); - - describe('Other Error', () => { - it('should throw', done => { - const app = App({ - signed: false, - decode() { - throw new Error('decode error'); - }, - }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-session;') - .expect('Set-Cookie', /koa\.sess=;/) - .expect(500, done); - }); - }); - }); - - describe('when encode session error', () => { - it('should throw', done => { - const app = App({ - encode() { - throw new Error('encode error'); - }, - }); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = 'hello'; - }); - - app.once('error', (err, ctx) => { - err.message.should.equal('encode error'); - should.exists(ctx); - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - - describe('session', () => { - describe('.inspect()', () => { - it('should return session content', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = ctx.session[inspect](); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect({ foo: 'bar' }) - .expect(200, done); - }); - }); - - describe('.length', () => { - it('should return session length', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.length); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('1') - .expect(200, done); - }); - }); - - describe('.populated', () => { - it('should return session populated', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.populated); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('.save()', () => { - it('should save session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.save(); - ctx.body = 'hello'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('hello') - .expect(200, done); - }); - }); - }); - - describe('when session is', () => { - describe('null', () => { - it('should expire the session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = null; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('an empty object', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = {}; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('an object', () => { - it('should create a session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { message: 'hello' }; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('anything else', () => { - it('should throw', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - }); - - describe('when an error is thrown downstream and caught upstream', () => { - it('should still save the session', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - - app.use(async function(ctx, next) { - try { - await next(); - } catch (err) { - ctx.status = err.status; - ctx.body = err.message; - } - }); - - app.use(session(app)); - - app.use(async function(ctx, next) { - ctx.session.name = 'funny'; - await next(); - }); - - app.use(async function(ctx) { - ctx.throw(401); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(401, done); - }); - }); - - describe('when maxAge present', () => { - describe('and not expire', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - - describe('and expired', () => { - it('should expire the sess', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.status = 200; - return; - } - - ctx.body = ctx.session.message || ''; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - setTimeout(() => { - request(server) - .get('/') - .set('cookie', cookie) - .expect('', done); - }, 200); - }); - }); - }); - }); - - describe('ctx.session.maxAge', () => { - it('should return opt.maxAge', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - ctx.body = ctx.session.maxAge; - }); - - request(app.listen()) - .get('/') - .expect('100', done); - }); - }); - - describe('ctx.session.maxAge=', () => { - it('should set sessionOptions.maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.session.maxAge = 100; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save even session not change', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.maxAge = 100; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save when create session only with maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { maxAge: 100 }; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - }); - - describe('ctx.session.regenerate', () => { - it('should change the session key, but not content', done => { - const app = new App(); - const message = 'hi'; - app.use(async function(ctx, next) { - ctx.session = { message: 'hi' }; - await next(); - }); - - app.use(async function(ctx, next) { - const sessionKey = ctx.cookies.get('koa.sess'); - if (sessionKey) { - await ctx.session.regenerate(); - } - await next(); - }); - - app.use(async function(ctx) { - ctx.session.message.should.equal(message); - ctx.body = ''; - }); - let koaSession = null; - request(app.callback()) - .get('/') - .expect(200, (err, res) => { - should.not.exist(err); - koaSession = res.headers['set-cookie'][0]; - koaSession.should.containEql('koa.sess='); - request(app.callback()) - .get('/') - .set('Cookie', koaSession) - .expect(200, (err, res) => { - should.not.exist(err); - const cookies = res.headers['set-cookie'][0]; - cookies.should.containEql('koa.sess='); - cookies.should.not.equal(koaSession); - done(); - }); - }); - }); - }); - - describe('when get session before enter session middleware', () => { - it('should work', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(async function(ctx, next) { - ctx.session.foo = 'hi'; - await next(); - }); - app.use(session({}, app)); - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.callback()) - .get('/') - .expect(200, (err, res) => { - should.not.exist(err); - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies) - .expect(200, done); - }); - }); - }); - - describe('options.sameSite', () => { - it('should return opt.sameSite=none', done => { - const app = App({ sameSite: 'none' }); - - app.use(async function(ctx) { - ctx.session = { foo: 'bar' }; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect(res => { - const cookie = res.headers['set-cookie'].join('|'); - assert(cookie.includes('path=/; samesite=none; httponly')); - }) - .expect('bar') - .expect(200, done); - }); - - it('should return opt.sameSite=lax', done => { - const app = App({ sameSite: 'lax' }); - - app.use(async function(ctx) { - ctx.session = { foo: 'bar' }; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect(res => { - const cookie = res.headers['set-cookie'].join('|'); - assert(cookie.includes('path=/; samesite=lax; httponly')); - }) - .expect('bar') - .expect(200, done); - }); - }); - - describe('when valid and beforeSave set', () => { - it('should ignore session when uid changed', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({ - valid(ctx, sess) { - return ctx.cookies.get('uid') === sess.uid; - }, - beforeSave(ctx, sess) { - sess.uid = ctx.cookies.get('uid'); - }, - }, app)); - app.use(async function(ctx) { - if (!ctx.session.foo) { - ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); - } - - ctx.body = { - foo: ctx.session.foo, - uid: ctx.cookies.get('uid'), - }; - }); - - request(app.callback()) - .get('/') - .set('Cookie', 'uid=123') - .expect(200, (err, res) => { - should.not.exist(err); - const data = res.body; - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=123') - .expect(200) - .expect(data, err => { - should.not.exist(err); - - // should ignore uid:123 session and create a new session for uid:456 - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=456') - .expect(200, (err, res) => { - should.not.exist(err); - res.body.uid.should.equal('456'); - res.body.foo.should.not.equal(data.foo); - done(); - }); - }); - }); - }); - }); - - describe('when options.encode and options.decode are functions', () => { - describe('they are used to encode/decode stored cookie values', () => { - it('should work', done => { - let encodeCallCount = 0; - let decodeCallCount = 0; - - function encode(data) { - ++encodeCallCount; - return JSON.stringify({ enveloped: data }); - } - function decode(data) { - ++decodeCallCount; - return JSON.parse(data).enveloped; - } - - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - app.use(session({ - encode, - decode, - }, app)); - - app.use(async function(ctx) { - ctx.session.counter = (ctx.session.counter || 0) + 1; - ctx.body = ctx.session; - return; - }); - - request(app.callback()) - .get('/') - .expect(() => { encodeCallCount.should.above(0, 'encode was not called'); }) - .expect(200, (err, res) => { - should.not.exist(err); - res.body.counter.should.equal(1, 'expected body to be equal to session.counter'); - const cookies = res.headers['set-cookie'].join(';'); - request(app.callback()) - .get('/') - .set('Cookie', cookies) - .expect(() => { decodeCallCount.should.be.above(0, 'decode was not called'); }) - .expect(200, (err, res) => { - should.not.exist(err); - res.body.counter.should.equal(2); - done(); - }); - }); - }); - }); - }); - - describe('when rolling set to true', () => { - let app; - before(() => { - app = App({ rolling: true }); - - app.use(function* () { - console.log(this.path); - if (this.path === '/set') this.session = { foo: 'bar' }; - this.body = this.session; - }); - }); - - it('should not send set-cookie when session not exists', () => { - return request(app.callback()) - .get('/') - .expect({}) - .expect(res => { - should.not.exist(res.headers['set-cookie']); - }); - }); - - it('should send set-cookie when session exists and not change', done => { - request(app.callback()) - .get('/set') - .expect({ foo: 'bar' }) - .end((err, res) => { - should.not.exist(err); - res.headers['set-cookie'].should.have.length(2); - const cookie = res.headers['set-cookie'].join(';'); - request(app.callback()) - .get('/') - .set('cookie', cookie) - .expect(res => { - res.headers['set-cookie'].should.have.length(2); - }) - .expect({ foo: 'bar' }, done); - }); - }); - }); - - describe('init multi session middleware', () => { - it('should work', () => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - const s1 = session({}, app); - const s2 = session({}, app); - assert(s1); - assert(s2); - }); - }); -}); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - app.use(session(options, app)); - return app; -} diff --git a/test/cookie.test.ts b/test/cookie.test.ts new file mode 100644 index 0000000..add102e --- /dev/null +++ b/test/cookie.test.ts @@ -0,0 +1,900 @@ +import { strict as assert } from 'node:assert'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import session, { type CreateSessionOptions } from '../src/index.js'; + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + app.use(session(options, app)); + return app; +} + +const inspect = Symbol.for('nodejs.util.inspect.custom'); + +describe('Koa Session Cookie', () => { + let cookie: string; + + describe('when options.signed = true', () => { + describe('when app.keys are set', () => { + it('should work', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({}, app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hi'; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect(200); + }); + }); + + describe('when app.keys are not set', () => { + it('should throw and clean this cookie', async () => { + const app = new Koa(); + + app.use(session(app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hi'; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + + describe('when app not set', () => { + it('should throw', () => { + const app = new Koa(); + assert.throws(() => { + app.use((session as any)()); + }, /app instance required: `session\(opts, app\)`/); + assert.throws(() => { + app.use(session({})); + }, /app instance required: `session\(opts, app\)`/); + }); + }); + }); + + describe('when options.signed = false', () => { + describe('when app.keys are not set', () => { + it('should work', async () => { + const app = new Koa(); + + app.use(session({ signed: false }, app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hi'; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect(200); + }); + }); + }); + + describe('when the session contains a ;', () => { + it('should still work', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session!.string; + } + }); + + const server = app.callback(); + + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + // samesite is not set + assert(!cookie.join(';').includes('samesite')); + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session; + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hello'; + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + const newCookie = res.get('Set-Cookie')!; + // samesite is not set + assert(!newCookie.join(';').includes('samesite')); + }); + }); + }); + + describe('after session set to null with signed cookie', () => { + it('should return expired cookies', async () => { + const app = App({ + signed: true, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', + /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', + /koa\.sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200); + }); + }); + + describe('after session set to null without signed cookie', () => { + it('should return expired cookies', async () => { + const app = App({ + signed: false, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200); + }); + }); + + describe('when get session after set to null', () => { + it('should return null', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=;/) + .expect('true') + .expect(200); + }); + }); + + describe('when decode session', () => { + describe('SyntaxError', () => { + it('should create new session', async () => { + const app = App({ signed: false }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = String(ctx.session.isNew); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-session;') + .expect('true') + .expect(200); + }); + }); + + describe('Other Error', () => { + it('should throw', async () => { + const app = App({ + signed: false, + decode() { + throw new Error('decode error'); + }, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = String(ctx.session!.isNew); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-session;') + .expect('Set-Cookie', /koa\.sess=;/) + .expect(500); + }); + }); + }); + + describe('when encode session error', () => { + it('should throw', async () => { + const app = App({ + encode() { + throw new Error('encode error'); + }, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = 'hello'; + }); + + app.once('error', (err, ctx) => { + assert.equal(err.message, 'encode error'); + assert(ctx); + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = ctx.session![inspect](); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200); + }); + }); + + describe('.length', () => { + it('should return session length', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.length); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('1') + .expect(200); + }); + }); + + describe('.populated', () => { + it('should return session populated', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.populated); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('true') + .expect(200); + }); + }); + + describe('.save()', () => { + it('should save session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.save(); + ctx.body = 'hello'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('hello') + .expect(200); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = null; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('an object', () => { + it('should create a session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('anything else', () => { + it('should throw', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect(/Internal Server Error/) + .expect(500); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + try { + await next(); + } catch (err: any) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session(app)); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session.name = 'funny'; + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + ctx.throw(401); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(401); + }); + }); + + describe('when maxAge present', () => { + describe('and not expire', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session!.message; + }); + + const server = app.callback(); + + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and expired', () => { + it('should expire the sess', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session.message || ''; + }); + + const server = app.callback(); + + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await new Promise(resolve => setTimeout(resolve, 200)); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect(''); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session!.maxAge; + }); + + await request(app.callback()) + .get('/') + .expect('100'); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.session!.maxAge = 100; + ctx.body = ctx.session!.foo; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save even session not change', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.maxAge = 100; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save when create session only with maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + }); + + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', async () => { + const app = App(); + const message = 'hi'; + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session!.regenerate(); + } + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, message); + ctx.body = ''; + }); + let res = await request(app.callback()) + .get('/') + .expect(200); + + const koaSession = res.get('Set-Cookie')!.join(';'); + assert.match(koaSession, /koa\.sess=/); + res = await request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200); + + const cookies = res.get('Set-Cookie')!.join(';'); + assert.match(cookies, /koa\.sess=/); + assert.notEqual(cookies, koaSession); + }); + }); + + describe('when get session before enter session middleware', () => { + it('should work', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session!.foo = 'hi'; + await next(); + }); + app.use(session({}, app)); + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + const cookies = res.get('Set-Cookie')!.join(';'); + assert(cookies.includes('koa.sess=')); + + await request(app.callback()) + .get('/') + .set('Cookie', cookies) + .expect(200); + }); + }); + + describe('options.sameSite', () => { + it('should return opt.sameSite=none', async () => { + const app = App({ sameSite: 'none' }); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + const res = await request(app.callback()) + .get('/') + .expect('bar') + .expect(200); + const cookie = res.get('Set-Cookie')!.join('|'); + assert(cookie.includes('path=/; samesite=none; httponly')); + }); + + it('should return opt.sameSite=lax', async () => { + const app = App({ sameSite: 'lax' }); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + const res = await request(app.callback()) + .get('/') + .expect('bar') + .expect(200); + const cookie = res.get('Set-Cookie')!.join('|'); + assert(cookie.includes('path=/; samesite=lax; httponly')); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + }, app)); + app.use(async (ctx: Koa.Context) => { + if (!ctx.session!.foo) { + ctx.session!.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session!.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200); + + const data = res.body; + const cookies = res.get('Set-Cookie')!.join(';'); + assert(cookies.includes('koa.sess=')); + + await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data); + + // should ignore uid:123 session and create a new session for uid:456 + const res2 = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200); + + assert.equal(res2.body.uid, '456'); + assert.notEqual(res2.body.foo, data.foo); + }); + }); + + describe('when options.encode and options.decode are functions', () => { + describe('they are used to encode/decode stored cookie values', () => { + it('should work', async () => { + let encodeCallCount = 0; + let decodeCallCount = 0; + + function encode(data: any) { + ++encodeCallCount; + return JSON.stringify({ enveloped: data }); + } + function decode(data: string) { + ++decodeCallCount; + return JSON.parse(data).enveloped; + } + + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + app.use(session({ + encode, + decode, + }, app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.counter = (ctx.session!.counter || 0) + 1; + ctx.body = ctx.session; + return; + }); + + const res = await request(app.callback()) + .get('/') + .expect(() => { assert(encodeCallCount > 0, 'encode was not called'); }) + .expect(200); + + assert.equal(res.body.counter, 1, 'expected body to be equal to session.counter'); + const cookies = res.get('Set-Cookie')!.join(';'); + const res2 = await request(app.callback()) + .get('/') + .set('Cookie', cookies) + .expect(() => { assert(decodeCallCount > 0, 'decode was not called'); }) + .expect(200); + + assert.equal(res2.body.counter, 2); + }); + }); + }); + + describe('when rolling set to true', () => { + let app: Koa; + before(() => { + app = App({ rolling: true }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', async () => { + const res = await request(app.callback()) + .get('/') + .expect({}); + + assert.equal(res.headers['set-cookie'], undefined); + }); + + it('should send set-cookie when session exists and not change', async () => { + const res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + assert.equal(res.get('Set-Cookie')!.length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + const res2 = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + assert.equal(res2.headers['set-cookie'].length, 2); + }); + }); + + describe('init multi session middleware', () => { + it('should work', () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + const s1 = session({}, app); + const s2 = session({}, app); + assert(s1); + assert(s2); + }); + }); +}); diff --git a/test/externalkey.test.js b/test/externalkey.test.js deleted file mode 100644 index 3dfd8c1..0000000 --- a/test/externalkey.test.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; -const Koa = require('koa'); -const request = require('supertest'); -const assert = require('assert'); -const session = require('..'); -const store = require('./store'); -const TOKEN_KEY = 'User-Token'; - -describe('Koa Session External Key', () => { - describe('when the external key set/get is invalid', () => { - it('should throw a error', () => { - try { - new App({ - externalKey: {}, - }); - } catch (err) { - assert.equal(err.message, 'externalKey.get must be function'); - } - }); - }); - describe('custom get/set external key', () => { - it('should still work', done => { - const app = App(); - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - assert(ctx.session.externalKey); - } else { - ctx.body = ctx.session.string; - assert(ctx.session.externalKey === ctx.get(TOKEN_KEY)); - } - }); - const server = app.listen(); - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const token = res.get(TOKEN_KEY); - request(server) - .get('/') - .set(TOKEN_KEY, token) - .expect(';', done); - }); - }); - }); -}); -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.store = store; - options.externalKey = options.externalKey || { - get: ctx => ctx.get(TOKEN_KEY), - set: (ctx, value) => ctx.set(TOKEN_KEY, value), - }; - app.use(session(options, app)); - return app; -} diff --git a/test/externalkey.test.ts b/test/externalkey.test.ts new file mode 100644 index 0000000..ef69845 --- /dev/null +++ b/test/externalkey.test.ts @@ -0,0 +1,60 @@ +import { strict as assert } from 'node:assert'; +import Koa from 'koa'; +import { ZodError } from 'zod'; +import { request } from '@eggjs/supertest'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import store from './store.js'; + +const TOKEN_KEY = 'User-Token'; + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.store = store; + options.externalKey = options.externalKey ?? { + get: ctx => ctx.get(TOKEN_KEY), + set: (ctx, value) => ctx.set(TOKEN_KEY, value), + }; + app.use(session(options, app)); + return app; +} + +describe('Koa Session External Key', () => { + describe('when the external key set/get is invalid', () => { + it('should throw a error', () => { + assert.throws(() => { + App({ + externalKey: {} as any, + }); + }, err => { + assert(err instanceof ZodError); + assert.match(err.message, /externalKey/); + return true; + }); + }); + }); + + describe('custom get/set external key', () => { + it('should still work', async () => { + const app = App(); + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + assert(ctx.session.externalKey); + } else { + ctx.body = ctx.session.string; + assert.equal(ctx.session.externalKey, ctx.get(TOKEN_KEY)); + } + }); + const res = await request(app.callback()) + .post('/') + .expect(204); + const token = res.get(TOKEN_KEY)!; + await request(app.callback()) + .get('/') + .set(TOKEN_KEY as any, token) + .expect(';'); + }); + }); +}); diff --git a/test/genid_bench.js b/test/genid_bench.js deleted file mode 100644 index 8ff2900..0000000 --- a/test/genid_bench.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const Benchmark = require('benchmark'); -const uuid = require('uuid'); -const uid = require('uid-safe'); - -const suite = new Benchmark.Suite(); - -const genidByUid = () => `${Date.now()}-${uid.sync(24)}`; -const genidByUuidV1 = () => uuid.v1(); -const genidByUuidV4 = () => uuid.v4(); - -console.log('genidByUid() => %s', genidByUid()); -console.log('genidByUuidV1() => %s', genidByUuidV1()); -console.log('genidByUuidV4() => %s', genidByUuidV4()); - -// add tests -suite -.add('uid()', function() { - genidByUid(); -}) -.add('genidByUuidV1()', function() { - genidByUuidV1(); -}) -.add('genidByUuidV4()', function() { - genidByUuidV4(); -}) -// add listeners -.on('cycle', function(event) { - console.log(String(event.target)); -}) -.on('complete', function() { - console.log('Fastest is ' + this.filter('fastest').map('name')); -}) -// run async -.run({ async: true }); - -// genidByUid() => 1556529339180-DRnQyEqlYjGr_Zq_42fHpdFMlBfVlAoG -// genidByUuidV1() => 5af3b830-6a5f-11e9-91fb-abc918efca3d -// genidByUuidV4() => 27088fa8-8436-4c8b-aae7-76ba316db9e3 -// -// uid() x 260,850 ops/sec ±1.50% (84 runs sampled) -// genidByUuidV1() x 1,181,483 ops/sec ±0.93% (86 runs sampled) -// genidByUuidV4() x 301,840 ops/sec ±1.40% (83 runs sampled) -// Fastest is genidByUuidV1() diff --git a/test/store.js b/test/store.js deleted file mode 100644 index e344c72..0000000 --- a/test/store.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -const sessions = {}; - -module.exports = { - async get(key) { - return sessions[key]; - }, - - async set(key, value) { - sessions[key] = value; - }, - - async destroy(key) { - sessions[key] = undefined; - }, -}; diff --git a/test/store.test.js b/test/store.test.js deleted file mode 100644 index be3a5fe..0000000 --- a/test/store.test.js +++ /dev/null @@ -1,861 +0,0 @@ -'use strict'; - -const Koa = require('koa'); -const request = require('supertest'); -const should = require('should'); -const mm = require('mm'); -const session = require('..'); -const store = require('./store'); -const pedding = require('pedding'); -const assert = require('assert'); -const sleep = require('mz-modules/sleep'); - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -describe('Koa Session External Store', () => { - let cookie; - - describe('when the session contains a ;', () => { - it('should still work', done => { - const app = App(); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('new session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and not populated', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session; - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when populated', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - done(); - }); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - }); - - describe('saved session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed but not changed', () => { - it('should be the same session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, done); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and changed', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.money = '$$$'; - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - }); - - describe('when session is', () => { - describe('null', () => { - it('should expire the session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = null; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('an empty object', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = {}; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('an object', () => { - it('should create a session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { message: 'hello' }; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('anything else', () => { - it('should throw', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - }); - - describe('session', () => { - describe('.inspect()', () => { - it('should return session content', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = ctx.session[inspect](); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect({ foo: 'bar' }) - .expect(200, done); - }); - }); - - describe('.length', () => { - it('should return session length', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.length); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('1') - .expect(200, done); - }); - }); - - describe('.populated', () => { - it('should return session populated', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.populated); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('.save()', () => { - it('should save session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.save(); - ctx.body = 'hello'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('hello') - .expect(200, done); - }); - }); - }); - - describe('when an error is thrown downstream and caught upstream', () => { - it('should still save the session', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - - app.use(async function(ctx, next) { - try { - await next(); - } catch (err) { - ctx.status = err.status; - ctx.body = err.message; - } - }); - - app.use(session({ store }, app)); - - app.use(async function(ctx, next) { - ctx.session.name = 'funny'; - await next(); - }); - - app.use(async function(ctx) { - ctx.throw(401); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(401, done); - }); - }); - - describe('when maxAge present', () => { - describe('and set to be a session cookie', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 'session' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - it('should use the default maxAge when improper string given', done => { - const app = App({ maxAge: 'not the right string' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - describe('and not expire', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - - describe('and expired', () => { - it('should expire the sess', done => { - done = pedding(done, 2); - const app = App({ maxAge: 100 }); - app.on('session:expired', args => { - assert(args.key.match(/^\w+-/)); - assert(args.value); - assert(args.ctx); - done(); - }); - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.status = 200; - return; - } - - ctx.body = ctx.session.message || ''; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - setTimeout(() => { - request(server) - .get('/') - .set('cookie', cookie) - .expect('', done); - }, 200); - }); - }); - }); - }); - - describe('ctx.session.maxAge', () => { - it('should return opt.maxAge', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - ctx.body = ctx.session.maxAge; - }); - - request(app.listen()) - .get('/') - .expect('100', done); - }); - }); - - describe('ctx.session.maxAge=', () => { - it('should set sessionOptions.maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.session.maxAge = 100; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save even session not change', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.maxAge = 100; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save when create session only with maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { maxAge: 100 }; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - }); - - describe('ctx.session.regenerate', () => { - it('should change the session key, but not content', done => { - const app = new App(); - const message = 'hi'; - app.use(async function(ctx, next) { - ctx.session = { message: 'hi' }; - await next(); - }); - - app.use(async function(ctx, next) { - const sessionKey = ctx.cookies.get('koa.sess'); - if (sessionKey) { - await ctx.session.regenerate(); - } - await next(); - }); - - app.use(async function(ctx) { - ctx.session.message.should.equal(message); - ctx.body = ''; - }); - let koaSession = null; - request(app.callback()) - .get('/') - .expect(200, (err, res) => { - should.not.exist(err); - koaSession = res.headers['set-cookie'][0]; - koaSession.should.containEql('koa.sess='); - request(app.callback()) - .get('/') - .set('Cookie', koaSession) - .expect(200, (err, res) => { - should.not.exist(err); - const cookies = res.headers['set-cookie'][0]; - cookies.should.containEql('koa.sess='); - cookies.should.not.equal(koaSession); - done(); - }); - }); - }); - }); - - describe('when store return empty', () => { - it('should create new Session', done => { - done = pedding(done, 2); - const app = App({ signed: false }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - app.on('session:missed', args => { - assert(args.key === 'invalid-key'); - assert(args.ctx); - done(); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-key') - .expect('true') - .expect(200, done); - }); - }); - - describe('when valid and beforeSave set', () => { - it('should ignore session when uid changed', done => { - done = pedding(done, 2); - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({ - valid(ctx, sess) { - return ctx.cookies.get('uid') === sess.uid; - }, - beforeSave(ctx, sess) { - sess.uid = ctx.cookies.get('uid'); - }, - store, - }, app)); - app.use(async function(ctx) { - if (!ctx.session.foo) { - ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); - } - - ctx.body = { - foo: ctx.session.foo, - uid: ctx.cookies.get('uid'), - }; - }); - app.on('session:invalid', args => { - assert(args.key); - assert(args.value); - assert(args.ctx); - done(); - }); - - request(app.callback()) - .get('/') - .set('Cookie', 'uid=123') - .expect(200, (err, res) => { - should.not.exist(err); - const data = res.body; - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=123') - .expect(200) - .expect(data, err => { - should.not.exist(err); - - // should ignore uid:123 session and create a new session for uid:456 - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=456') - .expect(200, (err, res) => { - should.not.exist(err); - res.body.uid.should.equal('456'); - res.body.foo.should.not.equal(data.foo); - done(); - }); - }); - }); - }); - }); - - describe('ctx.session', () => { - after(mm.restore); - - it('can be mocked', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - mm(app.context, 'session', { - foo: 'bar', - }); - - request(app.listen()) - .get('/') - .expect({ - foo: 'bar', - }) - .expect(200, done); - }); - }); - - describe('when rolling set to true', () => { - let app; - before(() => { - app = App({ rolling: true }); - - app.use(async ctx => { - if (ctx.path === '/set') ctx.session = { foo: 'bar' }; - ctx.body = ctx.session; - }); - }); - - it('should not send set-cookie when session not exists', () => { - return request(app.callback()) - .get('/') - .expect({}) - .expect(res => { - should.not.exist(res.headers['set-cookie']); - }); - }); - - it('should send set-cookie when session exists and not change', done => { - request(app.callback()) - .get('/set') - .expect({ foo: 'bar' }) - .end((err, res) => { - should.not.exist(err); - res.headers['set-cookie'].should.have.length(2); - const cookie = res.headers['set-cookie'].join(';'); - request(app.callback()) - .get('/') - .set('cookie', cookie) - .expect(res => { - res.headers['set-cookie'].should.have.length(2); - }) - .expect({ foo: 'bar' }, done); - }); - }); - }); - - describe('when prefix present', () => { - it('should still work', done => { - const app = App({ prefix: 'sess:' }); - - app.use(async ctx => { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - cookie.join().should.match(/koa\.sess=sess:/); - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('when renew set to true', () => { - let app; - before(() => { - app = App({ renew: true, maxAge: 2000 }); - - app.use(async ctx => { - if (ctx.path === '/set') ctx.session = { foo: 'bar' }; - ctx.body = ctx.session; - }); - }); - - it('should not send set-cookie when session not exists', () => { - return request(app.callback()) - .get('/') - .expect({}) - .expect(res => { - should.not.exist(res.headers['set-cookie']); - }); - }); - - it('should send set-cookie when session near expire and not change', async () => { - let res = await request(app.callback()) - .get('/set') - .expect({ foo: 'bar' }); - - res.headers['set-cookie'].should.have.length(2); - const cookie = res.headers['set-cookie'].join(';'); - await sleep(1200); - res = await request(app.callback()) - .get('/') - .set('cookie', cookie) - .expect({ foo: 'bar' }); - res.headers['set-cookie'].should.have.length(2); - }); - - it('should not send set-cookie when session not near expire and not change', async () => { - let res = await request(app.callback()) - .get('/set') - .expect({ foo: 'bar' }); - - res.headers['set-cookie'].should.have.length(2); - const cookie = res.headers['set-cookie'].join(';'); - await sleep(500); - res = await request(app.callback()) - .get('/') - .set('cookie', cookie) - .expect({ foo: 'bar' }); - should.not.exist(res.headers['set-cookie']); - }); - }); - - describe('when get session before middleware', () => { - it('should return empty session', async () => { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - const options = {}; - options.store = store; - app.use(async (ctx, next) => { - // will not take effect - ctx.session.should.be.ok(); - ctx.session.foo = '123'; - await next(); - }); - app.use(session(options, app)); - app.use(async ctx => { - if (ctx.path === '/set') ctx.session = { foo: 'bar' }; - ctx.body = ctx.session; - }); - - let res = await request(app.callback()) - .get('/') - .expect({}); - - res = await request(app.callback()) - .get('/set') - .expect({ foo: 'bar' }); - - res.headers['set-cookie'].should.have.length(2); - const cookie = res.headers['set-cookie'].join(';'); - await sleep(1200); - res = await request(app.callback()) - .get('/') - .set('cookie', cookie) - .expect({ foo: 'bar' }); - }); - }); -}); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.store = store; - app.use(session(options, app)); - return app; -} diff --git a/test/store.test.ts b/test/store.test.ts new file mode 100644 index 0000000..39ba1b8 --- /dev/null +++ b/test/store.test.ts @@ -0,0 +1,800 @@ +import { strict as assert } from 'node:assert'; +import { scheduler } from 'node:timers/promises'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import { mm } from 'mm'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import store from './store.js'; + +const inspect = Symbol.for('nodejs.util.inspect.custom'); + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.store = store; + app.use(session(options, app)); + return app; +} + +describe('Koa Session External Store', () => { + let cookie: string; + + describe('when the session contains a ;', () => { + it('should still work', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session!.string; + } + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session; + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hello'; + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + + cookie = res.get('Set-Cookie')!.join(';'); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = null; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('an object', () => { + it('should create a session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('anything else', () => { + it('should throw', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = ctx.session![inspect](); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200); + }); + }); + + describe('.length', () => { + it('should return session length', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.length); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('1') + .expect(200); + }); + }); + + describe('.populated', () => { + it('should return session populated', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.populated); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('true') + .expect(200); + }); + }); + + describe('.save()', () => { + it('should save session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.save(); + ctx.body = 'hello'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('hello') + .expect(200); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + try { + await next(); + } catch (err: any) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session({ store }, app)); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session!.name = 'funny'; + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + ctx.throw(401); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(401); + }); + }); + + describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 'session' }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session!.message; + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + assert(!cookie.includes('expires=')); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and not expire', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session!.message; + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and expired', () => { + it('should expire the sess', async () => { + const app = App({ maxAge: 100 }); + app.on('session:expired', args => { + assert(args.key.match(/^\w+-/)); + assert(args.value); + assert(args.ctx); + }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session!.message || ''; + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await new Promise(resolve => setTimeout(resolve, 200)); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect(''); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session!.maxAge; + }); + + await request(app.callback()) + .get('/') + .expect('100'); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.session!.maxAge = 100; + ctx.body = ctx.session!.foo; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save even session not change', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.maxAge = 100; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save when create session only with maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + }); + + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', async () => { + const app = App(); + const message = 'hi'; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session!.regenerate(); + } + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, message); + ctx.body = ''; + }); + + let res = await request(app.callback()) + .get('/') + .expect(200); + + const koaSession = res.get('Set-Cookie')!.join(';'); + assert.match(koaSession, /koa\.sess=/); + res = await request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200); + + const cookies = res.get('Set-Cookie')!.join(';'); + assert.match(cookies, /koa\.sess=/); + assert.notEqual(cookies, koaSession); + }); + }); + + describe('when store return empty', () => { + it('should create new Session', async () => { + const app = App({ signed: false }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = String(ctx.session!.isNew); + }); + + app.on('session:missed', args => { + assert.equal(args.key, 'invalid-key'); + assert(args.ctx); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-key') + .expect('true') + .expect(200); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + store, + }, app)); + + app.use(async (ctx: Koa.Context) => { + if (!ctx.session!.foo) { + ctx.session!.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session!.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + app.on('session:invalid', args => { + assert(args.key); + assert(args.value); + assert(args.ctx); + }); + + let res = await request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200); + + const data = res.body; + const cookies = res.get('Set-Cookie')!.join(';'); + assert(cookies.includes('koa.sess=')); + + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200); + + assert.deepEqual(res.body, data); + + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200); + + assert.equal(res.body.uid, '456'); + assert.notEqual(res.body.foo, data.foo); + }); + }); + + describe('ctx.session', () => { + after(mm.restore); + + it('can be mocked', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + mm(app.context, 'session', { + foo: 'bar', + }); + + await request(app.callback()) + .get('/') + .expect({ + foo: 'bar', + }) + .expect(200); + }); + }); + + describe('when rolling set to true', () => { + let app: Koa; + before(() => { + app = App({ rolling: true }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', async () => { + const res = await request(app.callback()) + .get('/') + .expect({}); + + assert(!res.headers['set-cookie']); + }); + + it('should send set-cookie when session exists and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + assert.equal(res.headers['set-cookie'].length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + + assert.equal(res.headers['set-cookie'].length, 2); + }); + }); + + describe('when prefix present', () => { + it('should still work', async () => { + const app = App({ prefix: 'sess:' }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session!.string; + } + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + assert(cookie.join().includes('koa.sess=sess:')); + + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('when renew set to true', () => { + let app: Koa; + before(() => { + app = App({ renew: true, maxAge: 2000 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', async () => { + const res = await request(app.callback()) + .get('/') + .expect({}); + + assert(!res.headers['set-cookie']); + }); + + it('should send set-cookie when session near expire and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + assert.equal(res.get('Set-Cookie')!.length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + + await scheduler.wait(1200); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + + assert.equal(res.headers['set-cookie'].length, 2); + }); + + it('should not send set-cookie when session not near expire and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + assert.equal(res.headers['set-cookie'].length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + + await scheduler.wait(500); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when get session before middleware', () => { + it('should return empty session', async () => { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + const options = { store }; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + assert(ctx.session); + ctx.session.foo = '123'; + await next(); + }); + + app.use(session(options, app)); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + + let res = await request(app.callback()) + .get('/') + .expect({}); + + res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + const cookie = res.get('Set-Cookie')!.join(';'); + await scheduler.wait(1200); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + }); + }); +}); diff --git a/test/store.ts b/test/store.ts new file mode 100644 index 0000000..43ccf80 --- /dev/null +++ b/test/store.ts @@ -0,0 +1,15 @@ +const sessions: Record = {}; + +export default { + async get(key: string) { + return sessions[key]; + }, + + async set(key: string, value: unknown) { + sessions[key] = value; + }, + + async destroy(key: string) { + sessions[key] = undefined; + }, +}; diff --git a/test/store_with_ctx.test.js b/test/store_with_ctx.test.ts similarity index 59% rename from test/store_with_ctx.test.js rename to test/store_with_ctx.test.ts index 21fe43a..e7331a5 100644 --- a/test/store_with_ctx.test.js +++ b/test/store_with_ctx.test.ts @@ -1,40 +1,49 @@ -'use strict'; +import { strict as assert } from 'node:assert'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import store from './store_with_ctx.js'; -const Koa = require('koa'); -const request = require('supertest'); -const session = require('..'); -const store = require('./store_with_ctx'); +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.store = store; + app.use(async (ctx, next) => { + await next(); + ctx.body = ctx.state.test === undefined ? 'undefined' : ctx.state.test; + }); + + app.use(session(options, app)); + return app; +} -describe('Koa Session External Store methods can acceess Koa context', () => { - let cookie; +describe('Koa Session External Store methods can access Koa context', () => { + let cookie: string; describe('new session', () => { describe('when not accessed', () => { it('should not set ctx.state.test variable', async () => { const app = App(); - request(app.callback()) + await request(app.callback()) .get('/') .expect('undefined'); }); }); describe('when populated', () => { - it('should set ctx.state.test variable', done => { + it('should set ctx.state.test variable', async () => { const app = App(); app.use(async ctx => { if (ctx.path === '/set') ctx.session = { foo: 'bar' }; }); - request(app.listen()) - .get('/set') - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - res.text.should.equal('set'); - done(); - }); + const res = await request(app.callback()) + .get('/set') + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.equal(res.text, 'set'); }); }); @@ -42,7 +51,7 @@ describe('Koa Session External Store methods can acceess Koa context', () => { it('should access ctx.state.test variable', async () => { const app = App(); - request(app.callback()) + await request(app.callback()) .get('/') .set('Cookie', cookie) .expect('get'); @@ -54,10 +63,12 @@ describe('Koa Session External Store methods can acceess Koa context', () => { const app = App(); app.use(async ctx => { - if (ctx.path === '/destroy') ctx.session = null; + if (ctx.path === '/destroy') { + ctx.session = null; + } }); - request(app.callback()) + await request(app.callback()) .get('/destroy') .set('Cookie', cookie) .expect('destroyed'); @@ -65,17 +76,3 @@ describe('Koa Session External Store methods can acceess Koa context', () => { }); }); }); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.store = store; - app.use(async (ctx, next) => { - await next(); - ctx.body = ctx.state.test === undefined ? 'undefined' : ctx.state.test; - }); - - app.use(session(options, app)); - return app; -} diff --git a/test/store_with_ctx.js b/test/store_with_ctx.ts similarity index 55% rename from test/store_with_ctx.js rename to test/store_with_ctx.ts index 2ebd234..7e50c7b 100644 --- a/test/store_with_ctx.js +++ b/test/store_with_ctx.ts @@ -1,21 +1,19 @@ -'use strict'; +const sessions: Record = {}; -const sessions = {}; - -module.exports = { - async get(key, maxAge, options) { +export default { + async get(key: string, _maxAge: number, options: any) { // check access to options.ctx options.ctx.state.test = 'get'; return sessions[key]; }, - async set(key, sess, maxAge, options) { + async set(key: string, sess: Record, _maxAge: number, options: any) { // check access to options.ctx options.ctx.state.test = 'set'; sessions[key] = sess; }, - async destroy(key, options) { + async destroy(key: string, options: any) { // check access to options.ctx options.ctx.state.test = 'destroyed'; sessions[key] = undefined; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ff41b73 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +} From 6d068ffcb7a0976a8da6d70f32624575f9061393 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 19 Jan 2025 07:32:03 +0000 Subject: [PATCH 149/154] Release 7.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] ## [7.0.0](https://github.com/koajs/session/compare/v6.4.0...v7.0.0) (2025-01-19) ### ⚠ BREAKING CHANGES * drop Node.js < 18.19.0 support ### Features * support cjs and esm both by tshy ([#228](https://github.com/koajs/session/issues/228)) ([575864c](https://github.com/koajs/session/commit/575864c6ae7504da15e72e8112a99757f3eee188)) --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e86323..f2a9a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# Changelog + +## [7.0.0](https://github.com/koajs/session/compare/v6.4.0...v7.0.0) (2025-01-19) + + +### ⚠ BREAKING CHANGES + +* drop Node.js < 18.19.0 support + +### Features + +* support cjs and esm both by tshy ([#228](https://github.com/koajs/session/issues/228)) ([575864c](https://github.com/koajs/session/commit/575864c6ae7504da15e72e8112a99757f3eee188)) 6.4.0 / 2023-02-04 ================== diff --git a/package.json b/package.json index 725d6c3..eea068b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git@github.com:koajs/session.git" }, - "version": "6.4.0", + "version": "7.0.0", "keywords": [ "koa", "middleware", From 3421008381a3aacf2e43d52314749eb850851193 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 19 Jan 2025 16:16:45 +0800 Subject: [PATCH 150/154] fix: remove unused dependencies (#229) --- __snapshots__/index.test.ts.js | 9 +++++++++ package.json | 3 +-- src/index.ts | 8 ++++---- test/index.test.ts | 11 +++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 __snapshots__/index.test.ts.js create mode 100644 test/index.test.ts diff --git a/__snapshots__/index.test.ts.js b/__snapshots__/index.test.ts.js new file mode 100644 index 0000000..6b9c1e0 --- /dev/null +++ b/__snapshots__/index.test.ts.js @@ -0,0 +1,9 @@ +exports['test/index.test.ts SessionOptions schema should have a valid schema 1'] = { + "key": "koa.sess", + "autoCommit": true, + "overwrite": true, + "httpOnly": true, + "signed": true, + "rolling": false, + "renew": false +} diff --git a/package.json b/package.json index eea068b..c92a61c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "koa": "2", "mm": "4", "rimraf": "6", + "snap-shot-it": "^7.9.10", "tshy": "3", "tshy-after": "1", "typescript": "5" @@ -33,9 +34,7 @@ "license": "MIT", "dependencies": { "crc": "^3.8.0", - "debug": "^4.3.3", "is-type-of": "^2.2.0", - "uuid": "^8.3.2", "zod": "^3.24.1" }, "engines": { diff --git a/src/index.ts b/src/index.ts index 0652ffe..e116660 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ const debug = debuglog('koa-session'); const GET_CONTEXT_SESSION = Symbol('get contextSession'); const CONTEXT_SESSION_INSTANCE = Symbol('contextSession instance'); -const SessionOptionsSchema = z.object({ +export const SessionOptions = z.object({ /** * cookie key * Default is `koa.sess` @@ -159,9 +159,9 @@ const SessionOptionsSchema = z.object({ .optional(), }); -const DEFAULT_SESSION_OPTIONS = SessionOptionsSchema.parse({}); +const DEFAULT_SESSION_OPTIONS = SessionOptions.parse({}); -export type SessionOptions = z.infer; +export type SessionOptions = z.infer; export type CreateSessionOptions = Partial; type Middleware = (ctx: any, next: any) => Promise; @@ -199,7 +199,7 @@ export function createSession(opts: CreateSessionOptions | any, app: any): Middl ...DEFAULT_SESSION_OPTIONS, ...opts, }; - SessionOptionsSchema.parse(options); + SessionOptions.parse(options); options = formatOptions(options); extendContext(app.context, options); diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..3b49ed6 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,11 @@ +import snapshot from 'snap-shot-it'; +import { SessionOptions } from '../src/index.js'; + +describe('test/index.test.ts', () => { + describe('SessionOptions schema', () => { + it('should have a valid schema', () => { + const parsed = SessionOptions.parse({}); + snapshot(parsed); + }); + }); +}); From 6dfdcf675f28d2661bbeb59b9d42bf5c5afdb47f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 19 Jan 2025 08:17:28 +0000 Subject: [PATCH 151/154] Release 7.0.1 [skip ci] ## [7.0.1](https://github.com/koajs/session/compare/v7.0.0...v7.0.1) (2025-01-19) ### Bug Fixes * remove unused dependencies ([#229](https://github.com/koajs/session/issues/229)) ([3421008](https://github.com/koajs/session/commit/3421008381a3aacf2e43d52314749eb850851193)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a9a24..0f1bd57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [7.0.1](https://github.com/koajs/session/compare/v7.0.0...v7.0.1) (2025-01-19) + + +### Bug Fixes + +* remove unused dependencies ([#229](https://github.com/koajs/session/issues/229)) ([3421008](https://github.com/koajs/session/commit/3421008381a3aacf2e43d52314749eb850851193)) + ## [7.0.0](https://github.com/koajs/session/compare/v6.4.0...v7.0.0) (2025-01-19) diff --git a/package.json b/package.json index c92a61c..2cfa6bc 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git@github.com:koajs/session.git" }, - "version": "7.0.0", + "version": "7.0.1", "keywords": [ "koa", "middleware", From c45bf8b236f8b79f4d2e4ed40b7fae585f30330b Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 19 Jan 2025 17:34:26 +0800 Subject: [PATCH 152/154] fix: make sure options instance is not mutated (#230) --- src/index.ts | 19 +++++++++++-------- test/store.test.ts | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index e116660..74ec11a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -188,19 +188,23 @@ export function createSession(opts: CreateSessionOptions | any, app: any): Middl throw new TypeError('app instance required: `session(opts, app)`'); } + const options: SessionOptions = opts ?? {}; + // back-compat maxage - if (opts && !('maxAge' in opts) && 'maxage' in opts) { - Reflect.set(opts, 'maxAge', Reflect.get(opts, 'maxage')); + if (!('maxAge' in options) && 'maxage' in options) { + Reflect.set(options, 'maxAge', Reflect.get(options, 'maxage')); if (process.env.NODE_ENV !== 'production') { - console.warn('DeprecationWarning: `maxage` option has been renamed to `maxAge`'); + console.warn('[koa-session] DeprecationWarning: `maxage` option has been renamed to `maxAge`'); } } - let options = { + + // keep backwards compatibility: make sure options instance is not mutated + Object.assign(options, { ...DEFAULT_SESSION_OPTIONS, - ...opts, - }; + ...options, + }); SessionOptions.parse(options); - options = formatOptions(options); + formatOptions(options); extendContext(app.context, options); return async function session(ctx: any, next: any) { @@ -264,7 +268,6 @@ function formatOptions(opts: SessionOptions) { opts.genid = () => randomUUID(); } } - return opts; } /** diff --git a/test/store.test.ts b/test/store.test.ts index 39ba1b8..3f300ab 100644 --- a/test/store.test.ts +++ b/test/store.test.ts @@ -11,7 +11,7 @@ const inspect = Symbol.for('nodejs.util.inspect.custom'); function App(options: CreateSessionOptions = {}) { const app = new Koa(); app.keys = [ 'a', 'b' ]; - options.store = store; + options.store = options.store ?? store; app.use(session(options, app)); return app; } @@ -21,14 +21,15 @@ describe('Koa Session External Store', () => { describe('when the session contains a ;', () => { it('should still work', async () => { - const app = App(); + const options: CreateSessionOptions = { store }; + const app = App(options); app.use(async (ctx: Koa.Context) => { if (ctx.method === 'POST') { - ctx.session!.string = ';'; + ctx.session.string = ';'; ctx.status = 204; } else { - ctx.body = ctx.session!.string; + ctx.body = ctx.session.string; } }); @@ -43,6 +44,37 @@ describe('Koa Session External Store', () => { .set('Cookie', cookie.join(';')) .expect(';'); }); + + it('should disable store on options', async () => { + const options: CreateSessionOptions = { store }; + const app = App(options); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string ?? 'new session create'; + } + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + + options.store = undefined; + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect('new session create'); + }); }); describe('new session', () => { From 36beadadf9db645ae5a0c8544544e4e6cfb203cc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 19 Jan 2025 09:35:09 +0000 Subject: [PATCH 153/154] Release 7.0.2 [skip ci] ## [7.0.2](https://github.com/koajs/session/compare/v7.0.1...v7.0.2) (2025-01-19) ### Bug Fixes * make sure options instance is not mutated ([#230](https://github.com/koajs/session/issues/230)) ([c45bf8b](https://github.com/koajs/session/commit/c45bf8b236f8b79f4d2e4ed40b7fae585f30330b)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1bd57..10dfcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [7.0.2](https://github.com/koajs/session/compare/v7.0.1...v7.0.2) (2025-01-19) + + +### Bug Fixes + +* make sure options instance is not mutated ([#230](https://github.com/koajs/session/issues/230)) ([c45bf8b](https://github.com/koajs/session/commit/c45bf8b236f8b79f4d2e4ed40b7fae585f30330b)) + ## [7.0.1](https://github.com/koajs/session/compare/v7.0.0...v7.0.1) (2025-01-19) diff --git a/package.json b/package.json index 2cfa6bc..808789e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git@github.com:koajs/session.git" }, - "version": "7.0.1", + "version": "7.0.2", "keywords": [ "koa", "middleware", From 968ae94b8a1f3f8ed53cb1e6dbab1c1c6704329f Mon Sep 17 00:00:00 2001 From: "Sixia \"Leask\" Huang" Date: Sun, 6 Apr 2025 18:00:42 -0400 Subject: [PATCH 154/154] Update Readme.md (#231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Providing `nul` as `sameSite` will lead to errors. Zod requires sameSite to be a string, so the description in the documentation was inaccurate and has been corrected. ``` ZodError: [ { "code": "invalid_type", "expected": "string", "received": "null", "path": [ "sameSite" ], "message": "Expected string, received null" } ] ``` ## Checklist - [✅] I have ensured my pull request is not behind the main or master branch of the original repository. - [✅] I have rebased all commits where necessary so that reviewing this pull request can be done without having to merge it first. - [✅] I have written a commit message that passes commitlint linting. - [✅] I have ensured that my code changes pass linting tests. - [✅] I have ensured that my code changes pass unit tests. - [✅] I have described my pull request and the reasons for code changes along with context if necessary. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 34ee099..61c14ac 100644 --- a/Readme.md +++ b/Readme.md @@ -58,7 +58,7 @@ const CONFIG = { rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ secure: true, /** (boolean) secure cookie*/ - sameSite: null, /** (string) session cookie sameSite options (default null, don't set it) */ + sameSite: null, /** (string) session cookie sameSite options (default null, do not provide this key if you are not restricting sameSite) */ }; app.use(session(CONFIG, app));