From 46372e928f6d2e7398f9b88022ca617d2a3b0699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20F=2E=20Romaniello?= Date: Mon, 28 Dec 2015 14:09:08 -0300 Subject: [PATCH] improvements to nbf and jti claims --- README.md | 5 ++--- index.js | 31 +++++++++++++------------------ lib/timespan.js | 18 ++++++++++++++++++ test/expires_format.tests.js | 2 +- test/jwt.rs.tests.js | 6 ++++-- 5 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 lib/timespan.js diff --git a/README.md b/README.md index 97969be..3e0d14e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ 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"` -* `notBeforeMinutes` or `notBeforeSeconds` +* `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` @@ -80,8 +80,7 @@ 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. -* `ignoreNotBefore`... -* `jwtid`: if you want to check JWT ID (`jti`), provide a value here +* `ignoreNotBefore`... * `subject`: if you want to check subject (`sub`), provide a value here ```js diff --git a/index.js b/index.js index 239a22f..a695057 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ var jws = require('jws'); var ms = require('ms'); +var timespan = require('./lib/timespan'); var JWT = module.exports; @@ -57,13 +58,12 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) { if (!options.noTimestamp) { payload.iat = payload.iat || timestamp; } - - var notBeforeSeconds = options.notBeforeMinutes ? - options.notBeforeMinutes * 60 : - options.notBeforeSeconds; - - if (notBeforeSeconds) { - payload.nbf = timestamp + notBeforeSeconds; + + 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) { @@ -83,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'); } } @@ -211,13 +204,15 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) { } catch(err) { 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)) + 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) { diff --git a/lib/timespan.js b/lib/timespan.js new file mode 100644 index 0000000..e826317 --- /dev/null +++ b/lib/timespan.js @@ -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; + } + +}; \ No newline at end of file diff --git a/test/expires_format.tests.js b/test/expires_format.tests.js index f9e9481..341d2ba 100644 --- a/test/expires_format.tests.js +++ b/test/expires_format.tests.js @@ -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 () { diff --git a/test/jwt.rs.tests.js b/test/jwt.rs.tests.js index 18af156..0c27808 100644 --- a/test/jwt.rs.tests.js +++ b/test/jwt.rs.tests.js @@ -89,10 +89,12 @@ describe('RS256', function() { }); describe('when signing a token with not before', function() { - var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBeforeMinutes: -10 }); + 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(); @@ -101,7 +103,7 @@ describe('RS256', function() { it('should be invalid', function(done) { // not active token - token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBeforeMinutes: 10 }); + token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBefore: '10m' }); jwt.verify(token, pub, function(err, decoded) { assert.isUndefined(decoded);