diff --git a/History.md b/History.md index f514c1b9..0b568ef9 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,7 @@ unreleased ========== + * Add `genid` option to generate custom session IDs * Add `saveUninitialized` option to control saving uninitialized sessions * Add `unset` option to control unsetting `req.session` * deps: buffer-crc32@0.2.3 diff --git a/README.md b/README.md index 3bb68e86..fa3a64af 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,27 @@ middleware _before_ `session()`. - `secret` - session cookie is signed with this secret to prevent tampering. - `cookie` - session cookie settings. - (default: `{ path: '/', httpOnly: true, secure: false, maxAge: null }`) + - `genid` - function to call to generate a new session ID. (default: uses `uid2` library) - `rolling` - forces a cookie set on every response. This resets the expiration date. (default: `false`) - `resave` - forces session to be saved even when unmodified. (default: `true`) - `proxy` - trust the reverse proxy when setting secure cookies (via "x-forwarded-proto" header). When set to `true`, the "x-forwarded-proto" header will be used. When set to `false`, all headers are ignored. When left unset, will use the "trust proxy" setting from express. (default: `undefined`) - `saveUninitialized` - forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified. This is useful for implementing login sessions, reducing server storage usage, or complying with laws that require permission before setting a cookie. (default: `true`) - `unset` - controls result of unsetting `req.session` (through `delete`, setting to `null`, etc.). This can be "keep" to keep the session in the store but ignore modifications or "destroy" to destroy the stored session. (default: `'keep'`) +#### options.genid + +Generate a custom session ID for new sessions. Provide a function that returns a string that will be used as a session ID. The function is given `req` as the first argument if you want to use some value attached to `req` when generating the ID. + +**NOTE** be careful you generate unique IDs so your sessions do not conflict. + +```js +app.use(session({ + genid: function(req) { + return genuuid(); // use UUIDs for session IDs + }, + secret: 'keyboard cat' +})) +``` #### Cookie options diff --git a/index.js b/index.js index db20d65c..cfc7ed3d 100644 --- a/index.js +++ b/index.js @@ -72,6 +72,12 @@ function session(options){ , storeReady = true , rollingSessions = options.rolling || false; + var generateId = options.genid || generateSessionId; + + if (typeof generateId !== 'function') { + throw new TypeError('genid option must be a function'); + } + // TODO: switch default to false on next major var resaveSession = options.resave === undefined ? true @@ -96,7 +102,7 @@ function session(options){ // generates the new session store.generate = function(req){ - req.sessionID = uid(24); + req.sessionID = generateId(req); req.session = new Session(req); req.session.cookie = new Cookie(cookie); }; @@ -276,6 +282,17 @@ function session(options){ }; }; +/** + * Generate a session ID for a new session. + * + * @return {String} + * @api private + */ + +function generateSessionId(sess) { + return uid(24); +} + /** * Hash the given `sess` object omitting changes to `.cookie`. * diff --git a/test/session.js b/test/session.js index 78e52517..0c8ed37f 100644 --- a/test/session.js +++ b/test/session.js @@ -186,6 +186,55 @@ describe('session()', function(){ }) }) + describe('genid option', function(){ + it('should reject non-function values', function(){ + session.bind(null, { genid: 'bogus!' }).should.throw(/genid.*must/); + }); + + it('should provide default generator', function(done){ + request(app) + .get('/') + .expect('set-cookie', /connect\.sid=s%3A([^\.]{24})\./i) + .expect(200, done); + }); + + it('should allow custom function', function(done){ + var app = express() + .use(cookieParser()) + .use(session({ genid: function(){ return 'a' }, secret: 'keyboard cat', cookie: { maxAge: min }})) + .use(respond); + + request(app) + .get('/') + .expect('set-cookie', /connect\.sid=s%3Aa\./i) + .expect(200, done); + }); + + it('should encode unsafe chars', function(done){ + var app = express() + .use(cookieParser()) + .use(session({ genid: function(){ return '%' }, secret: 'keyboard cat', cookie: { maxAge: min }})) + .use(respond); + + request(app) + .get('/') + .expect('set-cookie', /connect\.sid=s%3A%25\./i) + .expect(200, done); + }); + + it('should provide req argument', function(done){ + var app = express() + .use(cookieParser()) + .use(session({ genid: function(req){ return req.url }, secret: 'keyboard cat', cookie: { maxAge: min }})) + .use(respond); + + request(app) + .get('/foo') + .expect('set-cookie', /connect\.sid=s%3A%2Ffoo\./i) + .expect(200, done); + }); + }); + describe('key option', function(){ it('should default to "connect.sid"', function(done){ request(app)