Initial check in

This commit is contained in:
Mike Nicholson
2014-11-09 00:43:38 -05:00
parent 7e29daa1fd
commit aec0fba51a
10 changed files with 528 additions and 0 deletions
View File
+113
View File
@@ -0,0 +1,113 @@
var passport = require('passport-strategy')
,util = require('util');
/**
* Strategy constructor
*
* @param secretOrKey - string or buffer containing the secret or PEM-encoded public key
* @param options
* issuer: If defined issuer will be verified against this value
* audience: If defined audience will be verified against this value
* tokenBodyField: Field in request body containing token. Default is auth_token.
* tokenHeader: HTTP Header containing jwt. Default is 'x-auth_token'.
* @param verify - Verify callback with args (jwt_payload, done_callback)
*/
function JwtStrategy(secretOrKey, options, verify) {
if (typeof options == 'function') {
verify = options;
options = {};
}
if (!verify) {
throw new TypeError('JwtStrategy requires a verify callback');
}
if (!secretOrKey) {
throw new TypeError('JwtStrategy requires a secret or key');
}
passport.Strategy.call(this);
this.name = 'jwt';
this._verify = verify;
this._passReqToCallback = options.passReqToCallback;
this._tokenHeader = options.tokenHeader;
this._tokenBodyField = options.tokenBodyField;
this._verifOpts = {}
if (options.issuer) {
this._verifOpts.issuer = options.issues;
}
if (options.audience) {
this._verifOpts.audience = options.audience;
}
};
util.inherits(JwtStrategy, passport.Strategy);
/**
* Allow for injection of JWT Verifier.
*
* This improves testability by allowing tests to cleanly isolate failures in the JWT Verification
* process from failures in the passport related mechanics of authentication.
*
* Note that this should only be replaced in tests.
*/
JwtStrategy.JwtVerifier = require('./verify_jwt');
/**
* Authenticate request based on JWT obtained from header or post body
*/
JwtStrategy.prototype.authenticate = function(req, options) {
var self = this;
// Extract the jwt from the request
// Try the header first
var token = req.headers['x-auth_token'];
// If not in the header try the body
if (!token && req.body) {
token = req.body.auth_token;
}
if (!token) {
return self.fail(new Error("No auth token"));
}
// Verify the JWT
JwtStrategy.JwtVerifier(token, self._sercretOrKey, this._verifOpts, function(jwt_err, payload) {
if (jwt_err) {
return self.fail(jwt_err);
} else {
// Pass the parsed token to the user
var verified = function(err, user, info) {
if(err) {
return self.error(err);
} else if (!user) {
return self.fail(info);
} else {
return self.success(user, info);
}
};
try {
self._verify(payload, verified);
} catch(ex) {
self.error(ex);
}
}
});
};
/**
* Export the Jwt Strategy
*/
module.exports = JwtStrategy;
+5
View File
@@ -0,0 +1,5 @@
var jwt = require('jsonwebtoken');
module.exports = function(token, secretOrKey, options, callback) {
return jwt.verify(token, secretOrKey, options, callback);
};
+37
View File
@@ -0,0 +1,37 @@
{
"name": "passport-jwt",
"version": "0.0.1",
"description": "Passport authentication strategy using JSON Web Tokens",
"main": "./lib",
"scripts": {
"test": "./node_modules/.bin/mocha --reporter spec --require test/bootstrap test/*test.js"
},
"repository": {
"type": "git",
"url": "https://github.com/themikenicholson/passport-jwt.git"
},
"keywords": [
"Passport",
"Strategy",
"JSON",
"Web",
"Token",
"JWT"
],
"author": "Mike Nicholson",
"license": "MIT",
"bugs": {
"url": "https://github.com/themikenicholson/passport-jwt/issues"
},
"homepage": "https://github.com/themikenicholson/passport-jwt",
"devDependencies": {
"chai": "^1.9.2",
"chai-passport-strategy": "^0.2.0",
"mocha": "^2.0.1",
"sinon": "^1.11.1"
},
"dependencies": {
"jsonwebtoken": "^1.1.2",
"passport-strategy": "^1.0.0"
}
}
+3
View File
@@ -0,0 +1,3 @@
var chai = require('chai');
chai.use(require('chai-passport-strategy'));
global.expect = chai.expect;
+23
View File
@@ -0,0 +1,23 @@
var Strategy = require('../lib/strategy');
describe('Strategy', function() {
var strategy = new Strategy('secret', function() {});
it('should be named jwt', function() {
expect(strategy.name).to.equal('jwt');
});
it('should throw if constructed without a verify callback', function() {
expect(function() {
var s = new Strategy('secret');
}).to.throw(TypeError, "JwtStrategy requires a verify callback");
});
it('should throw if constructed without a secretOrKey arg', function() {
expect(function() {
var s = new Strategy(null, function() {});
}).to.throw(TypeError, 'JwtStrategy requires a secret or key');
});
});
+117
View File
@@ -0,0 +1,117 @@
var Strategy = require('../lib/strategy')
, chai = require('chai')
, sinon = require('sinon')
, test_data= require('./testdata');
describe('Strategy', function() {
var mockVerifier = null;
before(function() {
// Replace the JWT Verfier with a stub to capture the value
// extracted from the request
mockVerifier = sinon.stub();
mockVerifier.callsArgWith(3, null, test_data.valid_jwt.payload);
Strategy.JwtVerifier = mockVerifier;
});
describe('handling request with JWT in header', function() {
var strategy;
before(function(done) {
strategy = new Strategy('secret', function(jwt_payload, next) {
// Return values aren't important in this case
return next(null, {}, {});
});
mockVerifier.reset();
chai.passport.use(strategy)
.success(function(u, i) {
done();
})
.req(function(req) {
req.headers['x-auth_token'] = test_data.valid_jwt.token;
})
.authenticate();
});
it("verifies the right jwt", function() {
sinon.assert.calledOnce(mockVerifier);
expect(mockVerifier.args[0][0]).to.equal(test_data.valid_jwt.token);
});
});
describe('handling request with JWT in body', function() {
var strategy;
before(function(done) {
strategy = new Strategy('secret', function(jwt_payload, next) {
// Return values aren't important in this case
return next(null, {}, {});
});
mockVerifier.reset();
chai.passport.use(strategy)
.success(function(u, i) {
done();
})
.req(function(req) {
req.body = {}
req.body.auth_token = test_data.valid_jwt.token;
})
.authenticate();
});
it("verifies the right jwt", function() {
sinon.assert.calledOnce(mockVerifier);
expect(mockVerifier.args[0][0]).to.equal(test_data.valid_jwt.token);
});
});
describe('handling request with NO JWT', function() {
var info;
before(function(done) {
strategy = new Strategy('secret', function(jwt_payload, next) {
// Return values aren't important in this case
return next(null, {}, {});
});
mockVerifier.reset();
chai.passport.use(strategy)
.fail(function(i) {
info = i
done();
})
.req(function(req) {
req.body = {}
})
.authenticate();
});
it("should fail authentication", function() {
expect(info).to.be.an.object;
expect(info.message).to.equal("No auth token");
});
it('Should not try to verify anything', function() {
sinon.assert.notCalled(mockVerifier);
});
});
});
+78
View File
@@ -0,0 +1,78 @@
var Strategy = require('../lib/strategy')
, chai = require('chai')
, test_data = require('./testdata')
, sinon = require('sinon');
describe('Strategy', function() {
describe('handling valid jwt', function() {
var strategy, payload;
before(function(done) {
strategy = new Strategy('secret', function(jwt_payload, next) {
payload = jwt_payload;
next(null, {}, {});
});
// Mock successful verification
Strategy.JwtVerifier = sinon.stub();
Strategy.JwtVerifier.callsArgWith(3, null, test_data.valid_jwt.payload);
chai.passport.use(strategy)
.success(function(u, i) {
done();
})
.req(function(req) {
req.headers['x-auth_token'] = test_data.valid_jwt.token;
})
.authenticate();
});
it('should call verify with the correct payload', function() {
expect(payload).to.deep.equal(test_data.valid_jwt.payload);
});
});
describe('handling failing jwt', function() {
var strategy, info;
var verify_spy = sinon.spy();
before(function(done) {
strategy = new Strategy('secret', verify_spy);
// Mock errored verification
Strategy.JwtVerifier = sinon.stub();
Strategy.JwtVerifier.callsArgWith(3, new Error("jwt expired"), false);
chai.passport.use(strategy)
.fail(function(i) {
info = i;
done();
})
.req(function(req) {
req.headers['x-auth_token'] = test_data.valid_jwt.token;
})
.authenticate();
});
it('should not call verify', function() {
sinon.assert.notCalled(verify_spy);
});
it('should fail with error message.', function() {
expect(info).to.be.an.object;
expect(info.message).to.equal('jwt expired');
});
});
});
+138
View File
@@ -0,0 +1,138 @@
var chai = require('chai')
, Strategy = require('../lib/strategy')
, test_data = require('./testdata')
, sinon = require('sinon');
describe('Strategy', function() {
before(function() {
Strategy.JwtVerifier = sinon.stub();
Strategy.JwtVerifier.callsArgWith(3, null, test_data.valid_jwt.payload);
});
describe('Handling a request with a valid JWT and succesful verification', function() {
var strategy, user, info;
before(function(done) {
strategy = new Strategy('secret', function(jwt_paylod, next) {
return next(null, {user_id: 1234567890}, {foo:'bar'});
});
chai.passport.use(strategy)
.success(function(u, i) {
user = u;
info = i;
done();
})
.req(function(req) {
req.headers['x-auth_token'] = test_data.valid_jwt.token;
})
.authenticate();
});
it('should provide a user', function() {
expect(user).to.be.an.object;
expect(user.user_id).to.equal(1234567890);
});
it('should forward info', function() {
expect(info).to.be.an.object;
expect(info.foo).to.equal('bar');
});
});
describe('handling a request with valid jwt and failed verification', function() {
var strategy, info;
before(function(done) {
strategy = new Strategy('secret', function(jwt_payload, next) {
return next(null, false, {message: 'invalid user'});
});
chai.passport.use(strategy)
.fail(function(i) {
info = i;
done();
})
.req(function(req) {
req.headers['x-auth_token'] = test_data.valid_jwt.token;
})
.authenticate();
});
it('should fail with info', function() {
expect(info).to.be.an.object;
expect(info.message).to.equal('invalid user');
});
});
describe('handling a request with a valid jwt and an error during verification', function() {
var strategy, err;
before(function(done) {
strategy = new Strategy('secrety', function(jwt_payload, next) {
return next(new Error("ERROR"), false, {message: 'invalid user'});
});
chai.passport.use(strategy)
.error(function(e) {
err = e;
done();
})
.req(function(req) {
req.headers['x-auth_token'] = test_data.valid_jwt.token;
})
.authenticate();
});
it('should error', function() {
expect(err).to.be.an.instanceof(Error);
expect(err.message).to.equal('ERROR');
});
});
describe('hanlding a request with a valid jwt and an exception during verification', function() {
var strategy, err;
before(function(done) {
strategy = new Strategy('secret', function(jwt_payload, next) {
throw new Error("EXCEPTION");
});
chai.passport.use(strategy)
.error(function(e) {
err = e;
done();
})
.req(function(req) {
req.headers['x-auth_token'] = test_data.valid_jwt.token;
})
.authenticate();
});
it('should error', function() {
expect(err).to.be.an.instanceof(Error);
expect(err.message).to.equal('EXCEPTION');
});
});
});
+14
View File
@@ -0,0 +1,14 @@
module.exports = {
// Note, this JWT will expire sometime in 2286. If unit tests are failing around this time try
// generating a new, valid token :)
valid_jwt : {
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEyMzQ1Njc4OTAsIm5hbWUiOiJKb2huIERvZSIsImlzcyI6ImV4YW1wbGVzb2Z0LmNvbSIsImV4cCI6Ijk5OTk5OTk5OTkifQ.CbpI0TNI-FYXe6p3PgM__jwlz6aCT1qpUBsTVCfWuBM",
payload : {
"sub": "1234567890",
"name": "John Doe",
"iss": "examplesoft.com",
"exp": "9999999999"
},
secret: 'secret'
}
};