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

'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
}
}