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);