Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!**
Expand Down
5 changes: 3 additions & 2 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -320,6 +320,7 @@ class ContextSession {
}
await this.store.set(externalKey, json, maxAge, {
changed,
ctx: this.ctx,
rolling: opts.rolling,
});
if (opts.externalKey) {
Expand Down
23 changes: 23 additions & 0 deletions test/store_with_ctx.js
Original file line number Diff line number Diff line change
@@ -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;
},
};
81 changes: 81 additions & 0 deletions test/store_with_ctx.test.js
Original file line number Diff line number Diff line change
@@ -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;
}