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.0.0 to 2.0.1

1016

index.js

@@ -1,16 +0,20 @@

/*
Redis Sessions
// Generated by CoffeeScript 1.12.7
The MIT License (MIT)
/*
Redis Sessions
Copyright © 2013-2018 Patrick Liess, http://www.tcs.de
The MIT License (MIT)
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:
Copyright © 2013-2018 Patrick Liess, http://www.tcs.de
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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:
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.
*/
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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, RedisInst, RedisSessions, _,
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };
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;

@@ -23,271 +27,133 @@ _ = require("lodash");

RedisSessions = (function() {
// # RedisSessions
RedisSessions = (function(superClass) {
extend(RedisSessions, superClass);
// To create a new instance use:
// RedisSessions = require("redis-sessions")
// rs = new RedisSessions()
// Parameters:
// `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.
class RedisSessions extends EventEmitter {
constructor(o = {}) {
var ref, ref1, wipe;
super(o);
// ## Activity
// Get the number of active unique users (not sessions!) within the last *n* seconds
// **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 + ":";
if (((ref = o.client) != null ? (ref1 = ref.constructor) != null ? ref1.name : void 0 : void 0) === "RedisClient") {
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) => {
function RedisSessions(o) {
var 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 + ":";
if (((ref = o.client) != null ? (ref1 = ref.constructor) != null ? ref1.name : void 0 : void 0) === "RedisClient") {
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) {
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");
}
});
if (o.wipe !== 0) {
wipe = o.wipe || 600;
if (wipe < 10) {
wipe = 10;
}
setInterval(this._wipe, wipe * 1000);
};
})(this));
if (o.wipe !== 0) {
wipe = o.wipe || 600;
if (wipe < 10) {
wipe = 10;
}
setInterval(this._wipe, wipe * 1000);
}
}
activity(options, cb) {
boundMethodCheck(this, RedisSessions);
if (this._validate(options, ["app", "dt"], cb) === false) {
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);
return;
}
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
});
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;
}
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);
}
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);
}
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);
options.d = _.omit(options.d, nullkeys);
if (_.keys(options.d).length) {
thesession = thesession.concat(["d", JSON.stringify(options.d)]);
}
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
});
});
}
get(options, cb) {
var thekey;
boundMethodCheck(this, RedisSessions);
options = this._validate(options, ["app", "token"], cb);
if (options === false) {
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);
return;
}
thekey = `${this.redisns}${options.app}:${options.token}`;
this.redis.hmget(thekey, "id", "r", "w", "ttl", "d", "la", "ip", "no_resave", (err, resp) => {
if (resp[4] !== "OK") {
cb("Unknow error");
return;
}
cb(null, {
token: token
});
});
};
RedisSessions.prototype.get = function(options, cb) {
var thekey;
options = this._validate(options, ["app", "token"], cb);
if (options === false) {
return;
}
thekey = "" + this.redisns + options.app + ":" + options.token;
this.redis.hmget(thekey, "id", "r", "w", "ttl", "d", "la", "ip", "no_resave", (function(_this) {
return function(err, resp) {
var mc, o;

@@ -298,4 +164,3 @@ if (err) {

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

@@ -305,3 +170,2 @@ cb(null, {});

}
// Secret switch to disable updating the stats - we don't need this when we kill a session
if (options._noupdate) {

@@ -311,9 +175,8 @@ cb(null, o);

}
// Update the counters
mc = this._createMultiStatement(options.app, options.token, o.id, o.ttl, o.no_resave);
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) {

@@ -325,12 +188,13 @@ cb(err);

});
});
};
})(this));
};
RedisSessions.prototype._no_resave_check = function(session, options, cb, done) {
if (!session.no_resave) {
done();
return;
}
_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) => {
this.redis.zscore(this.redisns + "SESSIONS", options.app + ":" + options.token + ":" + session.id, (function(_this) {
return function(err, resp) {
if (err) {

@@ -340,4 +204,3 @@ cb(err);

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

@@ -347,13 +210,14 @@ return;

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

@@ -370,11 +234,12 @@ cb(err);

options.id = resp.id;
this._kill(options, cb);
});
}
_this._kill(options, cb);
};
})(this));
};
_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}`]];
this.redis.multi(mc).exec((err, resp) => {
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]];
this.redis.multi(mc).exec((function(_this) {
return function(err, resp) {
if (err) {

@@ -384,6 +249,4 @@ 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) {

@@ -402,16 +265,16 @@ cb(err);

}
});
};
})(this));
};
RedisSessions.prototype.killall = function(options, cb) {
var appsessionkey, appuserkey;
options = this._validate(options, ["app"], cb);
if (options === false) {
return;
}
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) => {
appsessionkey = "" + this.redisns + options.app + ":_sessions";
appuserkey = "" + this.redisns + options.app + ":_users";
this.redis.zrange(appsessionkey, 0, -1, (function(_this) {
return function(err, resp) {
var e, globalkeys, j, len, mc, thekey, tokenkeys, userkeys, ussets;

@@ -434,4 +297,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]);

@@ -445,8 +308,8 @@ }

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)];
this.redis.multi(mc).exec(function(err, resp) {
}).call(_this);
mc = [["zrem", appsessionkey].concat(resp), ["zrem", appuserkey].concat(userkeys), ["zrem", _this.redisns + "SESSIONS"].concat(globalkeys), ["del"].concat(ussets), ["del"].concat(tokenkeys)];
_this.redis.multi(mc).exec(function(err, resp) {
if (err) {

@@ -460,12 +323,13 @@ cb(err);

});
});
};
})(this));
};
RedisSessions.prototype.killsoid = function(options, cb) {
options = this._validate(options, ["app", "id"], cb);
if (options === false) {
return;
}
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) => {
this.redis.smembers("" + this.redisns + options.app + ":us:" + options.id, (function(_this) {
return function(err, resp) {
var j, len, mc, token;

@@ -483,13 +347,11 @@ if (err) {

mc = [];
// Grab all sessions we need to get
for (j = 0, len = resp.length; j < len; j++) {
token = resp[j];
// 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}`]);
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]);
}
mc.push(["exists", `${this.redisns}${options.app}:us:${options.id}`]);
this.redis.multi(mc).exec((err, resp) => {
mc.push(["exists", "" + _this.redisns + options.app + ":us:" + options.id]);
_this.redis.multi(mc).exec(function(err, resp) {
var e, k, len1, ref, total;

@@ -500,3 +362,2 @@ if (err) {

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

@@ -508,6 +369,4 @@ 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, {

@@ -523,24 +382,22 @@ kill: total

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

@@ -555,3 +412,2 @@ if (err) {

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

@@ -563,3 +419,2 @@ for (e in options.d) {

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

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

}
// 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);
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()]);
}

@@ -586,3 +438,3 @@ if (_.keys(resp.d).length) {

}
this.redis.multi(mc).exec(function(err, reply) {
_this.redis.multi(mc).exec(function(err, reply) {
if (err) {

@@ -592,15 +444,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;
}
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) => {
this.redis.zrevrangebyscore("" + this.redisns + options.app + ":_sessions", "+inf", this._now() - options.dt, (function(_this) {
return function(err, resp) {
var e;

@@ -620,13 +472,14 @@ if (err) {

})();
this._returnSessions(options, resp, cb);
});
_this._returnSessions(options, resp, cb);
};
})(this));
};
RedisSessions.prototype.soid = function(options, cb) {
options = this._validate(options, ["app", "id"], cb);
if (options === false) {
return;
}
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) => {
this.redis.smembers("" + this.redisns + options.app + ":us:" + options.id, (function(_this) {
return function(err, resp) {
if (err) {

@@ -636,109 +489,102 @@ cb(err);

}
this._returnSessions(options, resp, cb);
});
}
_this._returnSessions(options, resp, cb);
};
})(this));
};
// 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;
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]);
}
return o;
};
_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);
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));
}
return t + 'Z' + new Date().getTime().toString(36);
};
_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);
RedisSessions.prototype._handleError = function(cb, err, data) {
var _err, ref;
if (data == null) {
data = {};
}
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);
};
_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);
}
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;
}
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;
};
_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;
RedisSessions.prototype._returnSessions = function(options, sessions, cb) {
var e, mc;
if (!sessions.length) {
cb(null, {
sessions: []
});
return;
}
_returnSessions(options, sessions, cb) {
var e, mc;
boundMethodCheck(this, RedisSessions);
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"]);
}
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) => {
return results;
}).call(this);
this.redis.multi(mc).exec((function(_this) {
return function(err, resp) {
var j, len, o, session;

@@ -752,3 +598,3 @@ if (err) {

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

@@ -761,116 +607,112 @@ o.push(session);

});
});
}
};
})(this));
};
_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) {
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])) {
this._handleError(cb, "invalidValue", {
msg: "ttl must be a positive integer >= 10"
msg: "d." + e + " has a forbidden type. Only strings, numbers, boolean and null are allowed."
});
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;
}
_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;
}
});
}
return o;
};
// 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}$/
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;
}
});
};

@@ -886,4 +728,4 @@

}).call(this);
})(EventEmitter);
module.exports = RedisSessions;
{
"name": "redis-sessions",
"description": "An advanced session store for Redis",
"version": "2.0.0",
"version": "2.0.1",
"license": "MIT",

@@ -14,4 +14,4 @@ "author": "P. Liess <smrchy+npm@gmail.com>",

"dependencies": {
"redis": "^2.x",
"lodash": "^4.x"
"lodash": "^4.x",
"redis": "^2.x"
},

@@ -22,2 +22,3 @@ "optionalDependencies": {

"devDependencies": {
"coffeescript": "^1.12.7",
"async": "^2.x",

@@ -24,0 +25,0 @@ "mocha": "^5.2.0",

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