Skip to content

Commit fbade01

Browse files
committed
session: call store.touch() when manually touched
1 parent 66634e9 commit fbade01

File tree

4 files changed

+286
-16
lines changed

4 files changed

+286
-16
lines changed

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,26 @@ The default value is `undefined`.
210210
if there is a direct TLS/SSL connection.
211211
- `undefined` Uses the "trust proxy" setting from express
212212

213+
##### propagateTouch
214+
215+
Default: `false`. The default is `false` for backwards compatibility reasons
216+
only; you are encouraged to set this to `true`. Using `false` is deprecated; the
217+
default behavior will change to `true` in a future version and this option will
218+
be removed.
219+
220+
If `true`, calling `req.session.touch()` also does the following:
221+
222+
- Suppresses the middleware's automatic call to `req.session.touch()`
223+
(assuming it hasn't already happened).
224+
- Immediately calls `store.touch()` if the session is initialized (changed
225+
from its default state) or if `saveUninitialized` is enabled.
226+
- Suppresses the middleware's automatic call to `store.touch()` (assuming it
227+
hasn't already happened) if a call to `store.touch()` was attempted by
228+
`req.session.touch()`.
229+
230+
If `false`, `req.session.touch()` will not call `store.touch()` nor will it
231+
suppress the automatic calls to `req.session.touch()` and `store.touch()`.
232+
213233
##### resave
214234

215235
Forces the session to be saved back to the session store, even if the session
@@ -387,10 +407,12 @@ req.session.save(function(err) {
387407
})
388408
```
389409

390-
#### Session.touch()
410+
#### Session.touch(callback)
391411

392-
Updates the `.maxAge` property. Typically this is
393-
not necessary to call, as the session middleware does this for you.
412+
Updates the `.maxAge` property and maybe calls `store.touch()` (see the
413+
`propagateTouch` option). It is not usually necessary to call this method, as
414+
the session middleware does it for you. The callback is optional; if provided,
415+
it will be called with an error argument when done.
394416

395417
### req.session.id
396418

index.js

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ var defer = typeof setImmediate === 'function'
7474
* @param {Function} [options.genid]
7575
* @param {String} [options.name=connect.sid] Session ID cookie name
7676
* @param {Boolean} [options.proxy]
77+
* @param {Boolean} [options.propagateTouch] Whether session.touch() should call store.touch()
7778
* @param {Boolean} [options.resave] Resave unmodified sessions back to the store
7879
* @param {Boolean} [options.rolling] Enable/disable rolling session expiration
7980
* @param {Boolean} [options.saveUninitialized] Save uninitialized sessions to the store
@@ -96,6 +97,11 @@ function session(options) {
9697
// get the session cookie name
9798
var name = opts.name || opts.key || 'connect.sid'
9899

100+
var propagateTouch = opts.propagateTouch;
101+
if (!propagateTouch) {
102+
deprecate('falsy propagateTouch option; set to true');
103+
}
104+
99105
// get the session store
100106
var store = opts.store || new MemoryStore()
101107

@@ -209,6 +215,19 @@ function session(options) {
209215
var originalId;
210216
var savedHash;
211217
var touched = false
218+
var touchedStore = false;
219+
220+
function autoTouch() {
221+
if (touched) return;
222+
// For legacy reasons, auto-touch does not touch the session in the store. That is done later.
223+
var backup = propagateTouch;
224+
propagateTouch = false;
225+
try {
226+
req.session.touch();
227+
} finally {
228+
propagateTouch = backup;
229+
}
230+
}
212231

213232
// expose store
214233
req.sessionStore = store;
@@ -233,11 +252,7 @@ function session(options) {
233252
return;
234253
}
235254

236-
if (!touched) {
237-
// touch session
238-
req.session.touch()
239-
touched = true
240-
}
255+
autoTouch();
241256

242257
// set cookie
243258
setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data);
@@ -325,11 +340,7 @@ function session(options) {
325340
return _end.call(res, chunk, encoding);
326341
}
327342

328-
if (!touched) {
329-
// touch session
330-
req.session.touch()
331-
touched = true
332-
}
343+
autoTouch();
333344

334345
if (shouldSave(req)) {
335346
req.session.save(function onsave(err) {
@@ -394,6 +405,7 @@ function session(options) {
394405
function wrapmethods(sess) {
395406
var _reload = sess.reload
396407
var _save = sess.save;
408+
var _touch = sess.touch;
397409

398410
function reload(callback) {
399411
debug('reloading %s', this.id)
@@ -406,6 +418,21 @@ function session(options) {
406418
_save.apply(this, arguments);
407419
}
408420

421+
function touch(callback) {
422+
debug('touching %s', this.id);
423+
var cb = callback || function (err) { if (err) throw err; };
424+
var touchStore = propagateTouch && storeImplementsTouch &&
425+
// Don't touch the store unless the session has been or will be written to the store.
426+
(saveUninitializedSession || isModified(this) || isSaved(this));
427+
_touch.call(this, touchStore ? (function (err) {
428+
if (err) return cb(err);
429+
store.touch(this.id, this, cb);
430+
touchedStore = true; // Set synchronously regardless of success/failure.
431+
}).bind(this) : cb);
432+
touched = true; // Set synchronously regardless of success/failure.
433+
return this;
434+
}
435+
409436
Object.defineProperty(sess, 'reload', {
410437
configurable: true,
411438
enumerable: false,
@@ -419,6 +446,13 @@ function session(options) {
419446
value: save,
420447
writable: true
421448
});
449+
450+
Object.defineProperty(sess, 'touch', {
451+
configurable: true,
452+
enumerable: false,
453+
value: touch,
454+
writable: true
455+
});
422456
}
423457

424458
// check if session has been modified
@@ -457,7 +491,7 @@ function session(options) {
457491
return false;
458492
}
459493

460-
return cookieId === req.sessionID && !shouldSave(req);
494+
return !touchedStore && cookieId === req.sessionID && !shouldSave(req);
461495
}
462496

463497
// determine if cookie should be set on response

session/session.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77

88
'use strict';
99

10+
/**
11+
* Node.js 0.8+ async implementation.
12+
* @private
13+
*/
14+
15+
/* istanbul ignore next */
16+
var defer = typeof setImmediate === 'function'
17+
? setImmediate
18+
: function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
19+
1020
/**
1121
* Expose Session.
1222
*/
@@ -40,12 +50,15 @@ function Session(req, data) {
4050
* the cookie from expiring when the
4151
* session is still active.
4252
*
53+
* @param {Function} fn optional done callback
4354
* @return {Session} for chaining
4455
* @api public
4556
*/
4657

47-
defineMethod(Session.prototype, 'touch', function touch() {
48-
return this.resetMaxAge();
58+
defineMethod(Session.prototype, 'touch', function touch(fn) {
59+
this.resetMaxAge();
60+
if (fn) defer(fn);
61+
return this;
4962
});
5063

5164
/**

0 commit comments

Comments
 (0)