@@ -74,7 +74,7 @@ var defer = typeof setImmediate === 'function'
74
74
* @param {Boolean } [options.resave] Resave unmodified sessions back to the store
75
75
* @param {Boolean } [options.rolling] Enable/disable rolling session expiration
76
76
* @param {Boolean } [options.saveUninitialized] Save uninitialized sessions to the store
77
- * @param {String } [options.secret] Secret for signing session ID
77
+ * @param {String|Array } [options.secret] Secret for signing session ID
78
78
* @param {Object } [options.store=MemoryStore] Session store
79
79
* @param {String } [options.unset]
80
80
* @return {Function } middleware
@@ -116,6 +116,19 @@ function session(options){
116
116
// TODO: switch to "destroy" on next major
117
117
var unsetDestroy = options . unset === 'destroy' ;
118
118
119
+ if ( Array . isArray ( options . secret ) && options . secret . length === 0 ) {
120
+ throw new TypeError ( 'secret option array must contain one or more strings' ) ;
121
+ }
122
+
123
+ if ( options . secret && ! Array . isArray ( options . secret ) ) {
124
+ options . secret = [ options . secret ] ;
125
+ }
126
+
127
+ if ( ! options . secret ) {
128
+ deprecate ( 'req.secret; provide secret option' ) ;
129
+ options . secret = undefined ;
130
+ }
131
+
119
132
// notify user that this store is not
120
133
// meant for a production environment
121
134
if ( 'production' == env && store instanceof MemoryStore ) {
@@ -133,10 +146,6 @@ function session(options){
133
146
store . on ( 'disconnect' , function ( ) { storeReady = false ; } ) ;
134
147
store . on ( 'connect' , function ( ) { storeReady = true ; } ) ;
135
148
136
- if ( ! options . secret ) {
137
- deprecate ( 'req.secret; provide secret option' ) ;
138
- }
139
-
140
149
return function session ( req , res , next ) {
141
150
// self-awareness
142
151
if ( req . session ) return next ( ) ;
@@ -149,12 +158,15 @@ function session(options){
149
158
var originalPath = parseUrl . original ( req ) . pathname ;
150
159
if ( 0 != originalPath . indexOf ( cookie . path || '/' ) ) return next ( ) ;
151
160
161
+ // ensure a secret is available or bail
162
+ if ( ! options . secret && ! req . secret ) {
163
+ next ( new Error ( 'secret option required for sessions' ) ) ;
164
+ return ;
165
+ }
166
+
152
167
// backwards compatibility for signed cookies
153
168
// req.secret is passed from the cookie parser middleware
154
- var secret = options . secret || req . secret ;
155
-
156
- // ensure secret is available or bail
157
- if ( ! secret ) next ( new Error ( '`secret` option required for sessions' ) ) ;
169
+ var secrets = options . secret || [ req . secret ] ;
158
170
159
171
var originalHash ;
160
172
var originalId ;
@@ -164,7 +176,7 @@ function session(options){
164
176
req . sessionStore = store ;
165
177
166
178
// get the session ID from the cookie
167
- var cookieId = req . sessionID = getcookie ( req , name , secret ) ;
179
+ var cookieId = req . sessionID = getcookie ( req , name , secrets ) ;
168
180
169
181
// set-cookie
170
182
onHeaders ( res , function ( ) {
@@ -185,7 +197,7 @@ function session(options){
185
197
return ;
186
198
}
187
199
188
- setcookie ( res , name , req . sessionID , secret , cookie . data ) ;
200
+ setcookie ( res , name , req . sessionID , secrets [ 0 ] , cookie . data ) ;
189
201
} ) ;
190
202
191
203
// proxy end() to commit the session
@@ -441,7 +453,7 @@ function generateSessionId(sess) {
441
453
* @private
442
454
*/
443
455
444
- function getcookie ( req , name , secret ) {
456
+ function getcookie ( req , name , secrets ) {
445
457
var header = req . headers . cookie ;
446
458
var raw ;
447
459
var val ;
@@ -454,7 +466,7 @@ function getcookie(req, name, secret) {
454
466
455
467
if ( raw ) {
456
468
if ( raw . substr ( 0 , 2 ) === 's:' ) {
457
- val = signature . unsign ( raw . slice ( 2 ) , secret ) ;
469
+ val = unsigncookie ( raw . slice ( 2 ) , secrets ) ;
458
470
459
471
if ( val === false ) {
460
472
debug ( 'cookie signature invalid' ) ;
@@ -481,7 +493,7 @@ function getcookie(req, name, secret) {
481
493
482
494
if ( raw ) {
483
495
if ( raw . substr ( 0 , 2 ) === 's:' ) {
484
- val = signature . unsign ( raw . slice ( 2 ) , secret ) ;
496
+ val = unsigncookie ( raw . slice ( 2 ) , secrets ) ;
485
497
486
498
if ( val ) {
487
499
deprecate ( 'cookie should be available in req.headers.cookie' ) ;
@@ -573,3 +585,23 @@ function setcookie(res, name, val, secret, options) {
573
585
574
586
res . setHeader ( 'set-cookie' , header )
575
587
}
588
+
589
+ /**
590
+ * Verify and decode the given `val` with `secrets`.
591
+ *
592
+ * @param {String } val
593
+ * @param {Array } secrets
594
+ * @returns {String|Boolean }
595
+ * @private
596
+ */
597
+ function unsigncookie ( val , secrets ) {
598
+ for ( var i = 0 ; i < secrets . length ; i ++ ) {
599
+ var result = signature . unsign ( val , secrets [ i ] ) ;
600
+
601
+ if ( result !== false ) {
602
+ return result ;
603
+ }
604
+ }
605
+
606
+ return false ;
607
+ }
0 commit comments