Update Node_modules
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
'use strict'
|
||||
|
||||
class Crypto {
|
||||
init(options) {
|
||||
this.crypto = require('crypto')
|
||||
this.algorithm = options.algorithm || 'aes-256-gcm'
|
||||
this.hashing = options.hashing || 'sha512'
|
||||
this.encodeas = options.encodeas || 'hex'
|
||||
this.iv_size = options.iv_size || 16
|
||||
this.at_size = options.at_size || 16
|
||||
this.key_size = options.key_size || 32
|
||||
this.secret = this._deriveKey(options.secret) || false
|
||||
}
|
||||
|
||||
set(plaintext) {
|
||||
const iv = this.crypto.randomBytes(this.iv_size).toString(this.encodeas)
|
||||
const aad = this._digest(
|
||||
iv + this.secret,
|
||||
JSON.stringify(plaintext),
|
||||
this.hashing,
|
||||
this.encodeas
|
||||
)
|
||||
const ct = this._encrypt(
|
||||
this.secret,
|
||||
JSON.stringify(plaintext),
|
||||
this.algorithm,
|
||||
this.encodeas,
|
||||
iv,
|
||||
aad
|
||||
)
|
||||
const hmac = this._digest(this.secret, ct.ct, this.hashing, this.encodeas)
|
||||
|
||||
const obj = JSON.stringify({
|
||||
hmac,
|
||||
ct: ct.ct,
|
||||
at: ct.at,
|
||||
aad,
|
||||
iv,
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
get(ciphertext) {
|
||||
let ct
|
||||
|
||||
if (ciphertext) {
|
||||
try {
|
||||
ct = JSON.parse(ciphertext)
|
||||
} catch (err) {
|
||||
ct = ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
const hmac = this._digest(this.secret, ct.ct, this.hashing, this.encodeas)
|
||||
|
||||
if (hmac !== ct.hmac) {
|
||||
throw new Error('Encrypted session was tampered with!')
|
||||
}
|
||||
|
||||
if (ct.at) {
|
||||
ct.at = Buffer.from(ct.at)
|
||||
}
|
||||
|
||||
const pt = this._decrypt(
|
||||
this.secret,
|
||||
ct.ct,
|
||||
this.algorithm,
|
||||
this.encodeas,
|
||||
ct.iv,
|
||||
ct.at,
|
||||
ct.aad
|
||||
)
|
||||
|
||||
return pt
|
||||
}
|
||||
|
||||
_digest(key, obj, hashing, encodeas) {
|
||||
const hmac = this.crypto.createHmac(this.hashing, key)
|
||||
hmac.setEncoding(encodeas)
|
||||
hmac.write(obj)
|
||||
hmac.end()
|
||||
return hmac.read().toString(encodeas)
|
||||
}
|
||||
|
||||
_encrypt(key, pt, algo, encodeas, iv, aad) {
|
||||
const cipher = this.crypto.createCipheriv(algo, key, iv, {
|
||||
authTagLength: this.at_size,
|
||||
})
|
||||
let ct
|
||||
let at
|
||||
|
||||
if (aad) {
|
||||
try {
|
||||
cipher.setAAD(Buffer.from(aad), {
|
||||
plaintextLength: Buffer.byteLength(pt),
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
ct = cipher.update(pt, 'utf8', encodeas)
|
||||
ct += cipher.final(encodeas)
|
||||
|
||||
try {
|
||||
at = cipher.getAuthTag()
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
return at ? { ct, at } : { ct }
|
||||
}
|
||||
|
||||
_decrypt(key, ct, algo, encodeas, iv, at, aad) {
|
||||
const cipher = this.crypto.createDecipheriv(algo, key, iv)
|
||||
let pt
|
||||
|
||||
if (at) {
|
||||
try {
|
||||
cipher.setAuthTag(Buffer.from(at))
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
if (aad) {
|
||||
try {
|
||||
cipher.setAAD(Buffer.from(aad), {
|
||||
plaintextLength: Buffer.byteLength(ct),
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
pt = cipher.update(ct, encodeas, 'utf8')
|
||||
pt += cipher.final('utf8')
|
||||
|
||||
return pt
|
||||
}
|
||||
|
||||
_deriveKey(secret) {
|
||||
const hash = this.crypto.createHash(this.hashing)
|
||||
hash.update(secret)
|
||||
const salt = hash.digest(this.encodeas).substr(0, 16)
|
||||
|
||||
const key = this.crypto.pbkdf2Sync(secret, salt, 10000, 64, this.hashing)
|
||||
|
||||
return key.toString(this.encodeas).substr(0, this.key_size)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Crypto()
|
||||
+216
-99
@@ -6,9 +6,7 @@ function withCallback(promise, cb) {
|
||||
// Assume that cb is a function - type checks and handling type errors
|
||||
// can be done by caller
|
||||
if (cb) {
|
||||
promise
|
||||
.then(res => cb(null, res))
|
||||
.catch(cb)
|
||||
promise.then(res => cb(null, res)).catch(cb)
|
||||
}
|
||||
return promise
|
||||
}
|
||||
@@ -22,7 +20,9 @@ function defaultSerializeFunction(session) {
|
||||
if (prop === 'cookie') {
|
||||
// Convert the cookie instance to an object, if possible
|
||||
// This gets rid of the duplicate object under session.cookie.data property
|
||||
obj.cookie = session.cookie.toJSON ? session.cookie.toJSON() : session.cookie
|
||||
obj.cookie = session.cookie.toJSON
|
||||
? session.cookie.toJSON()
|
||||
: session.cookie
|
||||
} else {
|
||||
obj[prop] = session[prop]
|
||||
}
|
||||
@@ -31,30 +31,28 @@ function defaultSerializeFunction(session) {
|
||||
return obj
|
||||
}
|
||||
|
||||
function computeTransformFunctions(options, defaultStringify) {
|
||||
function computeTransformFunctions(options) {
|
||||
if (options.serialize || options.unserialize) {
|
||||
return {
|
||||
serialize: options.serialize || defaultSerializeFunction,
|
||||
unserialize: options.unserialize || (x => x)
|
||||
unserialize: options.unserialize || (x => x),
|
||||
}
|
||||
}
|
||||
|
||||
if (options.stringify === false || defaultStringify === false) {
|
||||
if (options.stringify === false) {
|
||||
return {
|
||||
serialize: defaultSerializeFunction,
|
||||
unserialize: x => x
|
||||
unserialize: x => x,
|
||||
}
|
||||
}
|
||||
|
||||
if (options.stringify === true || defaultStringify === true) {
|
||||
return {
|
||||
serialize: JSON.stringify,
|
||||
unserialize: JSON.parse
|
||||
}
|
||||
// Default case
|
||||
return {
|
||||
serialize: JSON.stringify,
|
||||
unserialize: JSON.parse,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function (connect) {
|
||||
module.exports = function(connect) {
|
||||
const Store = connect.Store || connect.session.Store
|
||||
const MemoryStore = connect.MemoryStore || connect.session.MemoryStore
|
||||
|
||||
@@ -69,45 +67,63 @@ module.exports = function (connect) {
|
||||
|
||||
super(options)
|
||||
|
||||
/* Use crypto? */
|
||||
if (options.secret) {
|
||||
try {
|
||||
this.Crypto = require('./crypto.js')
|
||||
this.Crypto.init(options)
|
||||
delete options.secret
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/* Options */
|
||||
this.ttl = options.ttl || 1209600 // 14 days
|
||||
this.collectionName = options.collection || 'sessions'
|
||||
this.autoRemove = options.autoRemove || 'native'
|
||||
this.autoRemoveInterval = options.autoRemoveInterval || 10
|
||||
this.transformFunctions = computeTransformFunctions(options, true)
|
||||
|
||||
this.autoRemoveInterval = options.autoRemoveInterval || 10 // Minutes
|
||||
this.writeOperationOptions = options.writeOperationOptions || {}
|
||||
this.transformFunctions = computeTransformFunctions(options)
|
||||
this.options = options
|
||||
|
||||
this.changeState('init')
|
||||
|
||||
const newConnectionCallback = (err, db) => {
|
||||
const newConnectionCallback = (err, client) => {
|
||||
if (err) {
|
||||
this.connectionFailed(err)
|
||||
} else {
|
||||
this.handleNewConnectionAsync(db)
|
||||
this.handleNewConnectionAsync(client)
|
||||
}
|
||||
}
|
||||
|
||||
if (options.url) {
|
||||
// New native connection using url + mongoOptions
|
||||
MongoClient.connect(options.url, options.mongoOptions || {}, newConnectionCallback)
|
||||
const _options = options.mongoOptions || {}
|
||||
if (typeof _options.useNewUrlParser !== 'boolean') {
|
||||
_options.useNewUrlParser = true
|
||||
}
|
||||
MongoClient.connect(options.url, _options, newConnectionCallback)
|
||||
} else if (options.mongooseConnection) {
|
||||
// Re-use existing or upcoming mongoose connection
|
||||
if (options.mongooseConnection.readyState === 1) {
|
||||
this.handleNewConnectionAsync(options.mongooseConnection.db)
|
||||
this.handleNewConnectionAsync(options.mongooseConnection)
|
||||
} else {
|
||||
options.mongooseConnection.once('open', () => this.handleNewConnectionAsync(options.mongooseConnection.db))
|
||||
options.mongooseConnection.once('open', () =>
|
||||
this.handleNewConnectionAsync(options.mongooseConnection)
|
||||
)
|
||||
}
|
||||
} else if (options.db && options.db.listCollections) {
|
||||
// Re-use existing or upcoming native connection
|
||||
if (options.db.openCalled || options.db.openCalled === undefined) { // OpenCalled is undefined in mongodb@2.x
|
||||
this.handleNewConnectionAsync(options.db)
|
||||
} else if (options.client) {
|
||||
if (options.client.isConnected()) {
|
||||
this.handleNewConnectionAsync(options.client)
|
||||
} else {
|
||||
options.db.open(newConnectionCallback)
|
||||
options.client.once('open', () =>
|
||||
this.handleNewConnectionAsync(options.client)
|
||||
)
|
||||
}
|
||||
} else if (options.dbPromise) {
|
||||
options.dbPromise
|
||||
.then(db => this.handleNewConnectionAsync(db))
|
||||
} else if (options.clientPromise) {
|
||||
options.clientPromise
|
||||
.then(client => this.handleNewConnectionAsync(client))
|
||||
.catch(err => this.connectionFailed(err))
|
||||
} else {
|
||||
throw new Error('Connection strategy not found')
|
||||
@@ -121,23 +137,36 @@ module.exports = function (connect) {
|
||||
throw err
|
||||
}
|
||||
|
||||
handleNewConnectionAsync(db) {
|
||||
this.db = db
|
||||
return this
|
||||
.setCollection(db.collection(this.collectionName))
|
||||
handleNewConnectionAsync(client) {
|
||||
this.client = client
|
||||
this.db = typeof client.db !== 'function' ? client.db : client.db()
|
||||
return this.setCollection(this.db.collection(this.collectionName))
|
||||
.setAutoRemoveAsync()
|
||||
.then(() => this.changeState('connected'))
|
||||
}
|
||||
|
||||
setAutoRemoveAsync() {
|
||||
const removeQuery = () => {
|
||||
return {expires: {$lt: new Date()}}
|
||||
return { expires: { $lt: new Date() } }
|
||||
}
|
||||
switch (this.autoRemove) {
|
||||
case 'native':
|
||||
return this.collection.createIndex({expires: 1}, {expireAfterSeconds: 0})
|
||||
return this.collection.createIndex(
|
||||
{ expires: 1 },
|
||||
Object.assign({ expireAfterSeconds: 0 }, this.writeOperationOptions)
|
||||
)
|
||||
case 'interval':
|
||||
this.timer = setInterval(() => this.collection.remove(removeQuery(), {w: 0}), this.autoRemoveInterval * 1000 * 60)
|
||||
this.timer = setInterval(
|
||||
() =>
|
||||
this.collection.deleteMany(
|
||||
removeQuery(),
|
||||
Object.assign({}, this.writeOperationOptions, {
|
||||
w: 0,
|
||||
j: false,
|
||||
})
|
||||
),
|
||||
this.autoRemoveInterval * 1000 * 60
|
||||
)
|
||||
this.timer.unref()
|
||||
return Promise.resolve()
|
||||
default:
|
||||
@@ -180,7 +209,10 @@ module.exports = function (connect) {
|
||||
}
|
||||
|
||||
computeStorageId(sessionId) {
|
||||
if (this.options.transformId && typeof this.options.transformId === 'function') {
|
||||
if (
|
||||
this.options.transformId &&
|
||||
typeof this.options.transformId === 'function'
|
||||
) {
|
||||
return this.options.transformId(sessionId)
|
||||
}
|
||||
return sessionId
|
||||
@@ -189,25 +221,35 @@ module.exports = function (connect) {
|
||||
/* Public API */
|
||||
|
||||
get(sid, callback) {
|
||||
return withCallback(this.collectionReady()
|
||||
.then(collection => collection.findOne({
|
||||
_id: this.computeStorageId(sid),
|
||||
$or: [
|
||||
{expires: {$exists: false}},
|
||||
{expires: {$gt: new Date()}}
|
||||
]
|
||||
}))
|
||||
.then(session => {
|
||||
if (session) {
|
||||
const s = this.transformFunctions.unserialize(session.session)
|
||||
if (this.options.touchAfter > 0 && session.lastModified) {
|
||||
s.lastModified = session.lastModified
|
||||
return withCallback(
|
||||
this.collectionReady()
|
||||
.then(collection =>
|
||||
collection.findOne({
|
||||
_id: this.computeStorageId(sid),
|
||||
$or: [
|
||||
{ expires: { $exists: false } },
|
||||
{ expires: { $gt: new Date() } },
|
||||
],
|
||||
})
|
||||
)
|
||||
.then(session => {
|
||||
if (session) {
|
||||
if (this.Crypto) {
|
||||
const tmpSession = this.transformFunctions.unserialize(
|
||||
session.session
|
||||
)
|
||||
session.session = this.Crypto.get(tmpSession)
|
||||
}
|
||||
const s = this.transformFunctions.unserialize(session.session)
|
||||
if (this.options.touchAfter > 0 && session.lastModified) {
|
||||
s.lastModified = session.lastModified
|
||||
}
|
||||
this.emit('get', sid)
|
||||
return s
|
||||
}
|
||||
this.emit('get', sid)
|
||||
return s
|
||||
}
|
||||
})
|
||||
, callback)
|
||||
}),
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
set(sid, session, callback) {
|
||||
@@ -218,10 +260,21 @@ module.exports = function (connect) {
|
||||
|
||||
let s
|
||||
|
||||
if (this.Crypto) {
|
||||
try {
|
||||
session = this.Crypto.set(session)
|
||||
} catch (error) {
|
||||
return withCallback(Promise.reject(error), callback)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
s = {_id: this.computeStorageId(sid), session: this.transformFunctions.serialize(session)}
|
||||
s = {
|
||||
_id: this.computeStorageId(sid),
|
||||
session: this.transformFunctions.serialize(session),
|
||||
}
|
||||
} catch (err) {
|
||||
return callback(err)
|
||||
return withCallback(Promise.reject(err), callback)
|
||||
}
|
||||
|
||||
if (session && session.cookie && session.cookie.expires) {
|
||||
@@ -234,33 +287,43 @@ module.exports = function (connect) {
|
||||
// So we set the expiration to two-weeks from now
|
||||
// - as is common practice in the industry (e.g Django) -
|
||||
// or the default specified in the options.
|
||||
s.expires = new Date(Date.now() + (this.ttl * 1000))
|
||||
s.expires = new Date(Date.now() + this.ttl * 1000)
|
||||
}
|
||||
|
||||
if (this.options.touchAfter > 0) {
|
||||
s.lastModified = new Date()
|
||||
}
|
||||
|
||||
return withCallback(this.collectionReady()
|
||||
.then(collection => collection.updateOne({_id: this.computeStorageId(sid)}, {$set: s}, {upsert: true}))
|
||||
.then(rawResponse => {
|
||||
if (rawResponse.result) {
|
||||
rawResponse = rawResponse.result
|
||||
}
|
||||
if (rawResponse && rawResponse.upserted) {
|
||||
this.emit('create', sid)
|
||||
} else {
|
||||
this.emit('update', sid)
|
||||
}
|
||||
this.emit('set', sid)
|
||||
})
|
||||
, callback)
|
||||
return withCallback(
|
||||
this.collectionReady()
|
||||
.then(collection =>
|
||||
collection.updateOne(
|
||||
{ _id: this.computeStorageId(sid) },
|
||||
{ $set: s },
|
||||
Object.assign({ upsert: true }, this.writeOperationOptions)
|
||||
)
|
||||
)
|
||||
.then(rawResponse => {
|
||||
if (rawResponse.result) {
|
||||
rawResponse = rawResponse.result
|
||||
}
|
||||
if (rawResponse && rawResponse.upserted) {
|
||||
this.emit('create', sid)
|
||||
} else {
|
||||
this.emit('update', sid)
|
||||
}
|
||||
this.emit('set', sid)
|
||||
}),
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
touch(sid, session, callback) {
|
||||
const updateFields = {}
|
||||
const touchAfter = this.options.touchAfter * 1000
|
||||
const lastModified = session.lastModified ? session.lastModified.getTime() : 0
|
||||
const lastModified = session.lastModified
|
||||
? session.lastModified.getTime()
|
||||
: 0
|
||||
const currentDate = new Date()
|
||||
|
||||
// If the given options has a touchAfter property, check if the
|
||||
@@ -270,7 +333,7 @@ module.exports = function (connect) {
|
||||
const timeElapsed = currentDate.getTime() - session.lastModified
|
||||
|
||||
if (timeElapsed < touchAfter) {
|
||||
return callback()
|
||||
return withCallback(Promise.resolve(), callback)
|
||||
}
|
||||
updateFields.lastModified = currentDate
|
||||
}
|
||||
@@ -278,43 +341,97 @@ module.exports = function (connect) {
|
||||
if (session && session.cookie && session.cookie.expires) {
|
||||
updateFields.expires = new Date(session.cookie.expires)
|
||||
} else {
|
||||
updateFields.expires = new Date(Date.now() + (this.ttl * 1000))
|
||||
updateFields.expires = new Date(Date.now() + this.ttl * 1000)
|
||||
}
|
||||
|
||||
return withCallback(this.collectionReady()
|
||||
.then(collection => collection.updateOne({_id: this.computeStorageId(sid)}, {$set: updateFields}))
|
||||
.then(result => {
|
||||
if (result.nModified === 0) {
|
||||
throw new Error('Unable to find the session to touch')
|
||||
} else {
|
||||
this.emit('touch', sid, session)
|
||||
}
|
||||
})
|
||||
, callback)
|
||||
return withCallback(
|
||||
this.collectionReady()
|
||||
.then(collection =>
|
||||
collection.updateOne(
|
||||
{ _id: this.computeStorageId(sid) },
|
||||
{ $set: updateFields },
|
||||
this.writeOperationOptions
|
||||
)
|
||||
)
|
||||
.then(result => {
|
||||
if (result.nModified === 0) {
|
||||
throw new Error('Unable to find the session to touch')
|
||||
} else {
|
||||
this.emit('touch', sid, session)
|
||||
}
|
||||
}),
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
all(callback) {
|
||||
return withCallback(
|
||||
this.collectionReady()
|
||||
.then(collection =>
|
||||
collection.find({
|
||||
$or: [
|
||||
{ expires: { $exists: false } },
|
||||
{ expires: { $gt: new Date() } },
|
||||
],
|
||||
})
|
||||
)
|
||||
.then(sessions => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const results = []
|
||||
sessions.forEach(
|
||||
session =>
|
||||
results.push(
|
||||
this.transformFunctions.unserialize(session.session)
|
||||
),
|
||||
err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
this.emit('all', results)
|
||||
resolve(results)
|
||||
}
|
||||
)
|
||||
})
|
||||
}),
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
destroy(sid, callback) {
|
||||
return withCallback(this.collectionReady()
|
||||
.then(collection => collection.deleteOne({_id: this.computeStorageId(sid)}))
|
||||
.then(() => this.emit('destroy', sid))
|
||||
, callback)
|
||||
return withCallback(
|
||||
this.collectionReady()
|
||||
.then(collection =>
|
||||
collection.deleteOne(
|
||||
{ _id: this.computeStorageId(sid) },
|
||||
this.writeOperationOptions
|
||||
)
|
||||
)
|
||||
.then(() => this.emit('destroy', sid)),
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
length(callback) {
|
||||
return withCallback(this.collectionReady()
|
||||
.then(collection => collection.count({}))
|
||||
, callback)
|
||||
return withCallback(
|
||||
this.collectionReady().then(collection =>
|
||||
collection.countDocuments({})
|
||||
),
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
clear(callback) {
|
||||
return withCallback(this.collectionReady()
|
||||
.then(collection => collection.drop())
|
||||
, callback)
|
||||
return withCallback(
|
||||
this.collectionReady().then(collection =>
|
||||
collection.drop(this.writeOperationOptions)
|
||||
),
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.db) {
|
||||
this.db.close()
|
||||
if (this.client) {
|
||||
return this.client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user