Skip to content
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
59 changes: 35 additions & 24 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ exports.register.attributes = {
pkg: require('../package.json')
};

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

internals.implementation = function (server, options) {

Expand Down Expand Up @@ -53,42 +56,50 @@ internals.implementation = function (server, options) {

var token = parts[1];

jwt.verify(token, settings.key, function(err, decoded) {
if(err && err.message === 'jwt expired') {
return reply(Boom.unauthorized('Expired token received for JSON Web Token validation', 'Bearer'));
} else if (err) {
return reply(Boom.unauthorized('Invalid signature received for JSON Web Token validation', 'Bearer'));
}
var getKey = isFunction(settings.key) ?
settings.key :
function(token, callback) { callback(null, settings.key); };

if (!settings.validateFunc) {
return reply(null, { credentials: decoded });
}
getKey(token, function(err, key){
if (err) { return reply(Boom.wrap(err)); }

// handle err
jwt.verify(token, key, function(err, decoded) {
if(err && err.message === 'jwt expired') {
return reply(Boom.unauthorized('Expired token received for JSON Web Token validation', 'Bearer'));
} else if (err) {
return reply(Boom.unauthorized('Invalid signature received for JSON Web Token validation', 'Bearer'));
}

settings.validateFunc(decoded, function (err, isValid, credentials) {
if (!settings.validateFunc) {
return reply(null, { credentials: decoded });
}

credentials = credentials || null;

if (err) {
return reply(err, { credentials: credentials, log: { tags: ['auth', 'jwt'], data: err } });
}
settings.validateFunc(decoded, function (err, isValid, credentials) {

if (!isValid) {
return reply(Boom.unauthorized('Invalid token', 'Bearer'), { credentials: credentials });
}
credentials = credentials || null;

if (!credentials || typeof credentials !== 'object') {
if (err) {
return reply(err, { credentials: credentials, log: { tags: ['auth', 'jwt'], data: err } });
}

return reply(Boom.badImplementation('Bad credentials object received for jwt auth validation'), { log: { tags: 'credentials' } });
}
if (!isValid) {
return reply(Boom.unauthorized('Invalid token', 'Bearer'), { credentials: credentials });
}

// Authenticated
if (!credentials || typeof credentials !== 'object') {

return reply(null, { credentials: credentials });
});
return reply(Boom.badImplementation('Bad credentials object received for jwt auth validation'), { log: { tags: 'credentials' } });
}

});
// Authenticated

return reply(null, { credentials: credentials });
});

});
});
}
};

Expand Down
116 changes: 116 additions & 0 deletions test/dynamicKeys.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Load modules

var Lab = require('lab');
var Hapi = require('hapi');
var Hoek = require('hoek')
var Boom = require('boom');
var jwt = require('jsonwebtoken');


// Test shortcuts

var expect = Lab.expect;
var before = Lab.before;
var describe = Lab.experiment;
var it = Lab.test;

describe('Dynamic Secret', function () {
var keys = {
'john': 'johnkey',
'jane': 'janekey'
};

var tokenHeader = function (username, options) {
if (!keys[username]){
throw new Error('Invalid user name ' + username + '. Valid options \'john\' or \'jane\'');
}

options = options || {};

return 'Bearer ' + jwt.sign({username: username}, keys[username], options);
};

var tokenHandler = function (request, reply) {
reply(request.auth.credentials.username);
};

var getKey = function(token, callback){
getKey.lastToken = token;
var data = jwt.decode(token);
Hoek.nextTick(function(){
callback(null, keys[data.username]);
})();
}

var errorGetKey = function(token, callback){
callback(new Error('Failed'));
}

var boomErrorGetKey = function(token, callback){
callback(Boom.forbidden('forbidden'));
}

var server = new Hapi.Server({ debug: false });

before(function (done) {
server.pack.register(require('../'), function (err) {
expect(err).to.not.exist;
server.auth.strategy('normalError', 'jwt', false, { key: errorGetKey });
server.auth.strategy('boomError', 'jwt', false, { key: boomErrorGetKey });
server.auth.strategy('default', 'jwt', false, { key: getKey });
server.route([
{ method: 'POST', path: '/token', handler: tokenHandler, config: { auth: 'default' } },
{ method: 'POST', path: '/normalError', handler: tokenHandler, config: { auth: 'normalError' } },
{ method: 'POST', path: '/boomError', handler: tokenHandler, config: { auth: 'boomError' } }
]);

done();
});
});

['jane', 'john'].forEach(function(user){

it('uses key function passing ' + user + '\'s token if ' + user + ' is user', function (done) {

var request = { method: 'POST', url: '/token', headers: { authorization: tokenHeader(user) } };

server.inject(request, function (res) {
expect(res.result).to.exist;
expect(res.result).to.equal(user);

jwt.verify(getKey.lastToken, keys[user], function(err, decoded){
if (err) { return done(err); }
expect(decoded.username).to.equal(user);

done();
});
});
});
});

it('return 500 if an is error thrown when getting key', function(done){

var request = { method: 'POST', url: '/normalError', headers: { authorization: tokenHeader('john') } };

server.inject(request, function (res) {
expect(res).to.exist;
expect(res.result.statusCode).to.equal(500);
expect(res.result.error).to.equal('Internal Server Error');
expect(res.result.message).to.equal('An internal server error occurred');
done();
});
});

it('return 403 if an is error thrown when getting key', function(done){

var request = { method: 'POST', url: '/boomError', headers: { authorization: tokenHeader('john') } };

server.inject(request, function (res) {
expect(res).to.exist;
expect(res.result.statusCode).to.equal(403);
expect(res.result.error).to.equal('Forbidden');
expect(res.result.message).to.equal('forbidden');
done();
});
});
});