You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					185 lines
				
				4.8 KiB
			
		
		
			
		
	
	
					185 lines
				
				4.8 KiB
			| 
											3 years ago
										 | 'use strict' | ||
|  | 
 | ||
|  | const crypto = require('crypto') | ||
|  | 
 | ||
|  | const Cookie = require('./cookie') | ||
|  | const cookieSignature = require('cookie-signature') | ||
|  | const { configure: configureStringifier } = require('safe-stable-stringify') | ||
|  | 
 | ||
|  | const stringify = configureStringifier({ bigint: false }) | ||
|  | 
 | ||
|  | const maxAge = Symbol('maxAge') | ||
|  | const secretKey = Symbol('secretKey') | ||
|  | const sign = Symbol('sign') | ||
|  | const addDataToSession = Symbol('addDataToSession') | ||
|  | const generateId = Symbol('generateId') | ||
|  | const requestKey = Symbol('request') | ||
|  | const cookieOptsKey = Symbol('cookieOpts') | ||
|  | const originalHash = Symbol('originalHash') | ||
|  | const hash = Symbol('hash') | ||
|  | 
 | ||
|  | module.exports = class Session { | ||
|  |   constructor (request, idGenerator, cookieOpts, secret, prevSession = {}) { | ||
|  |     this[generateId] = idGenerator | ||
|  |     this.expires = null | ||
|  |     this.cookie = new Cookie(cookieOpts) | ||
|  |     this[cookieOptsKey] = cookieOpts | ||
|  |     this[maxAge] = cookieOpts.maxAge | ||
|  |     this[secretKey] = secret | ||
|  |     this[addDataToSession](prevSession) | ||
|  |     this[requestKey] = request | ||
|  |     this.touch() | ||
|  |     if (!this.sessionId) { | ||
|  |       this.sessionId = this[generateId](this[requestKey]) | ||
|  |       this.encryptedSessionId = this[sign]() | ||
|  |     } | ||
|  |     this[originalHash] = this[hash]() | ||
|  |   } | ||
|  | 
 | ||
|  |   touch () { | ||
|  |     if (this[maxAge]) { | ||
|  |       this.expires = new Date(Date.now() + this[maxAge]) | ||
|  |       this.cookie.expires = this.expires | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   regenerate (callback) { | ||
|  |     if (callback) { | ||
|  |       const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) | ||
|  | 
 | ||
|  |       this[requestKey].sessionStore.set(session.sessionId, session, error => { | ||
|  |         this[requestKey].session = session | ||
|  | 
 | ||
|  |         callback(error) | ||
|  |       }) | ||
|  |     } else { | ||
|  |       return new Promise((resolve, reject) => { | ||
|  |         const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) | ||
|  | 
 | ||
|  |         this[requestKey].sessionStore.set(session.sessionId, session, error => { | ||
|  |           this[requestKey].session = session | ||
|  | 
 | ||
|  |           if (error) { | ||
|  |             reject(error) | ||
|  |           } else { | ||
|  |             resolve() | ||
|  |           } | ||
|  |         }) | ||
|  |       }) | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   [addDataToSession] (prevSession) { | ||
|  |     for (const key in prevSession) { | ||
|  |       if (!['expires', 'cookie'].includes(key)) { | ||
|  |         this[key] = prevSession[key] | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   get (key) { | ||
|  |     return this[key] | ||
|  |   } | ||
|  | 
 | ||
|  |   set (key, value) { | ||
|  |     this[key] = value | ||
|  |   } | ||
|  | 
 | ||
|  |   destroy (callback) { | ||
|  |     if (callback) { | ||
|  |       this[requestKey].sessionStore.destroy(this.sessionId, error => { | ||
|  |         this[requestKey].session = null | ||
|  | 
 | ||
|  |         callback(error) | ||
|  |       }) | ||
|  |     } else { | ||
|  |       return new Promise((resolve, reject) => { | ||
|  |         this[requestKey].sessionStore.destroy(this.sessionId, error => { | ||
|  |           this[requestKey].session = null | ||
|  | 
 | ||
|  |           if (error) { | ||
|  |             reject(error) | ||
|  |           } else { | ||
|  |             resolve() | ||
|  |           } | ||
|  |         }) | ||
|  |       }) | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   reload (callback) { | ||
|  |     if (callback) { | ||
|  |       this[requestKey].sessionStore.get(this.sessionId, (error, session) => { | ||
|  |         this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) | ||
|  | 
 | ||
|  |         callback(error) | ||
|  |       }) | ||
|  |     } else { | ||
|  |       return new Promise((resolve, reject) => { | ||
|  |         this[requestKey].sessionStore.get(this.sessionId, (error, session) => { | ||
|  |           this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) | ||
|  | 
 | ||
|  |           if (error) { | ||
|  |             reject(error) | ||
|  |           } else { | ||
|  |             resolve() | ||
|  |           } | ||
|  |         }) | ||
|  |       }) | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   save (callback) { | ||
|  |     if (callback) { | ||
|  |       this[requestKey].sessionStore.set(this.sessionId, this, error => { | ||
|  |         callback(error) | ||
|  |       }) | ||
|  |     } else { | ||
|  |       return new Promise((resolve, reject) => { | ||
|  |         this[requestKey].sessionStore.set(this.sessionId, this, error => { | ||
|  |           if (error) { | ||
|  |             reject(error) | ||
|  |           } else { | ||
|  |             resolve() | ||
|  |           } | ||
|  |         }) | ||
|  |       }) | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   [sign] () { | ||
|  |     return cookieSignature.sign(this.sessionId, this[secretKey]) | ||
|  |   } | ||
|  | 
 | ||
|  |   [hash] () { | ||
|  |     const sess = this | ||
|  |     const str = stringify(sess, function (key, val) { | ||
|  |       // ignore sess.cookie property
 | ||
|  |       if (this === sess && key === 'cookie') { | ||
|  |         return | ||
|  |       } | ||
|  | 
 | ||
|  |       return val | ||
|  |     }) | ||
|  | 
 | ||
|  |     return crypto | ||
|  |       .createHash('sha1') | ||
|  |       .update(str, 'utf8') | ||
|  |       .digest('hex') | ||
|  |   } | ||
|  | 
 | ||
|  |   isModified () { | ||
|  |     return this[originalHash] !== this[hash]() | ||
|  |   } | ||
|  | 
 | ||
|  |   static restore (request, idGenerator, cookieOpts, secret, prevSession) { | ||
|  |     const restoredSession = new Session(request, idGenerator, cookieOpts, secret, prevSession) | ||
|  |     const restoredCookie = new Cookie(cookieOpts) | ||
|  |     restoredCookie.expires = new Date(prevSession.cookie.expires) | ||
|  |     restoredSession.cookie = restoredCookie | ||
|  |     restoredSession.expires = restoredCookie.expires | ||
|  |     restoredSession[originalHash] = restoredSession[hash]() | ||
|  |     return restoredSession | ||
|  |   } | ||
|  | } |