Skip to content

Commit

Permalink
Merge pull request #31 from koajs/session-expire
Browse files Browse the repository at this point in the history
set expire in cookie value
  • Loading branch information
dead-horse committed Dec 25, 2014
2 parents 799f846 + f8c9580 commit 39e93ea
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 18 deletions.
8 changes: 8 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand Down
84 changes: 70 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
*/

var debug = require('debug')('koa-session');
var deepEqual = require('deep-equal');

var ONE_DAY = 24 * 60 * 60 * 1000;

/**
* Initialize session middleware with `opts`:
Expand All @@ -28,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;
Expand All @@ -40,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(){
Expand All @@ -51,12 +56,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.
Expand All @@ -65,13 +72,15 @@ 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');
sess = new Session(this);
}

this._sess = sess;
this._prevjson = json;
return sess;
});

Expand All @@ -82,12 +91,17 @@ 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) {
throw err;
} finally {
commit(this, this._sessjson, this._sess, opts);
commit(this, this._prevjson, this._sess, opts);
}
}
};
Expand All @@ -96,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;

Expand All @@ -113,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();
}

/**
Expand All @@ -129,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];
}
}
}

/**
Expand Down Expand Up @@ -158,15 +180,16 @@ 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
*/

Session.prototype.changed = function(prev){
if (!prev) return true;
this._json = encode(this);
return this._json != prev;
delete prev._expire;
delete prev._maxAge;
return !deepEqual(prev, this.toJSON());
};

/**
Expand All @@ -192,6 +215,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.
Expand All @@ -201,10 +246,16 @@ 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 || ONE_DAY;
json._expire = maxAge + Date.now();
json._maxAge = maxAge;

json = encode(json);
debug('save %s', json);
ctx.cookies.set(key, json, opts);
};
Expand All @@ -219,7 +270,12 @@ 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;
return json;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
101 changes: 98 additions & 3 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,14 +344,109 @@ 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);
})
})
})
})

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) {
var app = koa();
app.keys = ['a', 'b'];
app.use(session(options, app));
// app.on('error', function (err) {
// console.log(err.stack);
// });
return app;
}

0 comments on commit 39e93ea

Please sign in to comment.