jwt-redis-session
Version:
JSON Web Token session middleware backed by Redis
453 lines (384 loc) • 13.9 kB
JavaScript
var path = require("path"),
_ = require("lodash"),
async = require("async"),
assert = require("chai").assert,
JWT = require("../index"),
server = require(path.join(__dirname, "fixture/server")),
request = require(path.join(__dirname, "fixture/client"));
describe("JWT Redis Session Tests", function(){
describe("Default JWT usage tests", function(){
var token = null;
before(function(done){
server.start(console.log, function(app, redisClient, callback){
app.use(JWT({
client: redisClient,
secret: "abc123"
}));
callback(8000);
}, done);
});
after(function(done){
server.inspect().client.quit();
server.end(done);
});
it("Should expose session methods to the application", function(done){
// hmm, setting a name on the function the "normal" way doesnt work...
var handler = function handler(req, res){
assert.isObject(req.session, "Request session is an object");
assert.isFunction(req.session.create, "Session has create function");
assert.isFunction(req.session.touch, "Session has touch function");
assert.isFunction(req.session.reload, "Session has reload function");
assert.isFunction(req.session.update, "Session has update function");
assert.isFunction(req.session.destroy, "Session has destroy function");
assert.isFunction(req.session.toJSON, "Session has toJSON function");
res.json({});
};
server.addRoute("/ping", "get", handler);
request({ path: "/ping", method: "get" }, null, function(error, resp){
assert.notOk(error, "Ping does not return an error");
assert.isObject(resp, "Ping response is an object");
server.removeRoute("/ping", "get");
done();
});
});
it("Should allow the user to create a new JWT session", function(done){
var handler = function handler(req, res){
req.session.create(function(error, token){
assert.isString(token, "Token is a string");
assert.notOk(error, "Error is null when creating token");
res.json({ token: token });
});
};
server.addRoute("/login", "get", handler);
request({ method: "get", path: "/login" }, null, function(error, resp){
assert.notOk(error, "Token creation did not return an error");
assert.isObject(resp, "Response is an object");
assert.property(resp, "token", "Response contains a token property");
assert.isString(resp.token, "Token is a string");
token = resp.token;
server.removeRoute("/login", "get");
done();
});
});
it("Should look for the JWT in the query, body, and headers", function(done){
var handler = function handler(req, res){
assert.isString(req.session.id, "Session has an ID");
assert.isString(req.session.jwt, "Session has a JWT");
res.json({});
};
server.addRoute("/ping", "all", handler);
var testResponse = function(error, resp, callback){
assert.notOk(error, "No error thrown");
assert.isObject(resp, "Response is an object");
assert.deepEqual(resp, {}, "Response is a blank object");
callback(error);
};
async.series([
function(callback){
request(
{ method: "get", path: "/ping" },
{ accessToken: token },
_.partialRight(testResponse, callback)
);
},
function(callback){
request(
{ method: "post", path: "/ping" },
{ accessToken: token },
_.partialRight(testResponse, callback)
);
},
function(callback){
request(
{
method: "get",
path: "/ping",
headers: {
"x-access-token": token
}
},
null,
_.partialRight(testResponse, callback)
);
}
], function(error){
assert.notOk(error, "Async series did not return an error");
server.removeRoute("/ping");
done();
});
});
it("Should expose the correct data to the application", function(done){
var handler = function handler(req, res){
assert.isString(req.session.id, "Session has an ID");
assert.isString(req.session.jwt, "Session has a JWT");
assert.isObject(req.session.claims, "Session has a claims object");
res.json({});
};
server.addRoute("/ping", "get", handler);
request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){
assert.notOk(error, "Ping did not return an error");
assert.isObject(resp, "Ping response is an object");
server.removeRoute("/ping", "get");
done();
});
});
it("Should allow the user to update and reload a session", function(done){
var handler = function handler(req, res){
req.session.foo = "bar";
req.session.update(function(error){
assert.notOk(error, "No error when updating session");
req.session.reload(function(err){
assert.notOk(err, "No error when reloading session");
assert.property(req.session, "foo", "Session has new foo property");
res.json(req.session.toJSON());
});
});
};
server.addRoute("/ping", "get", handler);
request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){
assert.notOk(error, "Ping did not return an error");
assert.isObject(resp, "Ping response is an object");
assert.property(resp, "foo", "Response has new foo property");
server.removeRoute("/ping", "get");
done();
});
});
it("Should allow the user to manually update the TTL on the session", function(done){
var handler = function handler(req, res){
req.session.touch(function(error){
assert.notOk(error, "No error when updating TTL on session");
res.json({});
});
};
server.addRoute("/ping", "get", handler);
request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){
assert.notOk(error, "Ping did not return an error");
assert.isObject(resp, "Ping response is an object");
server.removeRoute("/ping", "get");
done();
});
});
it("Should allow the user to serialize a session", function(done){
var session = { name: "Don Draper", realName: "Richard Witman" };
var handler = function handler(req, res){
_.extend(req.session, session);
req.session.update(function(error){
assert.notOk(error, "No error when updating session");
res.json(req.session.toJSON());
});
};
server.addRoute("/ping", "get", handler);
request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){
assert.notOk(error, "Ping did not return an error");
assert.isObject(resp, "Ping response is an object");
_.each(session, function(val, key){
assert.property(resp, key, "Response contains key for session property");
assert.equal(resp[key], session[key], "Response has correct value for key");
});
server.removeRoute("/ping", "get");
done();
});
});
it("Should allow the user to destroy a session", function(done){
var handler1 = function handler1(req, res){
req.session.destroy(function(error){
assert.notOk(error, "Destroy did not return an error");
res.json({});
});
};
var handler2 = function handler2(req, res){
assert.notOk(req.session.id, "Session does not have an ID");
assert.notOk(req.session.jwt, "Session does not have a JWT");
res.json(req.session.toJSON());
};
server.addRoute("/destroy", "get", handler1);
server.addRoute("/ping", "get", handler2);
async.series([
function(callback){
request({ method: "get", path: "/destroy" }, { accessToken: token }, function(error, resp){
assert.notOk(error, "Destroy call did not return an error");
assert.isObject(resp, "Destroy call returned an object");
callback(error);
});
},
function(callback){
request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){
assert.notOk(error, "Ping did not return an error");
assert.isObject(resp, "Ping returned an object");
assert.deepEqual(resp, {}, "Ping returned a blank object");
callback(error);
});
}
], function(error){
assert.notOk(error, "Async series did not return an error");
server.removeRoute("/destroy", "get");
server.removeRoute("/ping", "get");
done();
});
});
});
describe("Custom JWT usage tests", function(){
var customClaims = { foo: "bar" },
customRequestKey = "jwtSession",
customArg = "fancyAccessToken",
customRedisKeyspace = "jwt:";
var token = null;
before(function(done){
server.start(console.log, function(app, redisClient, callback){
app.use(JWT({
client: redisClient,
secret: "abc123",
requestKey: customRequestKey,
keyspace: customRedisKeyspace
}));
callback(8000);
}, done);
});
after(function(done){
server.inspect().client.quit();
server.end(done);
});
it("Should allow for a custom requestKey", function(done){
var handler = function handler(req, res){
assert.property(req, customRequestKey, "Request has custom requestKey property");
assert.isObject(req[customRequestKey], "Request has custom requestKey object");
res.json({});
};
server.addRoute("/ping", "get", handler);
request({ method: "get", path: "/ping" }, null, function(error, resp){
assert.notOk(error, "Ping did not return an error");
assert.isObject(resp, "Ping returned an object");
server.removeRoute("/ping", "get");
done();
});
});
it("Should allow the user to attach custom claims", function(done){
var claims = {
frodo: "baggins",
bilbo: "baggins"
};
var handler1 = function handler1(req, res){
req[customRequestKey].create(claims, function(error, token){
assert.isString(token, "Token is a string");
assert.notOk(error, "Error is null when creating token");
res.json({ token: token });
});
};
var handler2 = function handler2(req, res){
assert.isObject(req[customRequestKey], "Request object has JWT object");
assert.isObject(req[customRequestKey].claims, "Request object has JWT claims object");
_.each(claims, function(val, key){
assert.ok(req[customRequestKey].claims[key], "Request claims key matches original claims");
assert.equal(req[customRequestKey].claims[key], val, "Request claims value matches orignal claims value");
});
res.json({});
};
server.addRoute("/login", "get", handler1);
server.addRoute("/ping", "get", handler2);
async.series([
function(callback){
request({ method: "get", path: "/login" }, null, function(error, resp){
assert.notOk(error, "Token creation did not return an error");
assert.isObject(resp, "Response is an object");
assert.property(resp, "token", "Response contains a token property");
assert.isString(resp.token, "Token is a string");
token = resp.token;
callback(error);
});
},
function(callback){
request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){
assert.notOk(error, "Ping did not return an error");
assert.isObject(resp, "Ping returned an object");
callback(error);
});
}
], function(error){
assert.notOk(error, "Async series did not return error");
server.removeRoute("/ping", "get");
server.removeRoute("/login", "get");
done();
});
});
it("Should allow the user to use a custom request argument name", function(done){
var testResponse = function(error, resp, callback){
assert.notOk(error, "No error thrown");
assert.isObject(resp, "Response is an object");
assert.deepEqual(resp, {}, "Response is a blank object");
callback(error);
};
var restartServer = function(options, callback){
server.inspect().client.quit();
server.end(function(){
server.start(console.log, function(app, redisClient, cb){
options.client = redisClient;
app.use(JWT(options));
cb(8000);
}, callback);
});
};
var testData = {};
var handler1 = function handler1(req, res){
req.session.create(function(error, token){
assert.isString(token, "Token is a string");
assert.notOk(error, "Error is null when creating token");
res.json({ token: token });
});
};
var handler2 = function handler2(req, res){
assert.isObject(req.session, "Request object has JWT object");
assert.isString(req.session.jwt, "Request object found the token");
res.json({});
};
async.series([
function(callback){
restartServer({
secret: "abc123",
requestArg: customArg
}, callback);
},
function(callback){
server.addRoute("/login", "get", handler1);
server.addRoute("/ping", "all", handler2);
callback();
},
function(callback){
request({ method: "get", path: "/login" }, null, function(error, resp){
assert.notOk(error, "Token creation did not return an error");
assert.isObject(resp, "Response is an object");
assert.property(resp, "token", "Response contains a token property");
assert.isString(resp.token, "Token is a string");
token = resp.token;
testData[customArg] = token;
callback(error);
});
},
function(callback){
request({ method: "get", path: "/ping" }, testData, _.partialRight(testResponse, callback));
},
function(callback){
request({ method: "post", path: "/ping" }, testData, _.partialRight(testResponse, callback));
},
function(callback){
request({
method: "get",
path: "/ping",
headers: { "x-fancy-access-token": token }
},
null,
_.partialRight(testResponse, callback)
);
}
], function(error){
assert.notOk(error, "Async waterfall did not return an error");
server.removeRoute("/login", "get");
server.removeRoute("/ping");
restartServer({
secret: "abc123",
requestKey: customRequestKey,
keyspace: customRedisKeyspace
}, done);
});
});
});
});