Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

redis-sessions

Package Overview
Dependencies
Maintainers
1
Versions
49
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redis-sessions - npm Package Compare versions

Comparing version 2.1.0 to 3.0.0

LICENSE.md

9

CHANGELOG.md

@@ -1,3 +0,10 @@

# C
# CHANGELOG
## 3.0.0
* Dropped suppoer for Node 4 and Node 6
* Added test support for Node 12
* Updated all dependencies
* Added LICENSE.md file
## 2.1.0

@@ -4,0 +11,0 @@

1113

index.js

@@ -1,198 +0,325 @@

// Generated by CoffeeScript 1.12.7
// Generated by CoffeeScript 2.5.1
var EventEmitter, NodeCache, RedisInst, RedisSessions, _,
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };
/*
Redis Sessions
_ = require("lodash");
The MIT License (MIT)
RedisInst = require("redis");
Copyright © 2013-2018 Patrick Liess, http://www.tcs.de
EventEmitter = require("events").EventEmitter;
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
NodeCache = require("node-cache");
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
RedisSessions = (function() {
// # RedisSessions
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var EventEmitter, NodeCache, RedisInst, RedisSessions, _,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
// To create a new instance use:
_ = require("lodash");
// RedisSessions = require("redis-sessions")
// rs = new RedisSessions()
RedisInst = require("redis");
// Parameters:
EventEmitter = require("events").EventEmitter;
// `port`: *optional* Default: `6379`. The Redis port.
// `host`, *optional* Default: `127.0.0.1`. The Redis host.
// `options`, *optional* Default: `{}`. Additional options. See [https://github.com/mranney/node_redis#rediscreateclientport-host-options](redis.createClient))
// `namespace`: *optional* Default: `rs`. The namespace prefix for all Redis keys used by this module.
// `wipe`: *optional* Default: `600`. The interval in second after which the timed out sessions are wiped. No value less than 10 allowed.
// `client`: *optional* An external RedisClient object which will be used for the connection.
// `cachetime` (Number) *optional* Number of seconds to cache sessions in memory. Can only be used if no `client` is supplied. See the "Cache" section. Default: `0`.
NodeCache = require("node-cache");
class RedisSessions extends EventEmitter {
constructor(o = {}) {
var isclient, redissub, ref, ref1, wipe;
super(o);
// ## Activity
RedisSessions = (function(superClass) {
extend(RedisSessions, superClass);
// Get the number of active unique users (not sessions!) within the last *n* seconds
function RedisSessions(o) {
var isclient, redissub, ref, ref1, wipe;
if (o == null) {
o = {};
}
this._wipe = bind(this._wipe, this);
this._returnSessions = bind(this._returnSessions, this);
this._initErrors = bind(this._initErrors, this);
this._handleError = bind(this._handleError, this);
this.soid = bind(this.soid, this);
this.soapp = bind(this.soapp, this);
this.set = bind(this.set, this);
this.quit = bind(this.quit, this);
this.ping = bind(this.ping, this);
this.killsoid = bind(this.killsoid, this);
this.killall = bind(this.killall, this);
this._kill = bind(this._kill, this);
this.kill = bind(this.kill, this);
this.get = bind(this.get, this);
this.create = bind(this.create, this);
this.activity = bind(this.activity, this);
RedisSessions.__super__.constructor.call(this, o);
this._initErrors();
this.redisns = o.namespace || "rs";
this.redisns = this.redisns + ":";
this.wiperinterval = null;
this.sessioncache = null;
this.iscache = false;
isclient = false;
if (((ref = o.client) != null ? (ref1 = ref.constructor) != null ? ref1.name : void 0 : void 0) === "RedisClient") {
isclient = true;
this.redis = o.client;
} else if (o.options && o.options.url) {
this.redis = RedisInst.createClient(o.options);
} else {
this.redis = RedisInst.createClient(o.port || 6379, o.host || "127.0.0.1", o.options || {});
}
this.connected = this.redis.connected || false;
this.redis.on("connect", (function(_this) {
return function() {
_this.connected = true;
_this.emit("connect");
};
})(this));
this.redis.on("error", (function(_this) {
return function(err) {
// **Parameters:**
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `dt` Delta time. Amount of seconds to check (e.g. 600 for the last 10 min.)
this.activity = this.activity.bind(this);
// ## Create
// Creates a session for an app and id.
// **Parameters:**
// An object with the following keys:
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `id` must be [a-zA-Z0-9_-] and 1-64 chars long
// * `ip` must be a valid IP4 address
// * `ttl` *optional* Default: 7200. Positive integer between 1 and 2592000 (30 days)
// **Example:**
// create({
// app: "forum",
// id: "user1234",
// ip: "156.78.90.12",
// ttl: 3600
// }, callback)
// Returns the token when successful.
this.create = this.create.bind(this);
// ## Get
// Get a session for an app and token.
// **Parameters:**
// An object with the following keys:
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `token` must be [a-zA-Z0-9] and 64 chars long
this.get = this.get.bind(this);
// ## Kill
// Kill a session for an app and token.
// **Parameters:**
// An object with the following keys:
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `token` must be [a-zA-Z0-9] and 64 chars long
this.kill = this.kill.bind(this);
// Helper to _kill a single session
// Used by @kill and @wipe
// Needs options.app, options.token and options.id
this._kill = this._kill.bind(this);
// ## Killall
// Kill all sessions of a single app
// Parameters:
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
this.killall = this.killall.bind(this);
// ## Kill all Sessions of Id
// Kill all sessions of a single id within an app
// Parameters:
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `id` must be [a-zA-Z0-9_-] and 1-64 chars long
this.killsoid = this.killsoid.bind(this);
// ## Ping
// Ping the Redis server
this.ping = this.ping.bind(this);
// ## Quit
// Quit the Redis connection
// This is needed if Redis-Session is used with AWS Lambda.
this.quit = this.quit.bind(this);
// ## Set
// Set/Update/Delete custom data for a single session.
// All custom data is stored in the `d` object which is a simple hash object structure.
// `d` might contain **one or more** keys with the following types: `string`, `number`, `boolean`, `null`.
// Keys with all values except `null` will be stored. If a key containts `null` the key will be removed.
// Note: If `d` already contains keys that are not supplied in the set request then these keys will be untouched.
// **Parameters:**
// An object with the following keys:
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `token` must be [a-zA-Z0-9] and 64 chars long
// * `d` must be an object with keys whose values only consist of strings, numbers, boolean and null.
this.set = this.set.bind(this);
// ## Session of App
// Returns all sessions of a single app that were active within the last *n* seconds
// Note: This might return a lot of data depending on `dt`. Use with care.
// **Parameters:**
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `dt` Delta time. Amount of seconds to check (e.g. 600 for the last 10 min.)
this.soapp = this.soapp.bind(this);
// ## Sessions of ID (soid)
// Returns all sessions of a single id
// **Parameters:**
// An object with the following keys:
// * `app` must be [a-zA-Z0-9_-] and 3-20 chars long
// * `id` must be [a-zA-Z0-9_-] and 1-64 chars long
this.soid = this.soid.bind(this);
this._handleError = this._handleError.bind(this);
this._initErrors = this._initErrors.bind(this);
this._returnSessions = this._returnSessions.bind(this);
// Wipe old sessions
// Called by internal housekeeping every `options.wipe` seconds
this._wipe = this._wipe.bind(this);
this._initErrors();
this.redisns = o.namespace || "rs";
this.redisns = this.redisns + ":";
this.wiperinterval = null;
this.sessioncache = null;
this.iscache = false;
isclient = false;
if (((ref = o.client) != null ? (ref1 = ref.constructor) != null ? ref1.name : void 0 : void 0) === "RedisClient") {
isclient = true;
this.redis = o.client;
} else if (o.options && o.options.url) {
this.redis = RedisInst.createClient(o.options);
} else {
this.redis = RedisInst.createClient(o.port || 6379, o.host || "127.0.0.1", o.options || {});
}
this.connected = this.redis.connected || false;
this.redis.on("connect", () => {
this.connected = true;
this.emit("connect");
});
this.redis.on("error", (err) => {
if (err.message.indexOf("ECONNREFUSED")) {
_this.connected = false;
_this.emit("disconnect");
this.connected = false;
this.emit("disconnect");
} else {
console.error("Redis ERROR", err);
_this.emit("error");
this.emit("error");
}
};
})(this));
if (o.cachetime) {
if (isclient) {
console.log("Warning: Caching is disabled. Must not supply `client` option");
} else {
o.cachetime = parseInt(o.cachetime, 10);
if (o.cachetime > 0) {
this.sessioncache = new NodeCache({
stdTTL: o.cachetime,
useClones: false
});
if (o.options && o.options.url) {
redissub = RedisInst.createClient(o.options);
} else {
redissub = RedisInst.createClient(o.port || 6379, o.host || "127.0.0.1", o.options || {});
});
if (o.cachetime) {
if (isclient) {
console.log("Warning: Caching is disabled. Must not supply `client` option");
} else {
o.cachetime = parseInt(o.cachetime, 10);
if (o.cachetime > 0) {
// Setup node-cache
this.sessioncache = new NodeCache({
stdTTL: o.cachetime,
useClones: false
});
// Setup the Redis subscriber to listen for changes
if (o.options && o.options.url) {
redissub = RedisInst.createClient(o.options);
} else {
redissub = RedisInst.createClient(o.port || 6379, o.host || "127.0.0.1", o.options || {});
}
// Setup the listener for change messages
redissub.on("message", (c, m) => {
// Message will only contain a `{app}:{token}` string. Just delete it from node-cache.
this.sessioncache.del(m);
});
// Setup the subscriber
this.iscache = true;
redissub.subscribe(`${this.redisns}cache`);
}
redissub.on("message", (function(_this) {
return function(c, m) {
_this.sessioncache.del(m);
};
})(this));
this.iscache = true;
redissub.subscribe(this.redisns + "cache");
}
}
}
if (o.wipe !== 0) {
wipe = o.wipe || 600;
if (wipe < 10) {
wipe = 10;
if (o.wipe !== 0) {
wipe = o.wipe || 600;
if (wipe < 10) {
wipe = 10;
}
this.wiperinterval = setInterval(this._wipe, wipe * 1000);
}
this.wiperinterval = setInterval(this._wipe, wipe * 1000);
}
}
RedisSessions.prototype.activity = function(options, cb) {
if (this._validate(options, ["app", "dt"], cb) === false) {
return;
}
this.redis.zcount("" + this.redisns + options.app + ":_users", this._now() - options.dt, "+inf", function(err, resp) {
if (err) {
cb(err);
activity(options, cb) {
boundMethodCheck(this, RedisSessions);
if (this._validate(options, ["app", "dt"], cb) === false) {
return;
}
cb(null, {
activity: resp
this.redis.zcount(`${this.redisns}${options.app}:_users`, this._now() - options.dt, "+inf", function(err, resp) {
if (err) {
cb(err);
return;
}
cb(null, {
activity: resp
});
});
});
};
}
RedisSessions.prototype.create = function(options, cb) {
var e, mc, nullkeys, thesession, token;
options.d = options.d || {
___duMmYkEy: null
};
options = this._validate(options, ["app", "id", "ip", "ttl", "d", "no_resave"], cb);
if (options === false) {
return;
}
token = this._createToken();
mc = this._createMultiStatement(options.app, token, options.id, options.ttl, false);
mc.push(["sadd", "" + this.redisns + options.app + ":us:" + options.id, token]);
thesession = ["hmset", "" + this.redisns + options.app + ":" + token, "id", options.id, "r", 1, "w", 1, "ip", options.ip, "la", this._now(), "ttl", parseInt(options.ttl)];
if (options.d) {
nullkeys = [];
for (e in options.d) {
if (options.d[e] === null) {
nullkeys.push(e);
create(options, cb) {
var e, mc, nullkeys, thesession, token;
boundMethodCheck(this, RedisSessions);
options.d = options.d || {
___duMmYkEy: null
};
options = this._validate(options, ["app", "id", "ip", "ttl", "d", "no_resave"], cb);
if (options === false) {
return;
}
token = this._createToken();
// Prepopulate the multi statement
mc = this._createMultiStatement(options.app, token, options.id, options.ttl, false);
mc.push(["sadd", `${this.redisns}${options.app}:us:${options.id}`, token]);
// Create the default session hash
thesession = ["hmset", `${this.redisns}${options.app}:${token}`, "id", options.id, "r", 1, "w", 1, "ip", options.ip, "la", this._now(), "ttl", parseInt(options.ttl)];
if (options.d) {
// Remove null values
nullkeys = [];
for (e in options.d) {
if (options.d[e] === null) {
nullkeys.push(e);
}
}
options.d = _.omit(options.d, nullkeys);
if (_.keys(options.d).length) {
thesession = thesession.concat(["d", JSON.stringify(options.d)]);
}
}
options.d = _.omit(options.d, nullkeys);
if (_.keys(options.d).length) {
thesession = thesession.concat(["d", JSON.stringify(options.d)]);
// Check for `no_resave` #36
if (options.no_resave) {
thesession.push("no_resave");
thesession.push(1);
}
mc.push(thesession);
// Run the redis statement
this.redis.multi(mc).exec(function(err, resp) {
if (err) {
cb(err);
return;
}
if (resp[4] !== "OK") {
cb("Unknow error");
return;
}
cb(null, {
token: token
});
});
}
if (options.no_resave) {
thesession.push("no_resave");
thesession.push(1);
}
mc.push(thesession);
this.redis.multi(mc).exec(function(err, resp) {
if (err) {
cb(err);
get(options, cb) {
var cache, cachekey, thekey;
boundMethodCheck(this, RedisSessions);
options = this._validate(options, ["app", "token"], cb);
if (options === false) {
return;
}
if (resp[4] !== "OK") {
cb("Unknow error");
return;
cachekey = `${options.app}:${options.token}`;
if (this.iscache && !options._nocache) {
// Try to find the session in cache
cache = this.sessioncache.get(cachekey);
if (cache !== void 0) {
cb(null, cache);
return;
}
}
cb(null, {
token: token
});
});
};
RedisSessions.prototype.get = function(options, cb) {
var cache, cachekey, thekey;
options = this._validate(options, ["app", "token"], cb);
if (options === false) {
return;
}
cachekey = options.app + ":" + options.token;
if (this.iscache && !options._nocache) {
cache = this.sessioncache.get(cachekey);
if (cache !== void 0) {
cb(null, cache);
return;
}
}
thekey = "" + this.redisns + cachekey;
this.redis.hmget(thekey, "id", "r", "w", "ttl", "d", "la", "ip", "no_resave", (function(_this) {
return function(err, resp) {
thekey = `${this.redisns}${cachekey}`;
this.redis.hmget(thekey, "id", "r", "w", "ttl", "d", "la", "ip", "no_resave", (err, resp) => {
var mc, o;

@@ -203,3 +330,4 @@ if (err) {

}
o = _this._prepareSession(resp);
// Prepare the data
o = this._prepareSession(resp);
if (o === null) {

@@ -209,5 +337,6 @@ cb(null, {});

}
if (_this.iscache) {
_this.sessioncache.set(cachekey, o);
if (this.iscache) {
this.sessioncache.set(cachekey, o);
}
// Secret switch to disable updating the stats - we don't need this when we kill a session
if (options._noupdate) {

@@ -217,8 +346,9 @@ cb(null, o);

}
mc = _this._createMultiStatement(options.app, options.token, o.id, o.ttl, o.no_resave);
// Update the counters
mc = this._createMultiStatement(options.app, options.token, o.id, o.ttl, o.no_resave);
mc.push(["hincrby", thekey, "r", 1]);
if (o.idle > 1) {
mc.push(["hset", thekey, "la", _this._now()]);
mc.push(["hset", thekey, "la", this._now()]);
}
_this.redis.multi(mc).exec(function(err, resp) {
this.redis.multi(mc).exec(function(err, resp) {
if (err) {

@@ -230,13 +360,12 @@ cb(err);

});
};
})(this));
};
});
}
RedisSessions.prototype._no_resave_check = function(session, options, cb, done) {
if (!session.no_resave) {
done();
return;
}
this.redis.zscore(this.redisns + "SESSIONS", options.app + ":" + options.token + ":" + session.id, (function(_this) {
return function(err, resp) {
_no_resave_check(session, options, cb, done) {
if (!session.no_resave) {
done();
return;
}
// Check if the session has run out
this.redis.zscore(`${this.redisns}SESSIONS`, `${options.app}:${options.token}:${session.id}`, (err, resp) => {
if (err) {

@@ -246,3 +375,4 @@ cb(err);

}
if (resp === null || resp < _this._now()) {
if (resp === null || resp < this._now()) {
// Session has run out.
cb(null, {});

@@ -252,14 +382,13 @@ return;

done();
};
})(this));
};
});
}
RedisSessions.prototype.kill = function(options, cb) {
options = this._validate(options, ["app", "token"], cb);
if (options === false) {
return;
}
options._noupdate = true;
this.get(options, (function(_this) {
return function(err, resp) {
kill(options, cb) {
boundMethodCheck(this, RedisSessions);
options = this._validate(options, ["app", "token"], cb);
if (options === false) {
return;
}
options._noupdate = true;
this.get(options, (err, resp) => {
if (err) {

@@ -276,15 +405,14 @@ cb(err);

options.id = resp.id;
_this._kill(options, cb);
};
})(this));
};
this._kill(options, cb);
});
}
RedisSessions.prototype._kill = function(options, cb) {
var mc;
mc = [["zrem", "" + this.redisns + options.app + ":_sessions", options.token + ":" + options.id], ["srem", "" + this.redisns + options.app + ":us:" + options.id, options.token], ["zrem", this.redisns + "SESSIONS", options.app + ":" + options.token + ":" + options.id], ["del", "" + this.redisns + options.app + ":" + options.token], ["exists", "" + this.redisns + options.app + ":us:" + options.id]];
if (this.iscache) {
mc.push(["publish", this.redisns + "cache", options.app + ":" + options.token]);
}
this.redis.multi(mc).exec((function(_this) {
return function(err, resp) {
_kill(options, cb) {
var mc;
boundMethodCheck(this, RedisSessions);
mc = [["zrem", `${this.redisns}${options.app}:_sessions`, `${options.token}:${options.id}`], ["srem", `${this.redisns}${options.app}:us:${options.id}`, options.token], ["zrem", `${this.redisns}SESSIONS`, `${options.app}:${options.token}:${options.id}`], ["del", `${this.redisns}${options.app}:${options.token}`], ["exists", `${this.redisns}${options.app}:us:${options.id}`]];
if (this.iscache) {
mc.push(["publish", `${this.redisns}cache`, `${options.app}:${options.token}`]);
}
this.redis.multi(mc).exec((err, resp) => {
if (err) {

@@ -294,4 +422,6 @@ cb(err);

}
// NOW. If the last reply of the multi statement is 0 then this was the last session.
// We need to remove the ZSET for this user also:
if (resp[4] === 0) {
_this.redis.zrem("" + _this.redisns + options.app + ":_users", options.id, function() {
this.redis.zrem(`${this.redisns}${options.app}:_users`, options.id, function() {
if (err) {

@@ -310,16 +440,16 @@ cb(err);

}
};
})(this));
};
});
}
RedisSessions.prototype.killall = function(options, cb) {
var appsessionkey, appuserkey;
options = this._validate(options, ["app"], cb);
if (options === false) {
return;
}
appsessionkey = "" + this.redisns + options.app + ":_sessions";
appuserkey = "" + this.redisns + options.app + ":_users";
this.redis.zrange(appsessionkey, 0, -1, (function(_this) {
return function(err, resp) {
killall(options, cb) {
var appsessionkey, appuserkey;
boundMethodCheck(this, RedisSessions);
options = this._validate(options, ["app"], cb);
if (options === false) {
return;
}
// First we need to get all sessions of the app
appsessionkey = `${this.redisns}${options.app}:_sessions`;
appuserkey = `${this.redisns}${options.app}:_users`;
this.redis.zrange(appsessionkey, 0, -1, (err, resp) => {
var e, globalkeys, j, k, len, len1, mc, thekey, tokenkeys, userkeys, ussets;

@@ -342,4 +472,4 @@ if (err) {

thekey = e.split(":");
globalkeys.push(options.app + ":" + e);
tokenkeys.push("" + _this.redisns + options.app + ":" + thekey[0]);
globalkeys.push(`${options.app}:${e}`);
tokenkeys.push(`${this.redisns}${options.app}:${thekey[0]}`);
userkeys.push(thekey[1]);

@@ -353,14 +483,14 @@ }

e = userkeys[k];
results.push("" + this.redisns + options.app + ":us:" + e);
results.push(`${this.redisns}${options.app}:us:${e}`);
}
return results;
}).call(_this);
mc = [["zrem", appsessionkey].concat(resp), ["zrem", appuserkey].concat(userkeys), ["zrem", _this.redisns + "SESSIONS"].concat(globalkeys), ["del"].concat(ussets), ["del"].concat(tokenkeys)];
if (_this.iscache) {
}).call(this);
mc = [["zrem", appsessionkey].concat(resp), ["zrem", appuserkey].concat(userkeys), ["zrem", `${this.redisns}SESSIONS`].concat(globalkeys), ["del"].concat(ussets), ["del"].concat(tokenkeys)];
if (this.iscache) {
for (k = 0, len1 = resp.length; k < len1; k++) {
e = resp[k];
mc.push(["publish", _this.redisns + "cache", options.app + ":" + (e.split(":")[0])]);
mc.push(["publish", `${this.redisns}cache`, `${options.app}:${e.split(":")[0]}`]);
}
}
_this.redis.multi(mc).exec(function(err, resp) {
this.redis.multi(mc).exec(function(err, resp) {
if (err) {

@@ -374,13 +504,12 @@ cb(err);

});
};
})(this));
};
});
}
RedisSessions.prototype.killsoid = function(options, cb) {
options = this._validate(options, ["app", "id"], cb);
if (options === false) {
return;
}
this.redis.smembers("" + this.redisns + options.app + ":us:" + options.id, (function(_this) {
return function(err, resp) {
killsoid(options, cb) {
boundMethodCheck(this, RedisSessions);
options = this._validate(options, ["app", "id"], cb);
if (options === false) {
return;
}
this.redis.smembers(`${this.redisns}${options.app}:us:${options.id}`, (err, resp) => {
var j, len, mc, token;

@@ -398,14 +527,16 @@ if (err) {

mc = [];
// Grab all sessions we need to get
for (j = 0, len = resp.length; j < len; j++) {
token = resp[j];
mc.push(["zrem", "" + _this.redisns + options.app + ":_sessions", token + ":" + options.id]);
mc.push(["srem", "" + _this.redisns + options.app + ":us:" + options.id, token]);
mc.push(["zrem", _this.redisns + "SESSIONS", options.app + ":" + token + ":" + options.id]);
mc.push(["del", "" + _this.redisns + options.app + ":" + token]);
if (_this.iscache) {
mc.push(["publish", _this.redisns + "cache", options.app + ":" + token]);
// Add to the multi commands array
mc.push(["zrem", `${this.redisns}${options.app}:_sessions`, `${token}:${options.id}`]);
mc.push(["srem", `${this.redisns}${options.app}:us:${options.id}`, token]);
mc.push(["zrem", `${this.redisns}SESSIONS`, `${options.app}:${token}:${options.id}`]);
mc.push(["del", `${this.redisns}${options.app}:${token}`]);
if (this.iscache) {
mc.push(["publish", `${this.redisns}cache`, `${options.app}:${token}`]);
}
}
mc.push(["exists", "" + _this.redisns + options.app + ":us:" + options.id]);
_this.redis.multi(mc).exec(function(err, resp) {
mc.push(["exists", `${this.redisns}${options.app}:us:${options.id}`]);
this.redis.multi(mc).exec((err, resp) => {
var e, k, len1, ref, total;

@@ -416,2 +547,3 @@ if (err) {

}
// get the amount of deleted sessions
total = 0;

@@ -423,4 +555,6 @@ ref = resp.slice(3);

}
// NOW. If the last reply of the multi statement is 0 then this was the last session.
// We need to remove the ZSET for this user also:
if (_.last(resp) === 0) {
_this.redis.zrem("" + _this.redisns + options.app + ":_users", options.id, function() {
this.redis.zrem(`${this.redisns}${options.app}:_users`, options.id, function() {
cb(null, {

@@ -436,26 +570,28 @@ kill: total

});
};
})(this));
};
});
}
RedisSessions.prototype.ping = function(cb) {
this.redis.ping(cb);
};
ping(cb) {
boundMethodCheck(this, RedisSessions);
this.redis.ping(cb);
}
RedisSessions.prototype.quit = function() {
if (this.wiperinterval !== null) {
clearInterval(this.wiperinterval);
quit() {
boundMethodCheck(this, RedisSessions);
if (this.wiperinterval !== null) {
clearInterval(this.wiperinterval);
}
this.redis.quit();
}
this.redis.quit();
};
RedisSessions.prototype.set = function(options, cb) {
options = this._validate(options, ["app", "token", "d", "no_resave"], cb);
if (options === false) {
return;
}
options._noupdate = true;
options._nocache = true;
this.get(options, (function(_this) {
return function(err, resp) {
set(options, cb) {
boundMethodCheck(this, RedisSessions);
options = this._validate(options, ["app", "token", "d", "no_resave"], cb);
if (options === false) {
return;
}
options._noupdate = true;
options._nocache = true;
// Get the session
this.get(options, (err, resp) => {
var e, mc, nullkeys, thekey;

@@ -470,2 +606,3 @@ if (err) {

}
// Cleanup `d`
nullkeys = [];

@@ -477,2 +614,3 @@ for (e in options.d) {

}
// OK ready to set some data
if (resp.d) {

@@ -483,7 +621,10 @@ resp.d = _.extend(_.omit(resp.d, nullkeys), _.omit(options.d, nullkeys));

}
thekey = "" + _this.redisns + options.app + ":" + options.token;
mc = _this._createMultiStatement(options.app, options.token, resp.id, resp.ttl, resp.no_resave);
// We now have a cleaned version of resp.d ready to save back to Redis.
// If resp.d contains no keys we want to delete the `d` key within the hash though.
thekey = `${this.redisns}${options.app}:${options.token}`;
mc = this._createMultiStatement(options.app, options.token, resp.id, resp.ttl, resp.no_resave);
mc.push(["hincrby", thekey, "w", 1]);
// Only update the `la` (last access) value if more than 1 second idle
if (resp.idle > 1) {
mc.push(["hset", thekey, "la", _this._now()]);
mc.push(["hset", thekey, "la", this._now()]);
}

@@ -496,6 +637,6 @@ if (_.keys(resp.d).length) {

}
if (_this.iscache) {
mc.push(["publish", _this.redisns + "cache", options.app + ":" + options.token]);
if (this.iscache) {
mc.push(["publish", `${this.redisns}cache`, `${options.app}:${options.token}`]);
}
_this.redis.multi(mc).exec(function(err, reply) {
this.redis.multi(mc).exec(function(err, reply) {
if (err) {

@@ -505,15 +646,15 @@ cb(err);

}
// Set `w` to the actual counter value
resp.w = reply[3];
cb(null, resp);
});
};
})(this));
};
});
}
RedisSessions.prototype.soapp = function(options, cb) {
if (this._validate(options, ["app", "dt"], cb) === false) {
return;
}
this.redis.zrevrangebyscore("" + this.redisns + options.app + ":_sessions", "+inf", this._now() - options.dt, (function(_this) {
return function(err, resp) {
soapp(options, cb) {
boundMethodCheck(this, RedisSessions);
if (this._validate(options, ["app", "dt"], cb) === false) {
return;
}
this.redis.zrevrangebyscore(`${this.redisns}${options.app}:_sessions`, "+inf", this._now() - options.dt, (err, resp) => {
var e;

@@ -533,14 +674,13 @@ if (err) {

})();
_this._returnSessions(options, resp, cb);
};
})(this));
};
this._returnSessions(options, resp, cb);
});
}
RedisSessions.prototype.soid = function(options, cb) {
options = this._validate(options, ["app", "id"], cb);
if (options === false) {
return;
}
this.redis.smembers("" + this.redisns + options.app + ":us:" + options.id, (function(_this) {
return function(err, resp) {
soid(options, cb) {
boundMethodCheck(this, RedisSessions);
options = this._validate(options, ["app", "id"], cb);
if (options === false) {
return;
}
this.redis.smembers(`${this.redisns}${options.app}:us:${options.id}`, (err, resp) => {
if (err) {

@@ -550,102 +690,109 @@ cb(err);

}
_this._returnSessions(options, resp, cb);
};
})(this));
};
this._returnSessions(options, resp, cb);
});
}
RedisSessions.prototype._createMultiStatement = function(app, token, id, ttl, no_resave) {
var now, o;
now = this._now();
o = [["zadd", "" + this.redisns + app + ":_sessions", now, token + ":" + id], ["zadd", "" + this.redisns + app + ":_users", now, id], ["zadd", this.redisns + "SESSIONS", now + ttl, app + ":" + token + ":" + id]];
if (no_resave) {
o.push(["hset", "" + this.redisns + app + ":" + token, "ttl", ttl]);
// Helpers
_createMultiStatement(app, token, id, ttl, no_resave) {
var now, o;
now = this._now();
o = [["zadd", `${this.redisns}${app}:_sessions`, now, `${token}:${id}`], ["zadd", `${this.redisns}${app}:_users`, now, id], ["zadd", `${this.redisns}SESSIONS`, now + ttl, `${app}:${token}:${id}`]];
if (no_resave) {
o.push(["hset", `${this.redisns}${app}:${token}`, "ttl", ttl]);
}
return o;
}
return o;
};
RedisSessions.prototype._createToken = function() {
var i, j, possible, t;
t = "";
possible = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz0123456789";
for (i = j = 0; j < 55; i = ++j) {
t += possible.charAt(Math.floor(Math.random() * possible.length));
_createToken() {
var i, j, possible, t;
t = "";
// Note we don't use Z as a valid character here
possible = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz0123456789";
for (i = j = 0; j < 55; i = ++j) {
t += possible.charAt(Math.floor(Math.random() * possible.length));
}
// add the current time in ms to the very end seperated by a Z
return t + 'Z' + new Date().getTime().toString(36);
}
return t + 'Z' + new Date().getTime().toString(36);
};
RedisSessions.prototype._handleError = function(cb, err, data) {
var _err, ref;
if (data == null) {
data = {};
_handleError(cb, err, data = {}) {
var _err, ref;
boundMethodCheck(this, RedisSessions);
// try to create a error Object with humanized message
if (_.isString(err)) {
_err = new Error();
_err.name = err;
_err.message = ((ref = this._ERRORS) != null ? typeof ref[err] === "function" ? ref[err](data) : void 0 : void 0) || "unkown";
} else {
_err = err;
}
cb(_err);
}
if (_.isString(err)) {
_err = new Error();
_err.name = err;
_err.message = ((ref = this._ERRORS) != null ? typeof ref[err] === "function" ? ref[err](data) : void 0 : void 0) || "unkown";
} else {
_err = err;
_initErrors() {
var key, msg, ref;
boundMethodCheck(this, RedisSessions);
this._ERRORS = {};
ref = this.ERRORS;
for (key in ref) {
msg = ref[key];
this._ERRORS[key] = _.template(msg);
}
}
cb(_err);
};
RedisSessions.prototype._initErrors = function() {
var key, msg, ref;
this._ERRORS = {};
ref = this.ERRORS;
for (key in ref) {
msg = ref[key];
this._ERRORS[key] = _.template(msg);
_now() {
return parseInt((new Date()).getTime() / 1000);
}
};
RedisSessions.prototype._now = function() {
return parseInt((new Date()).getTime() / 1000);
};
RedisSessions.prototype._prepareSession = function(session) {
var now, o;
now = this._now();
if (session[0] === null) {
return null;
_prepareSession(session) {
var now, o;
now = this._now();
if (session[0] === null) {
return null;
}
// Create the return object
o = {
id: session[0],
r: Number(session[1]),
w: Number(session[2]),
ttl: Number(session[3]),
idle: now - session[5],
ip: session[6]
};
// Oh wait. If o.ttl < o.idle we need to bail out.
if (o.ttl < o.idle) {
// We return an empty session object
return null;
}
// Support for `no_resave` #36
if (session[7] === "1") {
o.no_resave = true;
o.ttl = o.ttl - o.idle;
}
// Parse the content of `d`
if (session[4]) {
o.d = JSON.parse(session[4]);
}
return o;
}
o = {
id: session[0],
r: Number(session[1]),
w: Number(session[2]),
ttl: Number(session[3]),
idle: now - session[5],
ip: session[6]
};
if (o.ttl < o.idle) {
return null;
}
if (session[7] === "1") {
o.no_resave = true;
o.ttl = o.ttl - o.idle;
}
if (session[4]) {
o.d = JSON.parse(session[4]);
}
return o;
};
RedisSessions.prototype._returnSessions = function(options, sessions, cb) {
var e, mc;
if (!sessions.length) {
cb(null, {
sessions: []
});
return;
}
mc = (function() {
var j, len, results;
results = [];
for (j = 0, len = sessions.length; j < len; j++) {
e = sessions[j];
results.push(["hmget", "" + this.redisns + options.app + ":" + e, "id", "r", "w", "ttl", "d", "la", "ip", "no_resave"]);
_returnSessions(options, sessions, cb) {
var e, mc;
boundMethodCheck(this, RedisSessions);
if (!sessions.length) {
cb(null, {
sessions: []
});
return;
}
return results;
}).call(this);
this.redis.multi(mc).exec((function(_this) {
return function(err, resp) {
mc = (function() {
var j, len, results;
results = [];
for (j = 0, len = sessions.length; j < len; j++) {
e = sessions[j];
results.push(["hmget", `${this.redisns}${options.app}:${e}`, "id", "r", "w", "ttl", "d", "la", "ip", "no_resave"]);
}
return results;
}).call(this);
this.redis.multi(mc).exec((err, resp) => {
var j, len, o, session;

@@ -659,3 +806,3 @@ if (err) {

e = resp[j];
session = _this._prepareSession(e);
session = this._prepareSession(e);
if (session !== null) {

@@ -668,112 +815,116 @@ o.push(session);

});
};
})(this));
};
});
}
RedisSessions.prototype._VALID = {
app: /^([a-zA-Z0-9_-]){3,20}$/,
id: /^(.*?){1,128}$/,
ip: /^.{1,39}$/,
token: /^([a-zA-Z0-9]){64}$/
};
RedisSessions.prototype._validate = function(o, items, cb) {
var e, item, j, keys, len;
for (j = 0, len = items.length; j < len; j++) {
item = items[j];
switch (item) {
case "app":
case "id":
case "ip":
case "token":
if (!o[item]) {
this._handleError(cb, "missingParameter", {
item: item
});
return false;
}
o[item] = o[item].toString();
if (!this._VALID[item].test(o[item])) {
this._handleError(cb, "invalidFormat", {
item: item
});
return false;
}
break;
case "ttl":
o.ttl = parseInt(o.ttl || 7200, 10);
if (_.isNaN(o.ttl) || !_.isNumber(o.ttl) || o.ttl < 10) {
this._handleError(cb, "invalidValue", {
msg: "ttl must be a positive integer >= 10"
});
return false;
}
break;
case "no_resave":
if (o.no_resave === true) {
o.no_resave = true;
} else {
o.no_resave = false;
}
break;
case "dt":
o[item] = parseInt(o[item], 10);
if (_.isNaN(o[item]) || !_.isNumber(o[item]) || o[item] < 10) {
this._handleError(cb, "invalidValue", {
msg: "ttl must be a positive integer >= 10"
});
return false;
}
break;
case "d":
if (!o[item]) {
this._handleError(cb, "missingParameter", {
item: item
});
return false;
}
if (!_.isObject(o.d) || _.isArray(o.d)) {
this._handleError(cb, "invalidValue", {
msg: "d must be an object"
});
return false;
}
keys = _.keys(o.d);
if (!keys.length) {
this._handleError(cb, "invalidValue", {
msg: "d must containt at least one key."
});
return false;
}
for (e in o.d) {
if (!_.isString(o.d[e]) && !_.isNumber(o.d[e]) && !_.isBoolean(o.d[e]) && !_.isNull(o.d[e])) {
_validate(o, items, cb) {
var e, item, j, keys, len;
for (j = 0, len = items.length; j < len; j++) {
item = items[j];
switch (item) {
case "app":
case "id":
case "ip":
case "token":
if (!o[item]) {
this._handleError(cb, "missingParameter", {
item: item
});
return false;
}
o[item] = o[item].toString();
if (!this._VALID[item].test(o[item])) {
this._handleError(cb, "invalidFormat", {
item: item
});
return false;
}
break;
case "ttl":
o.ttl = parseInt(o.ttl || 7200, 10);
if (_.isNaN(o.ttl) || !_.isNumber(o.ttl) || o.ttl < 10) {
this._handleError(cb, "invalidValue", {
msg: "d." + e + " has a forbidden type. Only strings, numbers, boolean and null are allowed."
msg: "ttl must be a positive integer >= 10"
});
return false;
}
}
break;
case "no_resave":
if (o.no_resave === true) {
o.no_resave = true;
} else {
o.no_resave = false;
}
break;
case "dt":
o[item] = parseInt(o[item], 10);
if (_.isNaN(o[item]) || !_.isNumber(o[item]) || o[item] < 10) {
this._handleError(cb, "invalidValue", {
msg: "ttl must be a positive integer >= 10"
});
return false;
}
break;
case "d":
if (!o[item]) {
this._handleError(cb, "missingParameter", {
item: item
});
return false;
}
if (!_.isObject(o.d) || _.isArray(o.d)) {
this._handleError(cb, "invalidValue", {
msg: "d must be an object"
});
return false;
}
keys = _.keys(o.d);
if (!keys.length) {
this._handleError(cb, "invalidValue", {
msg: "d must containt at least one key."
});
return false;
}
// Check if every key is either a boolean, string or a number
for (e in o.d) {
if (!_.isString(o.d[e]) && !_.isNumber(o.d[e]) && !_.isBoolean(o.d[e]) && !_.isNull(o.d[e])) {
this._handleError(cb, "invalidValue", {
msg: `d.${e} has a forbidden type. Only strings, numbers, boolean and null are allowed.`
});
return false;
}
}
}
}
return o;
}
return o;
_wipe() {
var that;
boundMethodCheck(this, RedisSessions);
that = this;
this.redis.zrangebyscore(`${this.redisns}SESSIONS`, "-inf", this._now(), function(err, resp) {
if (!err && resp.length) {
_.each(resp, function(e) {
var options;
e = e.split(':');
options = {
app: e[0],
token: e[1],
id: e[2]
};
that._kill(options, function() {});
});
return;
}
});
}
};
RedisSessions.prototype._wipe = function() {
var that;
that = this;
this.redis.zrangebyscore(this.redisns + "SESSIONS", "-inf", this._now(), function(err, resp) {
if (!err && resp.length) {
_.each(resp, function(e) {
var options;
e = e.split(':');
options = {
app: e[0],
token: e[1],
id: e[2]
};
that._kill(options, function() {});
});
return;
}
});
// Validation regex used by _validate
RedisSessions.prototype._VALID = {
app: /^([a-zA-Z0-9_-]){3,20}$/,
id: /^(.*?){1,128}$/,
ip: /^.{1,39}$/,
token: /^([a-zA-Z0-9]){64}$/
};

@@ -789,4 +940,4 @@

})(EventEmitter);
}).call(this);
module.exports = RedisSessions;
{
"name": "redis-sessions",
"description": "An advanced session store for Redis",
"version": "2.1.0",
"version": "3.0.0",
"license": "MIT",
"author": "P. Liess <smrchy+npm@gmail.com>",
"engines": {
"node": "> 0.10.20"
"node": "> 8"
},

@@ -15,11 +15,11 @@ "scripts": {

"dependencies": {
"lodash": "^4.17.11",
"node-cache": "^4.2.0",
"redis": "^2.x"
"lodash": "^4.17.15",
"node-cache": "^5.1.1",
"redis": "^3.0.2"
},
"devDependencies": {
"coffeescript": "^1.12.7",
"async": "^2.x",
"mocha": "^5.2.0",
"should": "11.1.0"
"coffeescript": "^2.5.1",
"async": "^3.2.0",
"mocha": "^8.0.1",
"should": "13.2.3"
},

@@ -36,3 +36,9 @@ "keywords": [

"url": "http://github.com/smrchy/redis-sessions.git"
},
"mocha": {
"bail": true,
"slow": 5,
"timeout": 8000,
"reporter": "spec"
}
}

@@ -1,2 +0,2 @@

// Generated by CoffeeScript 1.12.7
// Generated by CoffeeScript 2.5.1
var RedisSessions, _, async, should;

@@ -919,2 +919,3 @@

resp.d.e.should.equal(20.5);
// Delay the reply just a tiny bit in case Travis works slower
setTimeout(done, 20);

@@ -921,0 +922,0 @@ });

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc