Skip to content

Commit

Permalink
session: call store.touch() when manually touched
Browse files Browse the repository at this point in the history
  • Loading branch information
rhansen committed Dec 30, 2021
1 parent 66634e9 commit 0477b5a
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 16 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,26 @@ The default value is `undefined`.
if there is a direct TLS/SSL connection.
- `undefined` Uses the "trust proxy" setting from express

##### propagateTouch

Default: `false`. The default is `false` for backwards compatibility reasons
only; you are encouraged to set this to `true`. Using `false` is deprecated; the
default behavior will change to `true` in a future version and this option will
be removed.

If `true`, calling `req.session.touch()` also does the following:

- Suppresses the middleware's automatic call to `req.session.touch()`
(assuming it hasn't already happened).
- Immediately calls `store.touch()` if the session is initialized (changed
from its default state) or if `saveUninitialized` is enabled.
- Suppresses the middleware's automatic call to `store.touch()` (assuming it
hasn't already happened) if a call to `store.touch()` was attempted by
`req.session.touch()`.

If `false`, `req.session.touch()` will not call `store.touch()` nor will it
suppress the automatic calls to `req.session.touch()` and `store.touch()`.

##### resave

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

#### Session.touch()
#### Session.touch(callback)

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

### req.session.id

Expand Down
56 changes: 45 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ var defer = typeof setImmediate === 'function'
* @param {Function} [options.genid]
* @param {String} [options.name=connect.sid] Session ID cookie name
* @param {Boolean} [options.proxy]
* @param {Boolean} [options.propagateTouch] Whether session.touch() should call store.touch()
* @param {Boolean} [options.resave] Resave unmodified sessions back to the store
* @param {Boolean} [options.rolling] Enable/disable rolling session expiration
* @param {Boolean} [options.saveUninitialized] Save uninitialized sessions to the store
Expand All @@ -96,6 +97,11 @@ function session(options) {
// get the session cookie name
var name = opts.name || opts.key || 'connect.sid'

var propagateTouch = opts.propagateTouch;
if (!propagateTouch) {
deprecate('falsy propagateTouch option; set to true');
}

// get the session store
var store = opts.store || new MemoryStore()

Expand Down Expand Up @@ -209,6 +215,19 @@ function session(options) {
var originalId;
var savedHash;
var touched = false
var touchedStore = false;

function autoTouch() {
if (touched) return;
// For legacy reasons, auto-touch does not touch the session in the store. That is done later.
var backup = propagateTouch;
propagateTouch = false;
try {
req.session.touch();
} finally {
propagateTouch = backup;
}
}

// expose store
req.sessionStore = store;
Expand All @@ -233,11 +252,7 @@ function session(options) {
return;
}

if (!touched) {
// touch session
req.session.touch()
touched = true
}
autoTouch();

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

if (!touched) {
// touch session
req.session.touch()
touched = true
}
autoTouch();

if (shouldSave(req)) {
req.session.save(function onsave(err) {
Expand Down Expand Up @@ -394,6 +405,7 @@ function session(options) {
function wrapmethods(sess) {
var _reload = sess.reload
var _save = sess.save;
var _touch = sess.touch;

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

function touch(callback) {
debug('touching %s', this.id);
var cb = callback || function (err) { if (err) throw err; };
var touchStore = propagateTouch && storeImplementsTouch &&
// Don't touch the store if the session won't be saved to the store.
(saveUninitializedSession || isModified(this));
_touch.call(this, touchStore ? function (err) {
if (err) return cb(err);
store.touch(this.id, this, cb);
touchedStore = true; // Set synchronously regardless of success/failure.
} : cb);
touched = true; // Set synchronously regardless of success/failure.
return this;
}

Object.defineProperty(sess, 'reload', {
configurable: true,
enumerable: false,
Expand All @@ -419,6 +446,13 @@ function session(options) {
value: save,
writable: true
});

Object.defineProperty(sess, 'touch', {
configurable: true,
enumerable: false,
value: touch,
writable: true
});
}

// check if session has been modified
Expand Down Expand Up @@ -457,7 +491,7 @@ function session(options) {
return false;
}

return cookieId === req.sessionID && !shouldSave(req);
return !touchedStore && cookieId === req.sessionID && !shouldSave(req);
}

// determine if cookie should be set on response
Expand Down
17 changes: 15 additions & 2 deletions session/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@

'use strict';

/**
* Node.js 0.8+ async implementation.
* @private
*/

/* istanbul ignore next */
var defer = typeof setImmediate === 'function'
? setImmediate
: function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }

/**
* Expose Session.
*/
Expand Down Expand Up @@ -40,12 +50,15 @@ function Session(req, data) {
* the cookie from expiring when the
* session is still active.
*
* @param {Function} fn optional done callback
* @return {Session} for chaining
* @api public
*/

defineMethod(Session.prototype, 'touch', function touch() {
return this.resetMaxAge();
defineMethod(Session.prototype, 'touch', function touch(fn) {
this.resetMaxAge();
if (fn) defer(fn);
return this;
});

/**
Expand Down

0 comments on commit 0477b5a

Please sign in to comment.