diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b575c5..e62425b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: name: @@ -61,11 +61,11 @@ jobs: - name: Node.js 6.x node-version: "6.17" - npm-i: mocha@6.2.2 nyc@14.1.1 + npm-i: mocha@6.2.2 nyc@14.1.1 supertest@3.4.2 - name: Node.js 7.x node-version: "7.10" - npm-i: mocha@6.2.2 nyc@14.1.1 + npm-i: mocha@6.2.2 nyc@14.1.1 supertest@3.4.2 - name: Node.js 8.x node-version: "8.17" @@ -102,7 +102,7 @@ jobs: node-version: "17.2" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Node.js ${{ matrix.node-version }} shell: bash -eo pipefail -l {0} @@ -143,7 +143,7 @@ jobs: echo "node@$(node -v)" echo "npm@$(npm -v)" npm -s ls ||: - (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' + (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "$2=$3" >> $GITHUB_OUTPUT }' - name: Run tests shell: bash diff --git a/README.md b/README.md index 3126dec..f8a8bbb 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,17 @@ the loaded session. This session is either a new session if no valid session was provided in the request, or a loaded session from the request. The middleware will automatically add a `Set-Cookie` header to the response if the -contents of `req.session` were altered. _Note_ that no `Set-Cookie` header will be +contents of the session were altered. _Note_ that no `Set-Cookie` header will be in the response (and thus no session created for a specific user) unless there are contents in the session, so be sure to add something to `req.session` as soon as you have identifying information to store for the session. +If the session contents change rarely, you may wish to intervene to prolong +sessions, as described [below](#extending-the-session-expiration). + +You can create multiple cookie-sessions by passing multiple [options](#options) +objects to `cookieSession`. +But note that their [`names`](#name), as well as their +[`sessionNames`](#sessionname), must be distinct. #### Options @@ -68,6 +75,27 @@ Cookie session accepts these properties in the options object. ##### name The name of the cookie to set, defaults to `session`. +If you are using multiple cookie-sessions, give each a unique `name`. + +##### sessionName + +The name of the session. +Defaults to `"session"`. + +To avoid confusion, it is sensible to choose the same value as [`name`](#name). + +Sessions with the default name will be accessible at `req.session`, and their +[options](#reqsessionoptions) will be accessible at `req.sessionOptions`. + +The session data will always be accessible on the `req.sessions` object, at the +property matching your sessionName. +e.g. for sessionName `"foo"`, you can access the session at `req.sessions.foo`. + +Similarly, the [options](#reqsessionoptions) for each session will always be +accessible on the `req.sessionsOptions` object. +E.g. `"foo"`'s options will be at `req.sessionsOptions.foo`. + +To create multiple cookie-sessions, give each a unique `sessionName`. ##### keys @@ -99,29 +127,26 @@ The options can also contain any of the following (for the full list, see - `signed`: a boolean indicating whether the cookie is to be signed (`true` by default). - `overwrite`: a boolean indicating whether to overwrite previously set cookies of the same name (`true` by default). -### req.session - -Represents the session for the given request. - -#### .isChanged - -Is `true` if the session has been changed during the request. - -#### .isNew +### Accessing sessions -Is `true` if the session is new. +Session data can always be accessed via the property on[ +`req.sessions`](#reqsessions) matching their [sessionName](#sessionname). +Sessions with the default [sessionName](#sessionname), `"session"`, can also be +accessed at [`req.session`](#reqsession). -#### .isPopulated +#### req.session -Determine if the session has been populated with data or is empty. +The session data for the session with [sessionName](#sessionname) "session" (the +default). `undefined` if there is no session with [sessionName](#sessionname) +"session". -### req.sessionOptions +#### req.sessions -Represents the session options for the current request. These options are a -shallow clone of what was provided at middleware construction and can be -altered to change cookie setting behavior on a per-request basis. +Provides access to the data for all sessions, keyed by their +[sessionName](#sessionname). +E.g. for sessionName `"foo"`, you can access the session at `req.sessions.foo`. -### Destroying a session +#### Destroying a session To destroy a session simply set it to `null`: @@ -129,7 +154,7 @@ To destroy a session simply set it to `null`: req.session = null ``` -### Saving a session +#### Saving a session Since the entire contents of the session is kept in a client-side cookie, the session is "saved" by writing a cookie out in a `Set-Cookie` response header. @@ -137,6 +162,46 @@ This is done automatically if there has been a change made to the session when the Node.js response headers are being written to the client and the session was not destroyed. +#### Session properties + +Sessions always have the following properties, in addition to any you define: + +##### .isChanged + +Is `true` if the session has been changed during the request. + +##### .isNew + +Is `true` if the session is new. + +##### .isPopulated + +Determine if the session has been populated with data or is empty. + +#### Session options + +These options inherit from the options provided at middleware construction and +can be altered to change cookie setting behavior on a per-request basis. + +The options for each session can always be accessed via the property on +[`req.sessionsOptions`](#reqsessionsoptions) matching their +[sessionName](#sessionname). +Sessions with the default [sessionName](#sessionname), `"session"`, can also be +accessed at [`req.sessionOptions`](#reqsession). + +##### req.sessionOptions + +The session options for the session with [sessionName](#sessionname) `"session"` +(the default). +`undefined` if there is no session with [sessionName](#sessionname) `"session"`. + +##### req.sessionsOptions + +Provides access to the options for all sessions, keyed by their +[sessionName](#sessionname). +E.g. for sessionName `"foo"`, you can access the session options at +`req.sessionsOptions.foo`. + ## Examples ### Simple view counter example @@ -238,6 +303,42 @@ app.use(cookieSession({ // ... your logic here ... ``` +### Setting multiple cookies +This example sets a session cookie that the server can trust, and an insecure +session cookie that is just used to make some metadata available to the client. +```js +var cookieSession = require('cookie-session') +var express = require('express') + +var app = express() + +app.use(cookieSession({ + // One cookie not available to client-side JS + // name & sessionName default to 'session' + secret: 'topSecret' +}, { + // Another cookie. This one can be accessed by client-side JS. + name: 'insecureSession', + sessionName: 'insecureSession', + httpOnly: false, + signed: false +})) + +app.get('/', function (req, res, next) { + // Set secure session data + req.session.signedStuff ||= "shibboleth" + // Update insecure session data. (This is just an FYI for the client. The + // server must not trust it to authenticate users!) + const tomorrow = new Date(Date.now() + 24 * 60 * 60e3) + req.sessions.insecureSession.sessionExpiry = tomorrow + next() +}) + +// ... your logic here ... + +app.listen(3000) +``` + ## Usage Limitations ### Max Cookie Size @@ -251,7 +352,7 @@ recommends that a browser **SHOULD** allow > the cookie's name, value, and attributes) In practice this limit differs slightly across browsers. See a list of -[browser limits here](http://browsercookielimits.squawky.net/). As a rule +[browser limits here](http://browsercookielimits.iain.guru). As a rule of thumb **don't exceed 4093 bytes per domain**. If your session object is large enough to exceed a browser limit when encoded, diff --git a/index.js b/index.js index 9ece996..65aa4ae 100644 --- a/index.js +++ b/index.js @@ -13,9 +13,10 @@ */ var Buffer = require('safe-buffer').Buffer -var debug = require('debug')('cookie-session') +var debug = require('debug') var Cookies = require('cookies') var onHeaders = require('on-headers') +var flatten = require('array-flatten') /** * Module exports. @@ -24,50 +25,114 @@ var onHeaders = require('on-headers') module.exports = cookieSession +var defaultConfiguration = { + name: 'session', + sessionName: 'session', + overwrite: true, + httpOnly: true, + signed: true, +} + +/** + * @typedef Configuration + * + * @type {object} + * @property {boolean} [httpOnly=true] + * @property {array} [keys] + * @property {string} [name=session] - Name of the cookie to use + * @property {string} [sessionName=session] - The key on `request.sessions` + * through which the session can be accessed. Sessions with sessionName + * 'session' (the default) will always be accessible at `req.session`. + * @property {boolean} [overwrite=true] + * @property {string} [secret] + * @property {boolean} [signed=true] + */ + /** * Create a new cookie session middleware. * - * @param {object} [options] - * @param {boolean} [options.httpOnly=true] - * @param {array} [options.keys] - * @param {string} [options.name=session] Name of the cookie to use - * @param {boolean} [options.overwrite=true] - * @param {string} [options.secret] - * @param {boolean} [options.signed=true] - * @return {function} middleware + * @param {...Configuration|Configuration[]} configurations + * @return {function} middleware function * @public */ +function cookieSession (configurations) { + var configs = flatten(Array.prototype.slice.call(arguments), null) + switch (configs.length) { + case 0: + configs = [{}] + break + case 1: + break + default: + ['name', 'sessionName'].forEach(function throwIfDupName(nameKind) { + var names = Object.create(null) + configs.forEach(function checkUniqueness(c) { + var name = c[nameKind] || 'null' + if (name in names) { + throw new Error('Each configuration must have a unique ' + nameKind) + } else { + names[name] = true + } + }) + }) + } + return compose(configs.map(createCookieSessionMiddleware)) +} -function cookieSession (options) { - var opts = options || {} - - // cookie name - var name = opts.name || 'session' +function createCookieSessionMiddleware(config) { + // Options inherit from config but with defaults applied + var opts = Object.create(config) + Object.keys(defaultConfiguration).forEach(function applyDefaults(k) { + if (opts[k] == null) opts[k] = defaultConfiguration[k] + }) // secrets var keys = opts.keys - if (!keys && opts.secret) keys = [opts.secret] + if ((!keys || !keys.length) && opts.secret) keys = [opts.secret] - // defaults - if (opts.overwrite == null) opts.overwrite = true - if (opts.httpOnly == null) opts.httpOnly = true - if (opts.signed == null) opts.signed = true - - if (!keys && opts.signed) throw new Error('.keys required.') + if (!keys && opts.signed) { + throw new Error("If '.signed' is true, '.keys' or '.secret' are required.") + } - debug('session options %j', opts) + // Debug namespace + var sessionDebug = debug('cookie-session:' + opts.name) + sessionDebug('Session options %j', opts) - return function _cookieSession (req, res, next) { + return function cookieSessionMiddleware (req, res, next) { var cookies = new Cookies(req, res, { keys: keys }) var sess // for overriding - req.sessionOptions = Object.create(opts) + var overrideOptions = Object.create(opts) + + // If the name is session, add classic accessors for backwards compatibility + if (opts.sessionName === 'session') { + // for overriding + req.sessionOptions = overrideOptions + + // define req.session getter / setter + Object.defineProperty(req, 'session', { + configurable: true, + enumerable: true, + get: getSession, + set: setSession + }) + } + + if (!('sessions' in req)) { + req.sessions = {} + } + if (!('sessionsOptions' in req)) { + req.sessionsOptions = {} + } + + // for overriding + req.sessionsOptions[opts.sessionName] = overrideOptions - // define req.session getter / setter - Object.defineProperty(req, 'session', { + // define req.sessions[sessionName] getter / setter + Object.defineProperty(req.sessions, opts.sessionName, { configurable: true, enumerable: true, get: getSession, @@ -86,12 +151,14 @@ function cookieSession (options) { } // get session - if ((sess = tryGetSession(cookies, name, req.sessionOptions))) { + if ((sess = tryGetSession( + cookies, opts.name, req.sessionsOptions[opts.sessionName], sessionDebug + ))) { return sess } // create session - debug('new session') + sessionDebug('session created') return (sess = Session.create()) } @@ -120,26 +187,51 @@ function cookieSession (options) { try { if (sess === false) { // remove - debug('remove %s', name) - cookies.set(name, '', req.sessionOptions) + sessionDebug('session removed') + cookies.set(opts.name, '', req.sessionsOptions[opts.sessionName]) } else if ((!sess.isNew || sess.isPopulated) && sess.isChanged) { // save populated or non-new changed session - debug('save %s', name) - cookies.set(name, Session.serialize(sess), req.sessionOptions) + sessionDebug('save %s', opts.name) + cookies.set( + opts.name, + Session.serialize(sess), + req.sessionsOptions[opts.sessionName] + ) } } catch (e) { - debug('error saving session %s', e.message) + sessionDebug('error saving session: %s', e.message) } }) next() } -}; +} + +/** + * Compose a non-empty array of middleware into one function + * @param {function[]} middlewares - middleware functions + * @return {function} middleware function + */ +function compose(middlewares) { + var head = middlewares[0] + var tail = middlewares.slice(1) + + if (!tail.length) { + return head; + } + + return function(req, res, next) { + head(req, res, function(err) { + if (err) return next(err); + compose(tail)(req, res, next); + }); + }; +} /** * Session model. * - * @param {Context} ctx + * @param {SessionContext} ctx * @param {Object} obj * @private */ @@ -271,14 +363,14 @@ function encode (body) { * @private */ -function tryGetSession (cookies, name, opts) { +function tryGetSession (cookies, name, opts, log) { var str = cookies.get(name, opts) if (!str) { return undefined } - debug('parse %s', str) + log('parsing session: %s', str) try { return Session.deserialize(str) diff --git a/package.json b/package.json index 19fec15..70ed385 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ ], "repository": "expressjs/cookie-session", "dependencies": { + "array-flatten": "1.1.1", "cookies": "0.8.0", "debug": "3.2.7", "on-headers": "~1.0.2", @@ -22,16 +23,16 @@ }, "devDependencies": { "connect": "3.7.0", - "eslint": "7.32.0", - "eslint-config-standard": "14.1.1", - "eslint-plugin-import": "2.25.3", - "eslint-plugin-markdown": "2.2.1", + "eslint": "8.30.0", + "eslint-config-standard": "17.0.0", + "eslint-plugin-import": "2.26.0", + "eslint-plugin-markdown": "3.0.0", "eslint-plugin-node": "11.1.0", - "eslint-plugin-promise": "5.2.0", + "eslint-plugin-promise": "6.1.1", "eslint-plugin-standard": "4.1.0", - "mocha": "9.1.3", + "mocha": "10.2.0", "nyc": "15.1.0", - "supertest": "6.1.6" + "supertest": "6.3.0" }, "files": [ "HISTORY.md", diff --git a/test/test.js b/test/test.js index 2d4fa81..ea922b9 100644 --- a/test/test.js +++ b/test/test.js @@ -1,3 +1,4 @@ +'use strict' process.env.NODE_ENV = 'test' @@ -10,7 +11,7 @@ describe('Cookie Session', function () { describe('"httpOnly" option', function () { it('should default to "true"', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hi' res.end(String(req.sessionOptions.httpOnly)) }) @@ -23,7 +24,7 @@ describe('Cookie Session', function () { it('should use given "false"', function (done) { var app = App({ httpOnly: false }) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hi' res.end(String(req.sessionOptions.httpOnly)) }) @@ -38,7 +39,7 @@ describe('Cookie Session', function () { describe('"overwrite" option', function () { it('should default to "true"', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { res.setHeader('Set-Cookie', [ 'session=foo; path=/fake', 'foo=bar' @@ -55,7 +56,7 @@ describe('Cookie Session', function () { it('should use given "false"', function (done) { var app = App({ overwrite: false }) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { res.setHeader('Set-Cookie', [ 'session=foo; path=/fake', 'foo=bar' @@ -73,9 +74,9 @@ describe('Cookie Session', function () { }) describe('when options.name = my.session', function () { - it('should use my.session for cookie name', function (done) { + it("should use 'my.session' for cookie name, but 'session' for session name", function (done) { var app = App({ name: 'my.session' }) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hi' res.end() }) @@ -87,6 +88,138 @@ describe('Cookie Session', function () { }) }) + describe('when options.sessionName = my.session', function () { + it("the default-named cookie should be accessible at req.session", function (done) { + var app = App( + {}, // default name 'session' should be used + { + name: "secondary", + sessionName: "secondary", + } + ) + app.use(function (req, res, next) { + req.sessions.session.number = 1 + req.sessions.secondary.number = 2 + next() + }) + app.use(function (req, res, _next) { + res.end(String(req.session.number)) + }) + + request(app) + .get('/') + .expect(200, '1', done) + }) + + it("the session should be at req.sessions['my.session'] but not req.session", function (done) { + var app = App({ sessionName: 'my.session' }) + app.use(function (req, res, _next) { + req.sessions['my.session'].message = 'hi' + res.end(String(req.session)) + }) + + request(app) + .get('/') + .expect(shouldHaveCookie('session')) + .expect(200, 'undefined', done) + }) + + it("should use 'my.session' as the session name, but 'session' as the cookie name", function (done) { + var app = App({ sessionName: 'my.session' }) + app.use(function (req, res, _next) { + req.sessions['my.session'].message = 'hi' + res.end() + }) + + request(app) + .get('/') + .expect(shouldHaveCookie('session')) + .expect(200, done) + }) + }) + + describe('when two cookies are used with different options', function () { + it("should set the 'httpOnly' but not the 'jsAlso' cookie-session as httpOnly", function (done) { + var app = App({ + name: 'httpOnly', + sessionName: 'httpOnly', + }, { + name: 'jsAlso', + sessionName: 'jsAlso', + httpOnly: false, + }) + app.use(function (req, res, _next) { + req.sessions.httpOnly.message = 'httpOnly' + req.sessions.jsAlso.message = 'jsAlso' + res.end() + }) + + request(app) + .get('/') + .expect(shouldHaveCookieWithParameter('httpOnly', 'httpOnly')) + .expect(shouldHaveCookieWithoutParameter('jsAlso', 'httpOnly')) + .expect(200, done) + }) + }) + + describe('when two cookies are configured with the same name', function () { + it("should error if the cookies have the same name", function (done) { + assert.throws(function() { + App({}, {sessionName: 'secondary'}) + }) + done() + }) + it("should error if the cookies have the same SessionName", function (done) { + assert.throws(function() { + App({}, {name: 'secondary'}) + }) + done() + }) + }) + + describe('when multiple cookieSessions are required', function () { + var app + it("multiple configs to be passed to cookieSession in an array", function () { + app = connect() + app.use(session([ + { + signed: false, + }, { + signed: false, + name: 'secondary', + sessionName: 'secondary', + } + ])) + }) + it("cookieSession can be called multiple times", function () { + app = connect() + app.use(session({ + signed: false, + })) + app.use(session({ + signed: false, + name: 'secondary', + sessionName: 'secondary', + })) + }) + afterEach(function (done) { + app.use(function (req, _res, next) { + req.session.name = req.sessionOptions.name + req.sessions.secondary.name = req.sessionsOptions.secondary.name + next() + }) + app.use('/', function (req, res, _next) { + res.end(req.session.name + req.sessions.secondary.name) + }) + + request(app) + .get('/') + .expect(shouldHaveCookie('session')) + .expect(shouldHaveCookie('secondary')) + .expect(200, 'sessionsecondary', done) + }) + }) + describe('when options.signed = true', function () { describe('when options.keys are set', function () { it('should work', function (done) { @@ -94,7 +227,7 @@ describe('Cookie Session', function () { app.use(session({ keys: ['a', 'b'] })) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hi' res.end() }) @@ -111,7 +244,7 @@ describe('Cookie Session', function () { app.use(session({ secret: 'a' })) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hi' res.end() }) @@ -138,7 +271,7 @@ describe('Cookie Session', function () { app.use(session({ signed: false })) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hi' res.end() }) @@ -154,7 +287,7 @@ describe('Cookie Session', function () { describe('when connection not secured', function () { it('should not Set-Cookie', function (done) { var app = App({ secure: true }) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { process.nextTick(function () { req.session.message = 'hello!' res.end('greetings') @@ -172,7 +305,7 @@ describe('Cookie Session', function () { describe('when the session contains a ;', function () { it('should still work', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { if (req.method === 'POST') { req.session.string = ';' res.statusCode = 204 @@ -198,7 +331,7 @@ describe('Cookie Session', function () { describe('when the session is invalid', function () { it('should create new session', function (done) { var app = App({ name: 'my.session', signed: false }) - app.use(function (req, res, next) { + app.use(function (req, res, _next) { res.end(String(req.session.isNew)) }) @@ -213,7 +346,7 @@ describe('Cookie Session', function () { describe('when not accessed', function () { it('should not Set-Cookie', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { res.end('greetings') }) @@ -224,10 +357,10 @@ describe('Cookie Session', function () { }) }) - describe('when accessed and not populated', function (done) { + describe('when accessed and not populated', function () { it('should not Set-Cookie', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { var sess = req.session res.end(JSON.stringify(sess)) }) @@ -239,10 +372,10 @@ describe('Cookie Session', function () { }) }) - describe('when populated', function (done) { + describe('when populated', function () { it('should Set-Cookie', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hello' res.end() }) @@ -260,7 +393,7 @@ describe('Cookie Session', function () { before(function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hello' res.end() }) @@ -278,7 +411,7 @@ describe('Cookie Session', function () { describe('when not accessed', function () { it('should not Set-Cookie', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { res.end('aklsjdfklasjdf') }) @@ -293,7 +426,7 @@ describe('Cookie Session', function () { describe('when accessed but not changed', function () { it('should be the same session', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { assert.strictEqual(req.session.message, 'hello') res.end('aklsjdfkljasdf') }) @@ -306,7 +439,7 @@ describe('Cookie Session', function () { it('should not Set-Cookie', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { assert.strictEqual(req.session.message, 'hello') res.end('aklsjdfkljasdf') }) @@ -322,7 +455,7 @@ describe('Cookie Session', function () { describe('when accessed and changed', function () { it('should Set-Cookie', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.money = '$$$' res.end('klajsdlkfjadsf') }) @@ -340,7 +473,7 @@ describe('Cookie Session', function () { describe('null', function () { it('should expire the session', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session = null res.end('lkajsdf') }) @@ -353,7 +486,7 @@ describe('Cookie Session', function () { it('should no longer return a session', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session = null res.end(JSON.stringify(req.session)) }) @@ -368,7 +501,7 @@ describe('Cookie Session', function () { describe('{}', function () { it('should not Set-Cookie', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session = {} res.end('hello, world') }) @@ -383,7 +516,7 @@ describe('Cookie Session', function () { describe('{a: b}', function () { it('should create a session', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session = { message: 'hello' } res.end('klajsdfasdf') }) @@ -398,7 +531,7 @@ describe('Cookie Session', function () { describe('anything else', function () { it('should throw', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session = 'aklsdjfasdf' }) @@ -413,7 +546,7 @@ describe('Cookie Session', function () { describe('.isPopulated', function () { it('should be false on new session', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { res.end(String(req.session.isPopulated)) }) @@ -424,7 +557,7 @@ describe('Cookie Session', function () { it('should be true after adding property', function (done) { var app = App() - app.use(function (req, res, next) { + app.use(function (req, res, _next) { req.session.message = 'hello!' res.end(String(req.session.isPopulated)) }) @@ -436,21 +569,43 @@ describe('Cookie Session', function () { }) }) - describe('req.sessionOptions', function () { - it('should be the session options', function (done) { - var app = App({ name: 'my.session' }) - app.use(function (req, res, next) { + describe('session options', function () { + it('should be at sessionOptions by default', function (done) { + var app = App() + app.use(function (req, res, _next) { res.end(String(req.sessionOptions.name)) }) request(app) .get('/') - .expect(200, 'my.session', done) + .expect(200, 'session', done) + }) + + it('should also be in sessionsOptions[sessionName]', function (done) { + var app = App() + app.use(function (req, res, _next) { + res.end(String(req.sessionsOptions.session.name)) + }) + + request(app) + .get('/') + .expect(200, 'session', done) + }) + + it('should not be at sessionOptions for non-default sessionName', function (done) { + var app = App({ sessionName: 'foo' }) + app.use(function (req, res, _next) { + res.end(String(req.sessionOptions)) + }) + + request(app) + .get('/') + .expect(200, 'undefined', done) }) it('should alter the cookie setting', function (done) { - var app = App({ maxAge: 3600000, name: 'my.session' }) - app.use(function (req, res, next) { + var app = App({ maxAge: 3600000 }) + app.use(function (req, res, _next) { if (req.url === '/max') { req.sessionOptions.maxAge = 6500000 } @@ -461,23 +616,33 @@ describe('Cookie Session', function () { request(app) .get('/') - .expect(shouldHaveCookieWithTTLBetween('my.session', 0, 3600000)) + .expect(shouldHaveCookieWithTTLBetween('session', 0, 3600000)) .expect(200, function (err) { if (err) return done(err) request(app) .get('/max') - .expect(shouldHaveCookieWithTTLBetween('my.session', 5000000, Infinity)) + .expect(shouldHaveCookieWithTTLBetween('session', 5000000, Infinity)) .expect(200, done) }) }) }) }) -function App (options) { - var opts = Object.create(options || null) - opts.keys = ['a', 'b'] +/** + * Connect to an app using cookie-sessions with passed configurations + * @param {...Configuration} configurations + * @return {app} + */ +function App (configurations) { var app = connect() - app.use(session(opts)) + var configs = arguments.length ? Array.prototype.slice.call(arguments) : [{}] + var keyedConfigs = configs.map(function addKeys(configuration) { + return Object.create(configuration, { keys: { + value: ['a', 'b'], + enumerable: true, + }}) + }) + app.use(session(keyedConfigs)) return app }