root / HServer / 00.Server / 00.Program / node_modules / express-session / index.js
이력 | 보기 | 이력해설 | 다운로드 (15.4 KB)
1 | 39 | HKM | /*!
|
---|---|---|---|
2 | * express-session
|
||
3 | * Copyright(c) 2010 Sencha Inc.
|
||
4 | * Copyright(c) 2011 TJ Holowaychuk
|
||
5 | * Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||
6 | * MIT Licensed
|
||
7 | */
|
||
8 | |||
9 | 'use strict';
|
||
10 | |||
11 | /**
|
||
12 | * Module dependencies.
|
||
13 | * @private
|
||
14 | */
|
||
15 | |||
16 | var cookie = require('cookie'); |
||
17 | var crc = require('crc').crc32; |
||
18 | var debug = require('debug')('express-session'); |
||
19 | var deprecate = require('depd')('express-session'); |
||
20 | var parseUrl = require('parseurl'); |
||
21 | var uid = require('uid-safe').sync |
||
22 | , onHeaders = require('on-headers')
|
||
23 | , signature = require('cookie-signature')
|
||
24 | |||
25 | var Session = require('./session/session') |
||
26 | , MemoryStore = require('./session/memory')
|
||
27 | , Cookie = require('./session/cookie')
|
||
28 | , Store = require('./session/store')
|
||
29 | |||
30 | // environment
|
||
31 | |||
32 | var env = process.env.NODE_ENV;
|
||
33 | |||
34 | /**
|
||
35 | * Expose the middleware.
|
||
36 | */
|
||
37 | |||
38 | exports = module.exports = session; |
||
39 | |||
40 | /**
|
||
41 | * Expose constructors.
|
||
42 | */
|
||
43 | |||
44 | exports.Store = Store; |
||
45 | exports.Cookie = Cookie; |
||
46 | exports.Session = Session; |
||
47 | exports.MemoryStore = MemoryStore; |
||
48 | |||
49 | /**
|
||
50 | * Warning message for `MemoryStore` usage in production.
|
||
51 | * @private
|
||
52 | */
|
||
53 | |||
54 | var warning = 'Warning: connect.session() MemoryStore is not\n' |
||
55 | + 'designed for a production environment, as it will leak\n'
|
||
56 | + 'memory, and will not scale past a single process.';
|
||
57 | |||
58 | /**
|
||
59 | * Node.js 0.8+ async implementation.
|
||
60 | * @private
|
||
61 | */
|
||
62 | |||
63 | /* istanbul ignore next */
|
||
64 | var defer = typeof setImmediate === 'function' |
||
65 | ? setImmediate
|
||
66 | : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) } |
||
67 | |||
68 | /**
|
||
69 | * Setup session store with the given `options`.
|
||
70 | *
|
||
71 | * @param {Object} [options]
|
||
72 | * @param {Object} [options.cookie] Options for cookie
|
||
73 | * @param {Function} [options.genid]
|
||
74 | * @param {String} [options.name=connect.sid] Session ID cookie name
|
||
75 | * @param {Boolean} [options.proxy]
|
||
76 | * @param {Boolean} [options.resave] Resave unmodified sessions back to the store
|
||
77 | * @param {Boolean} [options.rolling] Enable/disable rolling session expiration
|
||
78 | * @param {Boolean} [options.saveUninitialized] Save uninitialized sessions to the store
|
||
79 | * @param {String|Array} [options.secret] Secret for signing session ID
|
||
80 | * @param {Object} [options.store=MemoryStore] Session store
|
||
81 | * @param {String} [options.unset]
|
||
82 | * @return {Function} middleware
|
||
83 | * @public
|
||
84 | */
|
||
85 | |||
86 | function session(options) { |
||
87 | var opts = options || {}
|
||
88 | |||
89 | // get the cookie options
|
||
90 | var cookieOptions = opts.cookie || {}
|
||
91 | |||
92 | // get the session id generate function
|
||
93 | var generateId = opts.genid || generateSessionId
|
||
94 | |||
95 | // get the session cookie name
|
||
96 | var name = opts.name || opts.key || 'connect.sid' |
||
97 | |||
98 | // get the session store
|
||
99 | var store = opts.store || new MemoryStore() |
||
100 | |||
101 | // get the trust proxy setting
|
||
102 | var trustProxy = opts.proxy
|
||
103 | |||
104 | // get the resave session option
|
||
105 | var resaveSession = opts.resave;
|
||
106 | |||
107 | // get the rolling session option
|
||
108 | var rollingSessions = Boolean(opts.rolling)
|
||
109 | |||
110 | // get the save uninitialized session option
|
||
111 | var saveUninitializedSession = opts.saveUninitialized
|
||
112 | |||
113 | // get the cookie signing secret
|
||
114 | var secret = opts.secret
|
||
115 | |||
116 | if (typeof generateId !== 'function') { |
||
117 | throw new TypeError('genid option must be a function'); |
||
118 | } |
||
119 | |||
120 | if (resaveSession === undefined) { |
||
121 | deprecate('undefined resave option; provide resave option');
|
||
122 | resaveSession = true;
|
||
123 | } |
||
124 | |||
125 | if (saveUninitializedSession === undefined) { |
||
126 | deprecate('undefined saveUninitialized option; provide saveUninitialized option');
|
||
127 | saveUninitializedSession = true;
|
||
128 | } |
||
129 | |||
130 | if (opts.unset && opts.unset !== 'destroy' && opts.unset !== 'keep') { |
||
131 | throw new TypeError('unset option must be "destroy" or "keep"'); |
||
132 | } |
||
133 | |||
134 | // TODO: switch to "destroy" on next major
|
||
135 | var unsetDestroy = opts.unset === 'destroy' |
||
136 | |||
137 | if (Array.isArray(secret) && secret.length === 0) { |
||
138 | throw new TypeError('secret option array must contain one or more strings'); |
||
139 | } |
||
140 | |||
141 | if (secret && !Array.isArray(secret)) {
|
||
142 | secret = [secret]; |
||
143 | } |
||
144 | |||
145 | if (!secret) {
|
||
146 | deprecate('req.secret; provide secret option');
|
||
147 | } |
||
148 | |||
149 | // notify user that this store is not
|
||
150 | // meant for a production environment
|
||
151 | /* istanbul ignore next: not tested */
|
||
152 | if ('production' == env && store instanceof MemoryStore) { |
||
153 | console.warn(warning); |
||
154 | } |
||
155 | |||
156 | // generates the new session
|
||
157 | store.generate = function(req){ |
||
158 | req.sessionID = generateId(req); |
||
159 | req.session = new Session(req);
|
||
160 | req.session.cookie = new Cookie(cookieOptions);
|
||
161 | |||
162 | if (cookieOptions.secure === 'auto') { |
||
163 | req.session.cookie.secure = issecure(req, trustProxy); |
||
164 | } |
||
165 | }; |
||
166 | |||
167 | var storeImplementsTouch = typeof store.touch === 'function'; |
||
168 | |||
169 | // register event listeners for the store to track readiness
|
||
170 | var storeReady = true |
||
171 | store.on('disconnect', function ondisconnect() { |
||
172 | storeReady = false
|
||
173 | }) |
||
174 | store.on('connect', function onconnect() { |
||
175 | storeReady = true
|
||
176 | }) |
||
177 | |||
178 | return function session(req, res, next) { |
||
179 | // self-awareness
|
||
180 | if (req.session) {
|
||
181 | next() |
||
182 | return
|
||
183 | } |
||
184 | |||
185 | // Handle connection as if there is no session if
|
||
186 | // the store has temporarily disconnected etc
|
||
187 | if (!storeReady) {
|
||
188 | debug('store is disconnected')
|
||
189 | next() |
||
190 | return
|
||
191 | } |
||
192 | |||
193 | // pathname mismatch
|
||
194 | var originalPath = parseUrl.original(req).pathname || '/' |
||
195 | if (originalPath.indexOf(cookieOptions.path || '/') !== 0) return next(); |
||
196 | |||
197 | // ensure a secret is available or bail
|
||
198 | if (!secret && !req.secret) {
|
||
199 | next(new Error('secret option required for sessions')); |
||
200 | return;
|
||
201 | } |
||
202 | |||
203 | // backwards compatibility for signed cookies
|
||
204 | // req.secret is passed from the cookie parser middleware
|
||
205 | var secrets = secret || [req.secret];
|
||
206 | |||
207 | var originalHash;
|
||
208 | var originalId;
|
||
209 | var savedHash;
|
||
210 | var touched = false |
||
211 | |||
212 | // expose store
|
||
213 | req.sessionStore = store; |
||
214 | |||
215 | // get the session ID from the cookie
|
||
216 | var cookieId = req.sessionID = getcookie(req, name, secrets);
|
||
217 | |||
218 | // set-cookie
|
||
219 | onHeaders(res, function(){
|
||
220 | if (!req.session) {
|
||
221 | debug('no session');
|
||
222 | return;
|
||
223 | } |
||
224 | |||
225 | if (!shouldSetCookie(req)) {
|
||
226 | return;
|
||
227 | } |
||
228 | |||
229 | // only send secure cookies via https
|
||
230 | if (req.session.cookie.secure && !issecure(req, trustProxy)) {
|
||
231 | debug('not secured');
|
||
232 | return;
|
||
233 | } |
||
234 | |||
235 | if (!touched) {
|
||
236 | // touch session
|
||
237 | req.session.touch() |
||
238 | touched = true
|
||
239 | } |
||
240 | |||
241 | // set cookie
|
||
242 | setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data);
|
||
243 | }); |
||
244 | |||
245 | // proxy end() to commit the session
|
||
246 | var _end = res.end;
|
||
247 | var _write = res.write;
|
||
248 | var ended = false; |
||
249 | res.end = function end(chunk, encoding) { |
||
250 | if (ended) {
|
||
251 | return false; |
||
252 | } |
||
253 | |||
254 | ended = true;
|
||
255 | |||
256 | var ret;
|
||
257 | var sync = true; |
||
258 | |||
259 | function writeend() { |
||
260 | if (sync) {
|
||
261 | ret = _end.call(res, chunk, encoding); |
||
262 | sync = false;
|
||
263 | return;
|
||
264 | } |
||
265 | |||
266 | _end.call(res); |
||
267 | } |
||
268 | |||
269 | function writetop() { |
||
270 | if (!sync) {
|
||
271 | return ret;
|
||
272 | } |
||
273 | |||
274 | if (chunk == null) { |
||
275 | ret = true;
|
||
276 | return ret;
|
||
277 | } |
||
278 | |||
279 | var contentLength = Number(res.getHeader('Content-Length')); |
||
280 | |||
281 | if (!isNaN(contentLength) && contentLength > 0) { |
||
282 | // measure chunk
|
||
283 | chunk = !Buffer.isBuffer(chunk) |
||
284 | ? new Buffer(chunk, encoding)
|
||
285 | : chunk; |
||
286 | encoding = undefined;
|
||
287 | |||
288 | if (chunk.length !== 0) { |
||
289 | debug('split response');
|
||
290 | ret = _write.call(res, chunk.slice(0, chunk.length - 1)); |
||
291 | chunk = chunk.slice(chunk.length - 1, chunk.length);
|
||
292 | return ret;
|
||
293 | } |
||
294 | } |
||
295 | |||
296 | ret = _write.call(res, chunk, encoding); |
||
297 | sync = false;
|
||
298 | |||
299 | return ret;
|
||
300 | } |
||
301 | |||
302 | if (shouldDestroy(req)) {
|
||
303 | // destroy session
|
||
304 | debug('destroying');
|
||
305 | store.destroy(req.sessionID, function ondestroy(err) { |
||
306 | if (err) {
|
||
307 | defer(next, err); |
||
308 | } |
||
309 | |||
310 | debug('destroyed');
|
||
311 | writeend(); |
||
312 | }); |
||
313 | |||
314 | return writetop();
|
||
315 | } |
||
316 | |||
317 | // no session to save
|
||
318 | if (!req.session) {
|
||
319 | debug('no session');
|
||
320 | return _end.call(res, chunk, encoding);
|
||
321 | } |
||
322 | |||
323 | if (!touched) {
|
||
324 | // touch session
|
||
325 | req.session.touch() |
||
326 | touched = true
|
||
327 | } |
||
328 | |||
329 | if (shouldSave(req)) {
|
||
330 | req.session.save(function onsave(err) { |
||
331 | if (err) {
|
||
332 | defer(next, err); |
||
333 | } |
||
334 | |||
335 | writeend(); |
||
336 | }); |
||
337 | |||
338 | return writetop();
|
||
339 | } else if (storeImplementsTouch && shouldTouch(req)) { |
||
340 | // store implements touch method
|
||
341 | debug('touching');
|
||
342 | store.touch(req.sessionID, req.session, function ontouch(err) { |
||
343 | if (err) {
|
||
344 | defer(next, err); |
||
345 | } |
||
346 | |||
347 | debug('touched');
|
||
348 | writeend(); |
||
349 | }); |
||
350 | |||
351 | return writetop();
|
||
352 | } |
||
353 | |||
354 | return _end.call(res, chunk, encoding);
|
||
355 | }; |
||
356 | |||
357 | // generate the session
|
||
358 | function generate() { |
||
359 | store.generate(req); |
||
360 | originalId = req.sessionID; |
||
361 | originalHash = hash(req.session); |
||
362 | wrapmethods(req.session); |
||
363 | } |
||
364 | |||
365 | // wrap session methods
|
||
366 | function wrapmethods(sess) { |
||
367 | var _reload = sess.reload
|
||
368 | var _save = sess.save;
|
||
369 | |||
370 | function reload(callback) { |
||
371 | debug('reloading %s', this.id) |
||
372 | _reload.call(this, function () { |
||
373 | wrapmethods(req.session) |
||
374 | callback.apply(this, arguments) |
||
375 | }) |
||
376 | } |
||
377 | |||
378 | function save() { |
||
379 | debug('saving %s', this.id); |
||
380 | savedHash = hash(this);
|
||
381 | _save.apply(this, arguments); |
||
382 | } |
||
383 | |||
384 | Object.defineProperty(sess, 'reload', {
|
||
385 | configurable: true, |
||
386 | enumerable: false, |
||
387 | value: reload,
|
||
388 | writable: true |
||
389 | }) |
||
390 | |||
391 | Object.defineProperty(sess, 'save', {
|
||
392 | configurable: true, |
||
393 | enumerable: false, |
||
394 | value: save,
|
||
395 | writable: true |
||
396 | }); |
||
397 | } |
||
398 | |||
399 | // check if session has been modified
|
||
400 | function isModified(sess) { |
||
401 | return originalId !== sess.id || originalHash !== hash(sess);
|
||
402 | } |
||
403 | |||
404 | // check if session has been saved
|
||
405 | function isSaved(sess) { |
||
406 | return originalId === sess.id && savedHash === hash(sess);
|
||
407 | } |
||
408 | |||
409 | // determine if session should be destroyed
|
||
410 | function shouldDestroy(req) { |
||
411 | return req.sessionID && unsetDestroy && req.session == null; |
||
412 | } |
||
413 | |||
414 | // determine if session should be saved to store
|
||
415 | function shouldSave(req) { |
||
416 | // cannot set cookie without a session ID
|
||
417 | if (typeof req.sessionID !== 'string') { |
||
418 | debug('session ignored because of bogus req.sessionID %o', req.sessionID);
|
||
419 | return false; |
||
420 | } |
||
421 | |||
422 | return !saveUninitializedSession && cookieId !== req.sessionID
|
||
423 | ? isModified(req.session) |
||
424 | : !isSaved(req.session) |
||
425 | } |
||
426 | |||
427 | // determine if session should be touched
|
||
428 | function shouldTouch(req) { |
||
429 | // cannot set cookie without a session ID
|
||
430 | if (typeof req.sessionID !== 'string') { |
||
431 | debug('session ignored because of bogus req.sessionID %o', req.sessionID);
|
||
432 | return false; |
||
433 | } |
||
434 | |||
435 | return cookieId === req.sessionID && !shouldSave(req);
|
||
436 | } |
||
437 | |||
438 | // determine if cookie should be set on response
|
||
439 | function shouldSetCookie(req) { |
||
440 | // cannot set cookie without a session ID
|
||
441 | if (typeof req.sessionID !== 'string') { |
||
442 | return false; |
||
443 | } |
||
444 | |||
445 | return cookieId != req.sessionID
|
||
446 | ? saveUninitializedSession || isModified(req.session) |
||
447 | : rollingSessions || req.session.cookie.expires != null && isModified(req.session);
|
||
448 | } |
||
449 | |||
450 | // generate a session if the browser doesn't send a sessionID
|
||
451 | if (!req.sessionID) {
|
||
452 | debug('no SID sent, generating session');
|
||
453 | generate(); |
||
454 | next(); |
||
455 | return;
|
||
456 | } |
||
457 | |||
458 | // generate the session object
|
||
459 | debug('fetching %s', req.sessionID);
|
||
460 | store.get(req.sessionID, function(err, sess){
|
||
461 | // error handling
|
||
462 | if (err) {
|
||
463 | debug('error %j', err);
|
||
464 | |||
465 | if (err.code !== 'ENOENT') { |
||
466 | next(err); |
||
467 | return;
|
||
468 | } |
||
469 | |||
470 | generate(); |
||
471 | // no session
|
||
472 | } else if (!sess) { |
||
473 | debug('no session found');
|
||
474 | generate(); |
||
475 | // populate req.session
|
||
476 | } else {
|
||
477 | debug('session found');
|
||
478 | store.createSession(req, sess); |
||
479 | originalId = req.sessionID; |
||
480 | originalHash = hash(sess); |
||
481 | |||
482 | if (!resaveSession) {
|
||
483 | savedHash = originalHash |
||
484 | } |
||
485 | |||
486 | wrapmethods(req.session); |
||
487 | } |
||
488 | |||
489 | next(); |
||
490 | }); |
||
491 | }; |
||
492 | }; |
||
493 | |||
494 | /**
|
||
495 | * Generate a session ID for a new session.
|
||
496 | *
|
||
497 | * @return {String}
|
||
498 | * @private
|
||
499 | */
|
||
500 | |||
501 | function generateSessionId(sess) { |
||
502 | return uid(24); |
||
503 | } |
||
504 | |||
505 | /**
|
||
506 | * Get the session ID cookie from request.
|
||
507 | *
|
||
508 | * @return {string}
|
||
509 | * @private
|
||
510 | */
|
||
511 | |||
512 | function getcookie(req, name, secrets) { |
||
513 | var header = req.headers.cookie;
|
||
514 | var raw;
|
||
515 | var val;
|
||
516 | |||
517 | // read from cookie header
|
||
518 | if (header) {
|
||
519 | var cookies = cookie.parse(header);
|
||
520 | |||
521 | raw = cookies[name]; |
||
522 | |||
523 | if (raw) {
|
||
524 | if (raw.substr(0, 2) === 's:') { |
||
525 | val = unsigncookie(raw.slice(2), secrets);
|
||
526 | |||
527 | if (val === false) { |
||
528 | debug('cookie signature invalid');
|
||
529 | val = undefined;
|
||
530 | } |
||
531 | } else {
|
||
532 | debug('cookie unsigned')
|
||
533 | } |
||
534 | } |
||
535 | } |
||
536 | |||
537 | // back-compat read from cookieParser() signedCookies data
|
||
538 | if (!val && req.signedCookies) {
|
||
539 | val = req.signedCookies[name]; |
||
540 | |||
541 | if (val) {
|
||
542 | deprecate('cookie should be available in req.headers.cookie');
|
||
543 | } |
||
544 | } |
||
545 | |||
546 | // back-compat read from cookieParser() cookies data
|
||
547 | if (!val && req.cookies) {
|
||
548 | raw = req.cookies[name]; |
||
549 | |||
550 | if (raw) {
|
||
551 | if (raw.substr(0, 2) === 's:') { |
||
552 | val = unsigncookie(raw.slice(2), secrets);
|
||
553 | |||
554 | if (val) {
|
||
555 | deprecate('cookie should be available in req.headers.cookie');
|
||
556 | } |
||
557 | |||
558 | if (val === false) { |
||
559 | debug('cookie signature invalid');
|
||
560 | val = undefined;
|
||
561 | } |
||
562 | } else {
|
||
563 | debug('cookie unsigned')
|
||
564 | } |
||
565 | } |
||
566 | } |
||
567 | |||
568 | return val;
|
||
569 | } |
||
570 | |||
571 | /**
|
||
572 | * Hash the given `sess` object omitting changes to `.cookie`.
|
||
573 | *
|
||
574 | * @param {Object} sess
|
||
575 | * @return {String}
|
||
576 | * @private
|
||
577 | */
|
||
578 | |||
579 | function hash(sess) { |
||
580 | return crc(JSON.stringify(sess, function (key, val) { |
||
581 | // ignore sess.cookie property
|
||
582 | if (this === sess && key === 'cookie') { |
||
583 | return
|
||
584 | } |
||
585 | |||
586 | return val
|
||
587 | })) |
||
588 | } |
||
589 | |||
590 | /**
|
||
591 | * Determine if request is secure.
|
||
592 | *
|
||
593 | * @param {Object} req
|
||
594 | * @param {Boolean} [trustProxy]
|
||
595 | * @return {Boolean}
|
||
596 | * @private
|
||
597 | */
|
||
598 | |||
599 | function issecure(req, trustProxy) { |
||
600 | // socket is https server
|
||
601 | if (req.connection && req.connection.encrypted) {
|
||
602 | return true; |
||
603 | } |
||
604 | |||
605 | // do not trust proxy
|
||
606 | if (trustProxy === false) { |
||
607 | return false; |
||
608 | } |
||
609 | |||
610 | // no explicit trust; try req.secure from express
|
||
611 | if (trustProxy !== true) { |
||
612 | var secure = req.secure;
|
||
613 | return typeof secure === 'boolean' |
||
614 | ? secure |
||
615 | : false;
|
||
616 | } |
||
617 | |||
618 | // read the proto from x-forwarded-proto header
|
||
619 | var header = req.headers['x-forwarded-proto'] || ''; |
||
620 | var index = header.indexOf(','); |
||
621 | var proto = index !== -1 |
||
622 | ? header.substr(0, index).toLowerCase().trim()
|
||
623 | : header.toLowerCase().trim() |
||
624 | |||
625 | return proto === 'https'; |
||
626 | } |
||
627 | |||
628 | /**
|
||
629 | * Set cookie on response.
|
||
630 | *
|
||
631 | * @private
|
||
632 | */
|
||
633 | |||
634 | function setcookie(res, name, val, secret, options) { |
||
635 | var signed = 's:' + signature.sign(val, secret); |
||
636 | var data = cookie.serialize(name, signed, options);
|
||
637 | |||
638 | debug('set-cookie %s', data);
|
||
639 | |||
640 | var prev = res.getHeader('set-cookie') || []; |
||
641 | var header = Array.isArray(prev) ? prev.concat(data) : [prev, data];
|
||
642 | |||
643 | res.setHeader('set-cookie', header)
|
||
644 | } |
||
645 | |||
646 | /**
|
||
647 | * Verify and decode the given `val` with `secrets`.
|
||
648 | *
|
||
649 | * @param {String} val
|
||
650 | * @param {Array} secrets
|
||
651 | * @returns {String|Boolean}
|
||
652 | * @private
|
||
653 | */
|
||
654 | function unsigncookie(val, secrets) { |
||
655 | for (var i = 0; i < secrets.length; i++) { |
||
656 | var result = signature.unsign(val, secrets[i]);
|
||
657 | |||
658 | if (result !== false) { |
||
659 | return result;
|
||
660 | } |
||
661 | } |
||
662 | |||
663 | return false; |
||
664 | } |