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; +}