Skip to content

Commit

Permalink
implement checkTouchModified option
Browse files Browse the repository at this point in the history
  • Loading branch information
shahyar committed Jul 25, 2021
1 parent d198719 commit dfecc53
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 5 deletions.
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -269,6 +269,19 @@ will add an empty Passport object to the session for use after a user is
authenticated, which will be treated as a modification to the session, causing
it to be saved. *This has been fixed in PassportJS 0.3.0*

##### checkTouchModified

If set to `false`, unmodified sessions are always touched. However, when
`rolling` is disabled, the expiration date in the cookie can differ from that
which is in the store, since the cookie expiration date is only updated if the
session is modified, but the session in the store is updated regardless.

If set to `true` and the store supports "touching", unmodified sessions are not
touched in the store at the end of each request. Manually calling
`session.touch()` will extend the cookie expiry, and call `store.touch()`, which
should implement server-side expiry extension.

The default value is `false`.
##### secret

**Required option**
Expand Down
20 changes: 15 additions & 5 deletions index.js
Expand Up @@ -70,6 +70,7 @@ var defer = typeof setImmediate === 'function'
* Setup session store with the given `options`.
*
* @param {Object} [options]
* @param {Boolean} [options.checkTouchModified] Enable check preventing touch if session is unmodified
* @param {Object} [options.cookie] Options for cookie
* @param {Function} [options.genid]
* @param {String} [options.name=connect.sid] Session ID cookie name
Expand All @@ -87,6 +88,9 @@ var defer = typeof setImmediate === 'function'
function session(options) {
var opts = options || {}

// get the touch modification checker option
var checkTouchModified = opts.checkTouchModified

// get the cookie options
var cookieOptions = opts.cookie || {}

Expand Down Expand Up @@ -207,6 +211,7 @@ function session(options) {

var originalHash;
var originalId;
var originalExpires;
var savedHash;
var touched = false

Expand All @@ -228,8 +233,10 @@ function session(options) {
return false;
}

// Instead of getting the hash multiple times, only call when needed, and then cache it
var hashCache;
function getHash() {
return hash(req.session);
return hashCache ? hashCache : (hashCache = hash(req.session));
}

if (!shouldSetCookie(req, getHash)) {
Expand All @@ -242,7 +249,7 @@ function session(options) {
return;
}

if (!touched) {
if (!touched && !checkTouchModified) {
// touch session
req.session.touch()
touched = true
Expand Down Expand Up @@ -340,7 +347,7 @@ function session(options) {
return _end.call(res, chunk, encoding);
}

if (!touched) {
if (!touched && !checkTouchModified) {
// touch session
req.session.touch()
touched = true
Expand All @@ -349,7 +356,7 @@ function session(options) {
// Instead of getting the hash multiple times, only call when needed, and then cache it
var hashCache;
function getHash() {
return hashCache ? hashCache : hash(req.session);
return hashCache ? hashCache : (hashCache = hash(req.session));
}

if (shouldSave(req, getHash)) {
Expand Down Expand Up @@ -385,6 +392,7 @@ function session(options) {
store.generate(req);
originalId = req.sessionID;
originalHash = hash(req.session);
originalExpires = req.session.cookie.expires;
wrapmethods(req.session);
}

Expand All @@ -393,6 +401,7 @@ function session(options) {
store.createSession(req, sess)
originalId = req.sessionID
originalHash = hash(sess)
originalExpires = req.session.cookie.expires

if (!resaveSession) {
savedHash = originalHash
Expand Down Expand Up @@ -466,7 +475,8 @@ function session(options) {

// determine if session should be touched
function shouldTouch(req, getHash) {
return cookieId === req.sessionID && !shouldSave(req, getHash);
return cookieId === req.sessionID && !shouldSave(req, getHash) &&
(!checkTouchModified || originalExpires !== req.session.cookie.expires || isModified(req.session, getHash));
}

// determine if cookie should be set on response
Expand Down
147 changes: 147 additions & 0 deletions test/session.js
Expand Up @@ -1192,6 +1192,153 @@ describe('session()', function(){
})
});

describe('checkTouchModified option', function(){
describe('when false', function(){
it('should touch unmodified sessions', function(done){
var store = new session.MemoryStore()
var server = createServer({ checkTouchModified: false, store: store, resave: false }, function (req, res) {
req.session.user = 'bob';
res.end()
})

var touched = false
var _touch = store.touch;
store.touch = function touch(sid, sess, callback) {
touched = true;
_touch.call(store, sid, sess, callback);
}

assert.strictEqual(touched, false);

request(server)
.get('/')
.expect(shouldSetCookie('connect.sid'))
.expect(200, function(err, res){
if (err) return done(err);

request(server)
.get('/')
.set('Cookie', cookie(res))
.expect(200, function (err, res) {
assert.strictEqual(touched, true);
done();
})
});
});
});

describe('when true', function(){
it('should not touch unmodified sessions', function(done){
var store = new session.MemoryStore()
var server = createServer({ checkTouchModified: true, store: store, resave: false }, function (req, res) {
if (!req.session.user) {
req.session.user = 'bob';
}
res.end()
})

var touched = false
var _touch = store.touch;
store.touch = function touch(sid, sess, callback) {
touched = true;
_touch.call(store, sid, sess, callback);
}

assert.strictEqual(touched, false);

request(server)
.get('/')
.expect(shouldSetCookie('connect.sid'))
.expect(200, function(err, res){
if (err) return done(err);

setTimeout(function () {
request(server)
.get('/')
.set('Cookie', cookie(res))
.expect(200, function (err, res) {
assert.strictEqual(touched, false);
done();
})
}, 10);
});
});

it('should touch manually touched sessions', function(done){
var store = new session.MemoryStore()
var server = createServer({ checkTouchModified: true, store: store, resave: false }, function (req, res) {
if (!req.session.user) {
req.session.touch();
} else {
req.session.user = 'bob';
}
res.end()
})

var touched = false
var _touch = store.touch;
store.touch = function touch(sid, sess, callback) {
touched = true;
_touch.call(store, sid, sess, callback);
}

assert.strictEqual(touched, false);

request(server)
.get('/')
.expect(shouldSetCookie('connect.sid'))
.expect(200, function(err, res){
if (err) return done(err);

setTimeout(function () {
request(server)
.get('/')
.set('Cookie', cookie(res))
.expect(200, function (err, res) {
assert.strictEqual(touched, true);
done();
})
}, 10);
});
});

it('should not touch modified sessions, but save them instead', function(done){
var store = new session.MemoryStore()
var server = createServer({ checkTouchModified: true, store: store, resave: false }, function (req, res) {
req.session.count = req.session.count || 0
req.session.count++
res.end('hits: ' + req.session.count)
})

var touched = false
var _touch = store.touch;
store.touch = function touch(sid, sess, callback) {
touched = true;
_touch.call(store, sid, sess, callback);
}

assert.strictEqual(touched, false);

request(server)
.get('/')
.expect(shouldSetCookie('connect.sid'))
.expect(200, 'hits: 1', function(err, res){
if (err) return done(err);

setTimeout(function () {
request(server)
.get('/')
.set('Cookie', cookie(res))
.expect(200, 'hits: 2', function (err, res) {
assert.strictEqual(touched, false);
done();
})
}, 10);
});
});
});
});

describe('secret option', function () {
it('should reject empty arrays', function () {
assert.throws(createServer.bind(null, { secret: [] }), /secret option array/);
Expand Down

0 comments on commit dfecc53

Please sign in to comment.