Skip to content

Commit adc1673

Browse files
committed
Merge branch 'TATDK-master'
2 parents 7bb5aa6 + 46372e9 commit adc1673

File tree

6 files changed

+181
-12
lines changed

6 files changed

+181
-12
lines changed

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@ encoded private key for RSA and ECDSA.
2828

2929
* `algorithm` (default: `HS256`)
3030
* `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"`
31+
* `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"`
3132
* `audience`
3233
* `subject`
3334
* `issuer`
35+
* `jwtid`
36+
* `subject`
3437
* `noTimestamp`
3538
* `headers`
3639

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

40-
If any `expiresIn`, `audience`, `subject`, `issuer` are not provided, there is no default. The jwt generated won't include those properties in the payload.
43+
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.
4144

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

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

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

127+
// verify jwt id
128+
var cert = fs.readFileSync('public.pem'); // get public key
129+
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid' }, function(err, decoded) {
130+
// if jwt id mismatch, err == invalid jwt id
131+
});
132+
133+
// verify subject
134+
var cert = fs.readFileSync('public.pem'); // get public key
135+
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid', subject: 'subject' }, function(err, decoded) {
136+
// if subject mismatch, err == invalid subject
137+
});
138+
123139
// alg mismatch
124140
var cert = fs.readFileSync('public.pem'); // get public key
125141
jwt.verify(token, cert, { algorithms: ['RS256'] }, function (err, payload) {
@@ -191,6 +207,8 @@ Error object:
191207
* 'invalid signature'
192208
* 'jwt audience invalid. expected: [OPTIONS AUDIENCE]'
193209
* 'jwt issuer invalid. expected: [OPTIONS ISSUER]'
210+
* 'jwt id invalid. expected: [OPTIONS JWT ID]'
211+
* 'jwt subject invalid. expected: [OPTIONS SUBJECT]'
194212

195213
```js
196214
jwt.verify(token, 'shhhhh', function(err, decoded) {

index.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
var jws = require('jws');
22
var ms = require('ms');
3+
var timespan = require('./lib/timespan');
34

45
var JWT = module.exports;
56

67
var JsonWebTokenError = JWT.JsonWebTokenError = require('./lib/JsonWebTokenError');
8+
var NotBeforeError = module.exports.NotBeforeError = require('./lib/NotBeforeError');
79
var TokenExpiredError = JWT.TokenExpiredError = require('./lib/TokenExpiredError');
810

911
JWT.decode = function (jwt, options) {
@@ -57,6 +59,13 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {
5759
payload.iat = payload.iat || timestamp;
5860
}
5961

62+
if (options.notBefore) {
63+
payload.nbf = timespan(options.notBefore);
64+
if (typeof payload.nbf === 'undefined') {
65+
throw new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60');
66+
}
67+
}
68+
6069
if (options.expiresInSeconds || options.expiresInMinutes) {
6170
var deprecated_line;
6271
try {
@@ -74,15 +83,8 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {
7483

7584
payload.exp = timestamp + expiresInSeconds;
7685
} else if (options.expiresIn) {
77-
if (typeof options.expiresIn === 'string') {
78-
var milliseconds = ms(options.expiresIn);
79-
if (typeof milliseconds === 'undefined') {
80-
throw new Error('bad "expiresIn" format: ' + options.expiresIn);
81-
}
82-
payload.exp = timestamp + milliseconds / 1000;
83-
} else if (typeof options.expiresIn === 'number' ) {
84-
payload.exp = timestamp + options.expiresIn;
85-
} else {
86+
payload.exp = timespan(options.expiresIn);
87+
if (typeof payload.exp === 'undefined') {
8688
throw new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60');
8789
}
8890
}
@@ -96,6 +98,9 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {
9698
if (options.subject)
9799
payload.sub = options.subject;
98100

101+
if (options.jwtid)
102+
payload.jti = options.jwtid;
103+
99104
var encoding = 'utf8';
100105
if (options.encoding) {
101106
encoding = options.encoding;
@@ -200,6 +205,16 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) {
200205
return done(err);
201206
}
202207

208+
if (typeof payload.nbf !== 'undefined' && !options.ignoreNotBefore) {
209+
if (typeof payload.nbf !== 'number') {
210+
return done(new JsonWebTokenError('invalid nbf value'));
211+
}
212+
if (payload.nbf >= Math.floor(Date.now() / 1000)) {
213+
console.log(payload.nbf, '>=', Math.floor(Date.now() / 1000));
214+
return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000)));
215+
}
216+
}
217+
203218
if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {
204219
if (typeof payload.exp !== 'number') {
205220
return done(new JsonWebTokenError('invalid exp value'));

lib/NotBeforeError.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var JsonWebTokenError = require('./JsonWebTokenError');
2+
3+
var NotBeforeError = function (message, date) {
4+
JsonWebTokenError.call(this, message);
5+
this.name = 'NotBeforeError';
6+
this.date = date;
7+
};
8+
9+
NotBeforeError.prototype = Object.create(JsonWebTokenError.prototype);
10+
11+
NotBeforeError.prototype.constructor = NotBeforeError;
12+
13+
module.exports = NotBeforeError;

lib/timespan.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
var ms = require('ms');
2+
3+
module.exports = function (time) {
4+
var timestamp = Math.floor(Date.now() / 1000);
5+
6+
if (typeof time === 'string') {
7+
var milliseconds = ms(time);
8+
if (typeof milliseconds === 'undefined') {
9+
return;
10+
}
11+
return Math.floor(timestamp + milliseconds / 1000);
12+
} else if (typeof time === 'number' ) {
13+
return timestamp + time;
14+
} else {
15+
return;
16+
}
17+
18+
};

test/expires_format.tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('expires option', function() {
2727
it('should throw if expires has a bad string format', function () {
2828
expect(function () {
2929
jwt.sign({foo: 123}, '123', { expiresIn: '1 monkey' });
30-
}).to.throw(/bad "expiresIn" format: 1 monkey/);
30+
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
3131
});
3232

3333
it('should throw if expires is not an string or number', function () {

test/jwt.rs.tests.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,45 @@ describe('RS256', function() {
8888
});
8989
});
9090

91+
describe('when signing a token with not before', function() {
92+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBefore: -10 * 3600 });
93+
94+
it('should be valid expiration', function(done) {
95+
jwt.verify(token, pub, function(err, decoded) {
96+
console.log(token);
97+
console.dir(arguments);
98+
assert.isNotNull(decoded);
99+
assert.isNull(err);
100+
done();
101+
});
102+
});
103+
104+
it('should be invalid', function(done) {
105+
// not active token
106+
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBefore: '10m' });
107+
108+
jwt.verify(token, pub, function(err, decoded) {
109+
assert.isUndefined(decoded);
110+
assert.isNotNull(err);
111+
assert.equal(err.name, 'NotBeforeError');
112+
assert.instanceOf(err.date, Date);
113+
assert.instanceOf(err, jwt.NotBeforeError);
114+
done();
115+
});
116+
});
117+
118+
it('should NOT be invalid', function(done) {
119+
// not active token
120+
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBeforeMinutes: 10 });
121+
122+
jwt.verify(token, pub, { ignoreNotBefore: true }, function(err, decoded) {
123+
assert.ok(decoded.foo);
124+
assert.equal('bar', decoded.foo);
125+
done();
126+
});
127+
});
128+
});
129+
91130
describe('when signing a token with audience', function() {
92131
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', audience: 'urn:foo' });
93132

@@ -236,6 +275,72 @@ describe('RS256', function() {
236275
});
237276
});
238277

278+
describe('when signing a token with subject', function() {
279+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', subject: 'subject' });
280+
281+
it('should check subject', function() {
282+
jwt.verify(token, pub, { subject: 'subject' }, function(err, decoded) {
283+
assert.isNotNull(decoded);
284+
assert.isNull(err);
285+
});
286+
});
287+
288+
it('should throw when invalid subject', function() {
289+
jwt.verify(token, pub, { issuer: 'wrongSubject' }, function(err, decoded) {
290+
assert.isUndefined(decoded);
291+
assert.isNotNull(err);
292+
assert.equal(err.name, 'JsonWebTokenError');
293+
assert.instanceOf(err, jwt.JsonWebTokenError);
294+
});
295+
});
296+
});
297+
298+
describe('when signing a token without subject', function() {
299+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });
300+
301+
it('should check subject', function() {
302+
jwt.verify(token, pub, { subject: 'subject' }, function(err, decoded) {
303+
assert.isUndefined(decoded);
304+
assert.isNotNull(err);
305+
assert.equal(err.name, 'JsonWebTokenError');
306+
assert.instanceOf(err, jwt.JsonWebTokenError);
307+
});
308+
});
309+
});
310+
311+
describe('when signing a token with jwt id', function() {
312+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', jwtid: 'jwtid' });
313+
314+
it('should check jwt id', function() {
315+
jwt.verify(token, pub, { jwtid: 'jwtid' }, function(err, decoded) {
316+
assert.isNotNull(decoded);
317+
assert.isNull(err);
318+
});
319+
});
320+
321+
it('should throw when invalid jwt id', function() {
322+
jwt.verify(token, pub, { jwtid: 'wrongJwtid' }, function(err, decoded) {
323+
assert.isUndefined(decoded);
324+
assert.isNotNull(err);
325+
assert.equal(err.name, 'JsonWebTokenError');
326+
assert.instanceOf(err, jwt.JsonWebTokenError);
327+
});
328+
});
329+
});
330+
331+
describe('when signing a token without jwt id', function() {
332+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });
333+
334+
it('should check jwt id', function() {
335+
jwt.verify(token, pub, { jwtid: 'jwtid' }, function(err, decoded) {
336+
assert.isUndefined(decoded);
337+
assert.isNotNull(err);
338+
assert.equal(err.name, 'JsonWebTokenError');
339+
assert.instanceOf(err, jwt.JsonWebTokenError);
340+
});
341+
});
342+
});
343+
239344
describe('when verifying a malformed token', function() {
240345
it('should throw', function(done) {
241346
jwt.verify('fruit.fruit.fruit', pub, function(err, decoded) {

0 commit comments

Comments
 (0)