Skip to content

Update to reflect express-jwt last version #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 44 additions & 21 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ var jwt = require('jsonwebtoken');
var unless = require('express-unless');
var restify = require('restify');
var async = require('async');
var set = require('lodash.set');

var InvalidCredentialsError = require('restify-errors').InvalidCredentialsError;

var DEFAULT_REVOKED_FUNCTION = function(_, __, cb) { return cb(null, false); };

var getClass = {}.toString;
function isFunction(object) {
return object && getClass.call(object) == '[object Function]';
return Object.prototype.toString.call(object) === '[object Function]';
}

function wrapStaticSecretInCallback(secret){
Expand All @@ -30,6 +30,7 @@ module.exports = function(options) {
var isRevokedCallback = options.isRevoked || DEFAULT_REVOKED_FUNCTION;

var _requestProperty = options.userProperty || options.requestProperty || 'user';
var _resultProperty = options.resultProperty;
var credentialsRequired = typeof options.credentialsRequired === 'undefined' ? true : options.credentialsRequired;

var middleware = function(req, res, next) {
Expand Down Expand Up @@ -61,7 +62,11 @@ module.exports = function(options) {
if (/^Bearer$/i.test(scheme)) {
token = credentials;
} else {
return next(new InvalidCredentialsError('Format is Authorization: Bearer [token]'));
if (credentialsRequired) {
return next(new InvalidCredentialsError('Format is Authorization: Bearer [token]'));
} else {
return next();
}
}
} else {
return next(new InvalidCredentialsError('Format is Authorization: Bearer [token]'));
Expand All @@ -76,35 +81,53 @@ module.exports = function(options) {
}
}

var dtoken = jwt.decode(token, { complete: true }) || {};
var dtoken;

async.parallel([
function(callback){
try {
dtoken = jwt.decode(token, { complete: true }) || {};
} catch (err) {
return next(new InvalidCredentialsError('Invalid token'));
}

async.waterfall([
function getSecret(callback){
var arity = secretCallback.length;
if (arity == 4) {
secretCallback(req, dtoken.header, dtoken.payload, callback);
} else { // arity == 3
secretCallback(req, dtoken.payload, callback);
}
},
function(callback){
isRevokedCallback(req, dtoken.payload, callback);
function verifyToken(secret, callback) {
jwt.verify(token, secret, options, function(err, decoded) {
if (err) {
callback(new InvalidCredentialsError('Invalid token'));
} else {
callback(null, decoded);
}
});
},
function checkRevoked(decoded, callback) {
isRevokedCallback(req, dtoken.payload, function (err, revoked) {
if (err) {
callback(err);
}
else if (revoked) {
callback(new restify.UnauthorizedError('The token has been revoked.'));
} else {
callback(null, decoded);
}
});
}
], function(err, results){

], function (err, result){
if (err) { return next(err); }
var revoked = results[1];
if (revoked){
return next(new restify.UnauthorizedError('The token has been revoked.'));
if (_resultProperty) {
set(res, _resultProperty, result);
} else {
set(req, _requestProperty, result);
}

var secret = results[0];

jwt.verify(token, secret, options, function(err, decoded) {
if (err && credentialsRequired) return next(new InvalidCredentialsError(err));

req[_requestProperty] = decoded;
next();
});
next();
});
};

Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "restify-jwt",
"version": "0.4.0",
"version": "0.5.0",
"description": "JWT authentication middleware.",
"keywords": [
"auth",
Expand Down Expand Up @@ -33,12 +33,14 @@
],
"main": "./lib",
"dependencies": {
"async": "^0.9.0",
"async": "^1.5.0",
"express-unless": "^0.3.0",
"jsonwebtoken": "^5.0.0"
"jsonwebtoken": "^7.3.0",
"lodash.set": "^4.0.0"
},
"devDependencies": {
"mocha": "1.x.x",
"conventional-changelog": "~1.1.0",
"restify": "3.x"
},
"peerDependencies": {
Expand All @@ -49,6 +51,6 @@
"node": ">= 0.10.0"
},
"scripts": {
"test": "node_modules/.bin/mocha --reporter spec"
"test": "mocha --reporter spec"
}
}
78 changes: 65 additions & 13 deletions test/jwt.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ describe('failure tests', function () {
});
});

it('should next if authorization header is not Bearer and credentialsRequired is false', function() {
req.headers = {};
req.headers.authorization = 'Basic foobar';
restifyjwt({secret: 'shhhh', credentialsRequired: false})(req, res, function(err) {
assert.ok(typeof err === 'undefined');
});
});

it('should throw if authorization header is not well-formatted jwt', function() {
req.headers = {};
req.headers.authorization = 'Bearer wrongjwt';
Expand All @@ -69,6 +77,15 @@ describe('failure tests', function () {
});
});

it('should throw if jwt is an invalid json', function() {
req.headers = {};
req.headers.authorization = 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.yJ1c2VybmFtZSI6InNhZ3VpYXIiLCJpYXQiOjE0NzEwMTg2MzUsImV4cCI6MTQ3MzYxMDYzNX0.foo';
restifyjwt({secret: 'shhhh'})(req, res, function(err) {
assert.ok(err);
assert.equal(err.body.code, 'InvalidCredentials');
});
});

it('should throw if authorization header is not valid jwt', function() {
var secret = 'shhhhhh';
var token = jwt.sign({foo: 'bar'}, secret);
Expand All @@ -78,7 +95,6 @@ describe('failure tests', function () {
restifyjwt({secret: 'different-shhhh'})(req, res, function(err) {
assert.ok(err);
assert.equal(err.body.code, 'InvalidCredentials');
assert.equal(err.we_cause.message, 'invalid signature');
});
});

Expand All @@ -91,7 +107,6 @@ describe('failure tests', function () {
restifyjwt({secret: 'shhhhhh', audience: 'not-expected-audience'})(req, res, function(err) {
assert.ok(err);
assert.equal(err.body.code, 'InvalidCredentials');
assert.equal(err.we_cause.message, 'jwt audience invalid. expected: not-expected-audience');
});
});

Expand All @@ -104,7 +119,6 @@ describe('failure tests', function () {
restifyjwt({secret: 'shhhhhh'})(req, res, function(err) {
assert.ok(err);
assert.equal(err.body.code, 'InvalidCredentials');
assert.equal(err.we_cause.message, 'jwt expired');
});
});

Expand All @@ -117,7 +131,6 @@ describe('failure tests', function () {
restifyjwt({secret: 'shhhhhh', issuer: 'http://wrong'})(req, res, function(err) {
assert.ok(err);
assert.equal(err.body.code, 'InvalidCredentials');
assert.equal(err.we_cause.message, 'jwt issuer invalid. expected: http://wrong');
});
});

Expand Down Expand Up @@ -154,7 +167,6 @@ describe('failure tests', function () {
restifyjwt({secret: secret})(req,res, function(err) {
assert.ok(err);
assert.equal(err.body.code, 'InvalidCredentials');
assert.equal(err.we_cause.message, 'invalid token');
});
});

Expand All @@ -175,6 +187,17 @@ describe('work tests', function () {
});
});

it('should work with nested properties', function() {
var secret = 'shhhhhh';
var token = jwt.sign({foo: 'bar'}, secret);

req.headers = {};
req.headers.authorization = 'Bearer ' + token;
restifyjwt({secret: secret, requestProperty: 'auth.token'})(req, res, function() {
assert.equal('bar', req.auth.token.foo);
});
});

it('should work if authorization header is valid with a buffer secret', function() {
var secret = new Buffer('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'base64');
var token = jwt.sign({foo: 'bar'}, secret);
Expand All @@ -197,22 +220,38 @@ describe('work tests', function () {
});
});

it('should work if no authorization header and credentials are not required', function() {
req = {};
restifyjwt({secret: 'shhhh', credentialsRequired: false})(req, res, function(err) {
assert(typeof err === 'undefined');
it('should set resultProperty if option provided', function() {
var secret = 'shhhhhh';
var token = jwt.sign({foo: 'bar'}, secret);

req = { };
res = { };
req.headers = {};
req.headers.authorization = 'Bearer ' + token;
restifyjwt({secret: secret, resultProperty: 'locals.user'})(req, res, function() {
assert.equal('bar', res.locals.user.foo);
assert.ok(typeof req.user === 'undefined');
});
});

it('should work if token is expired and credentials are not required', function() {
it('should ignore userProperty if resultProperty option provided', function() {
var secret = 'shhhhhh';
var token = jwt.sign({foo: 'bar', exp: 1382412921}, secret);
var token = jwt.sign({foo: 'bar'}, secret);

req = { };
res = { };
req.headers = {};
req.headers.authorization = 'Bearer ' + token;
restifyjwt({ secret: secret, credentialsRequired: false })(req, res, function(err) {
restifyjwt({secret: secret, userProperty: 'auth', resultProperty: 'locals.user'})(req, res, function() {
assert.equal('bar', res.locals.user.foo);
assert.ok(typeof req.auth === 'undefined');
});
});

it('should work if no authorization header and credentials are not required', function() {
req = {};
restifyjwt({secret: 'shhhh', credentialsRequired: false})(req, res, function(err) {
assert(typeof err === 'undefined');
assert(typeof req.user === 'undefined')
});
});

Expand All @@ -223,6 +262,19 @@ describe('work tests', function () {
});
});

it('should produce a stack trace that includes the failure reason', function() {
var req = {};
var token = jwt.sign({foo: 'bar'}, 'secretA');
req.headers = {};
req.headers.authorization = 'Bearer ' + token;

restifyjwt({secret: 'secretB'})(req, res, function(err) {
var index = err.stack.indexOf('InvalidCredentialsError')
assert.equal(index, 0, "Stack trace didn't include 'invalid signature' message.")
});

});

it('should work with a custom getToken function', function() {
var secret = 'shhhhhh';
var token = jwt.sign({foo: 'bar'}, secret);
Expand Down