function session(options) {
var opts = options || {}
// get the cookie options
var cookieOptions = opts.cookie || {}
// get the session id generate function
var generateId = opts.genid || generateSessionId
// get the session cookie name
var name = opts.name || opts.key || 'connect.sid'
// get the session store
var store = opts.store || new MemoryStore()
// get the trust proxy setting
var trustProxy = opts.proxy
// get the resave session option
var resaveSession = opts.resave;
// get the rolling session option
var rollingSessions = Boolean(opts.rolling)
// get the save uninitialized session option
var saveUninitializedSession = opts.saveUninitialized
// get the cookie signing secret
var secret = opts.secret
if (typeof generateId !== 'function') {
throw new TypeError('genid option must be a function');
}
if (resaveSession === undefined) {
deprecate('undefined resave option; provide resave option');
resaveSession = true;
}
if (saveUninitializedSession === undefined) {
deprecate('undefined saveUninitialized option; provide saveUninitialized option');
saveUninitializedSession = true;
}
if (opts.unset && opts.unset !== 'destroy' && opts.unset !== 'keep') {
throw new TypeError('unset option must be "destroy" or "keep"');
}
// TODO: switch to "destroy" on next major
var unsetDestroy = opts.unset === 'destroy'
if (Array.isArray(secret) && secret.length === 0) {
throw new TypeError('secret option array must contain one or more strings');
}
if (secret && !Array.isArray(secret)) {
secret = [secret];
}
if (!secret) {
deprecate('req.secret; provide secret option');
}
// notify user that this store is not
// meant for a production environment
/* istanbul ignore next: not tested */
if ('production' == env && store instanceof MemoryStore) {
console.warn(warning);
}
// generates the new session
store.generate = function(req){
req.sessionID = generateId(req);
req.session = new Session(req);
req.session.cookie = new Cookie(cookieOptions);
if (cookieOptions.secure === 'auto') {
req.session.cookie.secure = issecure(req, trustProxy);
}
};
var storeImplementsTouch = typeof store.touch === 'function';
// register event listeners for the store to track readiness
var storeReady = true
store.on('disconnect', function ondisconnect() {
storeReady = false
})
store.on('connect', function onconnect() {
storeReady = true
})
return function session(req, res, next) {
// self-awareness
if (req.session) {
next()
return
}
// Handle connection as if there is no session if
// the store has temporarily disconnected etc
if (!storeReady) {
debug('store is disconnected')
next()
return
}
// pathname mismatch
var originalPath = parseUrl.original(req).pathname;
if (originalPath.indexOf(cookieOptions.path || '/') !== 0) return next();
// ensure a secret is available or bail
if (!secret && !req.secret) {
next(new Error('secret option required for sessions'));
return;
}
// backwards compatibility for signed cookies
// req.secret is passed from the cookie parser middleware
var secrets = secret || [req.secret];
var originalHash;
var originalId;
var savedHash;
var touched = false
// expose store
req.sessionStore = store;
// get the session ID from the cookie
var cookieId = req.sessionID = getcookie(req, name, secrets);
// set-cookie
onHeaders(res, function(){
if (!req.session) {
debug('no session');
return;
}
if (!shouldSetCookie(req)) {
return;
}
// only send secure cookies via https
if (req.session.cookie.secure && !issecure(req, trustProxy)) {
debug('not secured');
return;
}
if (!touched) ...n/a
function Cookie(options) {
this.path = '/';
this.maxAge = null;
this.httpOnly = true;
if (options) merge(this, options);
this.originalMaxAge = undefined == this.originalMaxAge
? this.maxAge
: this.originalMaxAge;
}n/a
function MemoryStore() {
Store.call(this)
this.sessions = Object.create(null)
}n/a
function Session(req, data) {
Object.defineProperty(this, 'req', { value: req });
Object.defineProperty(this, 'id', { value: req.sessionID });
if (typeof data === 'object' && data !== null) {
// merge data into this, ignoring prototype properties
for (var prop in data) {
if (!(prop in this)) {
this[prop] = data[prop]
}
}
}
}n/a
function Store() {
EventEmitter.call(this)
}n/a
function Cookie(options) {
this.path = '/';
this.maxAge = null;
this.httpOnly = true;
if (options) merge(this, options);
this.originalMaxAge = undefined == this.originalMaxAge
? this.maxAge
: this.originalMaxAge;
}n/a
serialize = function (name, val){
return cookie.serialize(name, val, this.data);
}...
* Set cookie on response.
*
* @private
*/
function setcookie(res, name, val, secret, options) {
var signed = 's:' + signature.sign(val, secret);
var data = cookie.serialize(name, signed, options);
debug('set-cookie %s', data);
var prev = res.getHeader('set-cookie') || [];
var header = Array.isArray(prev) ? prev.concat(data) : [prev, data];
res.setHeader('set-cookie', header)
...toJSON = function (){
return this.data;
}n/a
function MemoryStore() {
Store.call(this)
this.sessions = Object.create(null)
}n/a
function Store() {
EventEmitter.call(this)
}n/a
function all(callback) {
var sessionIds = Object.keys(this.sessions)
var sessions = Object.create(null)
for (var i = 0; i < sessionIds.length; i++) {
var sessionId = sessionIds[i]
var session = getSession.call(this, sessionId)
if (session) {
sessions[sessionId] = session;
}
}
callback && defer(callback, null, sessions)
}...
* Recommended methods are ones that this module will call on the store if
available.
* Optional methods are ones this module does not call at all, but helps
present uniform stores to users.
For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
### store.all(callback)
**Optional**
This optional method is used to get all sessions in the store as an array. The
`callback` should be called as `callback(error, sessions)`.
### store.destroy(sid, callback)
...function clear(callback) {
this.sessions = Object.create(null)
callback && defer(callback)
}...
**Required**
This required method is used to destroy/delete a session from the store given
a session ID (`sid`). The `callback` should be called as `callback(error)` once
the session is destroyed.
### store.clear(callback)
**Optional**
This optional method is used to delete all sessions from the store. The
`callback` should be called as `callback(error)` once the store is cleared.
### store.length(callback)
...function destroy(sessionId, callback) {
delete this.sessions[sessionId]
callback && defer(callback)
}...
```js
req.session.regenerate(function(err) {
// will have a new session here
})
```
#### Session.destroy(callback)
Destroys the session and will unset the `req.session` property.
Once complete, the `callback` will be invoked.
```js
req.session.destroy(function(err) {
// cannot access session here
...function get(sessionId, callback) {
defer(callback, null, getSession.call(this, sessionId))
}...
```js
var app = express()
var sess = {
secret: 'keyboard cat',
cookie: {}
}
if (app.get('env') === 'production') {
app.set('trust proxy', 1) // trust first proxy
sess.cookie.secure = true // serve secure cookies
}
app.use(session(sess))
```
...function length(callback) {
this.all(function (err, sessions) {
if (err) return callback(err)
callback(null, Object.keys(sessions).length)
})
}...
### store.clear(callback)
**Optional**
This optional method is used to delete all sessions from the store. The
`callback` should be called as `callback(error)` once the store is cleared.
### store.length(callback)
**Optional**
This optional method is used to get the count of all sessions in the store.
The `callback` should be called as `callback(error, len)`.
### store.get(sid, callback)
...function set(sessionId, session, callback) {
this.sessions[sessionId] = JSON.stringify(session)
callback && defer(callback)
}...
an https-enabled website, i.e., HTTPS is necessary for secure cookies. If `secure`
is set, and you access your site over HTTP, the cookie will not be set. If you
have your node.js behind a proxy and are using `secure: true`, you need to set
"trust proxy" in express:
```js
var app = express()
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}))
```
...function touch(sessionId, session, callback) {
var currentSession = getSession.call(this, sessionId)
if (currentSession) {
// update expiration
currentSession.cookie = session.cookie
this.sessions[sessionId] = JSON.stringify(currentSession)
}
callback && defer(callback)
}...
```js
req.session.save(function(err) {
// session saved
})
```
#### Session.touch()
Updates the `.maxAge` property. Typically this is
not necessary to call, as the session middleware does this for you.
### req.session.id
Each session has a unique ID associated with it. This property will
...function Store() {
EventEmitter.call(this)
}n/a
function EventEmitter() {
EventEmitter.init.call(this);
}n/a
createSession = function (req, sess){
var expires = sess.cookie.expires
, orig = sess.cookie.originalMaxAge;
sess.cookie = new Cookie(sess.cookie);
if ('string' == typeof expires) sess.cookie.expires = new Date(expires);
sess.cookie.originalMaxAge = orig;
req.session = new Session(req, sess);
return req.session;
}...
// no session
} else if (!sess) {
debug('no session found');
generate();
// populate req.session
} else {
debug('session found');
store.createSession(req, sess);
originalId = req.sessionID;
originalHash = hash(sess);
if (!resaveSession) {
savedHash = originalHash
}
...load = function (sid, fn){
var self = this;
this.get(sid, function(err, sess){
if (err) return fn(err);
if (!sess) return fn();
var req = { sessionID: sid, sessionStore: self };
fn(null, self.createSession(req, sess))
});
}n/a
regenerate = function (req, fn){
var self = this;
this.destroy(req.sessionID, function(err){
self.generate(req);
fn(err);
});
}...
} else {
sess.views = 1
res.end('welcome to the session demo. refresh!')
}
})
```
#### Session.regenerate(callback)
To regenerate the session simply invoke the method. Once complete,
a new SID and `Session` instance will be initialized at `req.session`
and the `callback` will be invoked.
```js
req.session.regenerate(function(err) {
...