Skip to content

Commit

Permalink
Merge branch 'TATDK-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
jfromaniello committed Dec 28, 2015
2 parents 7bb5aa6 + 46372e9 commit adc1673
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 12 deletions.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ encoded private key for RSA and ECDSA.

* `algorithm` (default: `HS256`)
* `expiresIn`: expressed in seconds or an string describing a time span [rauchg/ms](https://github.com/rauchg/ms.js). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `notBefore`: expressed in seconds or an string describing a time span [rauchg/ms](https://github.com/rauchg/ms.js). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `audience`
* `subject`
* `issuer`
* `jwtid`
* `subject`
* `noTimestamp`
* `headers`

If `payload` is not a buffer or a string, it will be coerced into a string
using `JSON.stringify`.

If any `expiresIn`, `audience`, `subject`, `issuer` are not provided, there is no default. The jwt generated won't include those properties in the payload.
If any `expiresIn`, `notBeforeMinutes`, `audience`, `subject`, `issuer` are not provided, there is no default. The jwt generated won't include those properties in the payload.

Additional headers can be provided via the `headers` object.

Expand Down Expand Up @@ -77,7 +80,8 @@ encoded public key for RSA and ECDSA.
* `audience`: if you want to check audience (`aud`), provide a value here
* `issuer`: if you want to check issuer (`iss`), provide a value here
* `ignoreExpiration`: if `true` do not validate the expiration of the token.
* `maxAge`: optional sets an expiration based on the `iat` field. Eg `2h`
* `ignoreNotBefore`...
* `subject`: if you want to check subject (`sub`), provide a value here

```js
// verify a token symmetric - synchronous
Expand Down Expand Up @@ -120,6 +124,18 @@ jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer' }, function(
// if issuer mismatch, err == invalid issuer
});

// verify jwt id
var cert = fs.readFileSync('public.pem'); // get public key
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid' }, function(err, decoded) {
// if jwt id mismatch, err == invalid jwt id
});

// verify subject
var cert = fs.readFileSync('public.pem'); // get public key
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid', subject: 'subject' }, function(err, decoded) {
// if subject mismatch, err == invalid subject
});

// alg mismatch
var cert = fs.readFileSync('public.pem'); // get public key
jwt.verify(token, cert, { algorithms: ['RS256'] }, function (err, payload) {
Expand Down Expand Up @@ -191,6 +207,8 @@ Error object:
* 'invalid signature'
* 'jwt audience invalid. expected: [OPTIONS AUDIENCE]'
* 'jwt issuer invalid. expected: [OPTIONS ISSUER]'
* 'jwt id invalid. expected: [OPTIONS JWT ID]'
* 'jwt subject invalid. expected: [OPTIONS SUBJECT]'

```js
jwt.verify(token, 'shhhhh', function(err, decoded) {
Expand Down
33 changes: 24 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
var jws = require('jws');
var ms = require('ms');
var timespan = require('./lib/timespan');

var JWT = module.exports;

var JsonWebTokenError = JWT.JsonWebTokenError = require('./lib/JsonWebTokenError');
var NotBeforeError = module.exports.NotBeforeError = require('./lib/NotBeforeError');
var TokenExpiredError = JWT.TokenExpiredError = require('./lib/TokenExpiredError');

JWT.decode = function (jwt, options) {
Expand Down Expand Up @@ -57,6 +59,13 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {
payload.iat = payload.iat || timestamp;
}

if (options.notBefore) {
payload.nbf = timespan(options.notBefore);
if (typeof payload.nbf === 'undefined') {
throw new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60');
}
}

if (options.expiresInSeconds || options.expiresInMinutes) {
var deprecated_line;
try {
Expand All @@ -74,15 +83,8 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {

payload.exp = timestamp + expiresInSeconds;
} else if (options.expiresIn) {
if (typeof options.expiresIn === 'string') {
var milliseconds = ms(options.expiresIn);
if (typeof milliseconds === 'undefined') {
throw new Error('bad "expiresIn" format: ' + options.expiresIn);
}
payload.exp = timestamp + milliseconds / 1000;
} else if (typeof options.expiresIn === 'number' ) {
payload.exp = timestamp + options.expiresIn;
} else {
payload.exp = timespan(options.expiresIn);
if (typeof payload.exp === 'undefined') {
throw new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60');
}
}
Expand All @@ -96,6 +98,9 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {
if (options.subject)
payload.sub = options.subject;

if (options.jwtid)
payload.jti = options.jwtid;

var encoding = 'utf8';
if (options.encoding) {
encoding = options.encoding;
Expand Down Expand Up @@ -200,6 +205,16 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) {
return done(err);
}

if (typeof payload.nbf !== 'undefined' && !options.ignoreNotBefore) {
if (typeof payload.nbf !== 'number') {
return done(new JsonWebTokenError('invalid nbf value'));
}
if (payload.nbf >= Math.floor(Date.now() / 1000)) {
console.log(payload.nbf, '>=', Math.floor(Date.now() / 1000));
return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000)));
}
}

if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {
if (typeof payload.exp !== 'number') {
return done(new JsonWebTokenError('invalid exp value'));
Expand Down
13 changes: 13 additions & 0 deletions lib/NotBeforeError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
var JsonWebTokenError = require('./JsonWebTokenError');

var NotBeforeError = function (message, date) {
JsonWebTokenError.call(this, message);
this.name = 'NotBeforeError';
this.date = date;
};

NotBeforeError.prototype = Object.create(JsonWebTokenError.prototype);

NotBeforeError.prototype.constructor = NotBeforeError;

module.exports = NotBeforeError;
18 changes: 18 additions & 0 deletions lib/timespan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var ms = require('ms');

module.exports = function (time) {
var timestamp = Math.floor(Date.now() / 1000);

if (typeof time === 'string') {
var milliseconds = ms(time);
if (typeof milliseconds === 'undefined') {
return;
}
return Math.floor(timestamp + milliseconds / 1000);
} else if (typeof time === 'number' ) {
return timestamp + time;
} else {
return;
}

};
2 changes: 1 addition & 1 deletion test/expires_format.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('expires option', function() {
it('should throw if expires has a bad string format', function () {
expect(function () {
jwt.sign({foo: 123}, '123', { expiresIn: '1 monkey' });
}).to.throw(/bad "expiresIn" format: 1 monkey/);
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
});

it('should throw if expires is not an string or number', function () {
Expand Down
105 changes: 105 additions & 0 deletions test/jwt.rs.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,45 @@ describe('RS256', function() {
});
});

describe('when signing a token with not before', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBefore: -10 * 3600 });

it('should be valid expiration', function(done) {
jwt.verify(token, pub, function(err, decoded) {
console.log(token);
console.dir(arguments);
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should be invalid', function(done) {
// not active token
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBefore: '10m' });

jwt.verify(token, pub, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'NotBeforeError');
assert.instanceOf(err.date, Date);
assert.instanceOf(err, jwt.NotBeforeError);
done();
});
});

it('should NOT be invalid', function(done) {
// not active token
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBeforeMinutes: 10 });

jwt.verify(token, pub, { ignoreNotBefore: true }, function(err, decoded) {
assert.ok(decoded.foo);
assert.equal('bar', decoded.foo);
done();
});
});
});

describe('when signing a token with audience', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', audience: 'urn:foo' });

Expand Down Expand Up @@ -236,6 +275,72 @@ describe('RS256', function() {
});
});

describe('when signing a token with subject', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', subject: 'subject' });

it('should check subject', function() {
jwt.verify(token, pub, { subject: 'subject' }, function(err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
});
});

it('should throw when invalid subject', function() {
jwt.verify(token, pub, { issuer: 'wrongSubject' }, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
});
});
});

describe('when signing a token without subject', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });

it('should check subject', function() {
jwt.verify(token, pub, { subject: 'subject' }, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
});
});
});

describe('when signing a token with jwt id', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', jwtid: 'jwtid' });

it('should check jwt id', function() {
jwt.verify(token, pub, { jwtid: 'jwtid' }, function(err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
});
});

it('should throw when invalid jwt id', function() {
jwt.verify(token, pub, { jwtid: 'wrongJwtid' }, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
});
});
});

describe('when signing a token without jwt id', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });

it('should check jwt id', function() {
jwt.verify(token, pub, { jwtid: 'jwtid' }, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
});
});
});

describe('when verifying a malformed token', function() {
it('should throw', function(done) {
jwt.verify('fruit.fruit.fruit', pub, function(err, decoded) {
Expand Down

0 comments on commit adc1673

Please sign in to comment.