connect-redis-crypto
Version:
Redis session store for Connect
197 lines (159 loc) • 4.8 kB
JavaScript
/*!
* Connect - Redis
* Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
module.exports = function(session) {
const Store = session.Store
// All callbacks should have a noop if none provided for compatibility
// with the most Redis clients.
const noop = () => {}
class RedisStore extends Store {
constructor(options = {}) {
super(options)
if (!options.client) {
throw new Error('A client must be directly provided to the RedisStore')
}
this.prefix = options.prefix == null ? 'sess:' : options.prefix
this.scanCount = Number(options.scanCount) || 100
this.serializer = options.serializer || JSON
this.client = options.client
this.ttl = options.ttl || 86400 // One day in seconds.
this.disableTouch = options.disableTouch || false
if (options.secret) {
this.secret = options.secret || false
this.kruptein = require('kruptein')(options)
}
}
get(sid, cb = noop) {
let key = this.prefix + sid
this.client.get(key, (err, data) => {
if (err) return cb(err)
if (!data) return cb()
if (this.secret) {
this.kruptein.get(this.secret, data, (err, pt) => {
if (err) return cb(err)
data = pt
})
}
let result
try {
result = this.serializer.parse(data)
} catch (err) {
return cb(err)
}
return cb(null, result)
})
}
set(sid, sess, cb = noop) {
let args = [this.prefix + sid]
if (this.secret) {
this.kruptein.set(this.secret, sess, (err, ct) => {
if (err) return cb(err)
sess = JSON.parse(ct)
})
}
let value
try {
value = this.serializer.stringify(sess)
} catch (er) {
return cb(er)
}
args.push(value)
args.push('EX', this._getTTL(sess))
this.client.set(args, cb)
}
touch(sid, sess, cb = noop) {
if (this.disableTouch) return cb()
let key = this.prefix + sid
this.client.expire(key, this._getTTL(sess), (err, ret) => {
if (err) return cb(err)
if (ret !== 1) return cb(null, 'EXPIRED')
cb(null, 'OK')
})
}
destroy(sid, cb = noop) {
let key = this.prefix + sid
this.client.del(key, cb)
}
clear(cb = noop) {
this._getAllKeys((err, keys) => {
if (err) return cb(err)
this.client.del(keys, cb)
})
}
length(cb = noop) {
this._getAllKeys((err, keys) => {
if (err) return cb(err)
return cb(null, keys.length)
})
}
ids(cb = noop) {
let prefixLen = this.prefix.length
this._getAllKeys((err, keys) => {
if (err) return cb(err)
keys = keys.map(key => key.substr(prefixLen))
return cb(null, keys)
})
}
all(cb = noop) {
let prefixLen = this.prefix.length
this._getAllKeys((err, keys) => {
if (err) return cb(err)
if (keys.length === 0) return cb(null, [])
this.client.mget(keys, (err, sessions) => {
if (err) return cb(err)
let result
try {
result = sessions.reduce((accum, data, index) => {
if (!data) return accum
if (this.secret) {
this.kruptein.get(this.secret, data, (err, pt) => {
if (err) return cb(err)
data = pt
})
}
data = this.serializer.parse(data)
data.id = keys[index].substr(prefixLen)
accum.push(data)
return accum
}, [])
} catch (e) {
err = e
}
return cb(err, result)
})
})
}
_getTTL(sess) {
let ttl
if (sess && sess.cookie && sess.cookie.expires) {
let ms = Number(new Date(sess.cookie.expires)) - Date.now()
ttl = Math.ceil(ms / 1000)
} else {
ttl = this.ttl
}
return ttl
}
_getAllKeys(cb = noop) {
let pattern = this.prefix + '*'
this._scanKeys({}, 0, pattern, this.scanCount, cb)
}
_scanKeys(keys = {}, cursor, pattern, count, cb = noop) {
let args = [cursor, 'match', pattern, 'count', count]
this.client.scan(args, (err, data) => {
if (err) return cb(err)
let [nextCursorId, scanKeys] = data
for (let key of scanKeys) {
keys[key] = true
}
// This can be a string or a number. We check both.
if (Number(nextCursorId) !== 0) {
return this._scanKeys(keys, nextCursorId, pattern, count, cb)
}
cb(null, Object.keys(keys))
})
}
}
return RedisStore
}