Skip to content

Remove joi to shrink module size #348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 6, 2017
Merged
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
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "JSON Web Token implementation (symmetric and asymmetric)",
"main": "index.js",
"scripts": {
"test": "mocha --require test/util/fakeDate && nsp check"
"test": "mocha --require test/util/fakeDate && nsp check && cost-of-modules"
},
"repository": {
"type": "git",
Expand All @@ -19,8 +19,14 @@
"url": "https://github.com/auth0/node-jsonwebtoken/issues"
},
"dependencies": {
"joi": "^6.10.1",
"jws": "^3.1.4",
"lodash.includes": "^4.3.0",
"lodash.isarray": "^4.0.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.0.0",
"xtend": "^4.0.1"
Expand All @@ -29,6 +35,7 @@
"atob": "^1.1.2",
"chai": "^1.10.0",
"conventional-changelog": "~1.1.0",
"cost-of-modules": "^1.0.1",
"mocha": "^2.1.0",
"nsp": "^2.6.2",
"sinon": "^1.15.4"
Expand Down
83 changes: 54 additions & 29 deletions sign.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
var Joi = require('joi');
var timespan = require('./lib/timespan');
var xtend = require('xtend');
var jws = require('jws');
var includes = require('lodash.includes');
var isArray = require('lodash.isarray');
var isBoolean = require('lodash.isboolean');
var isInteger = require('lodash.isinteger');
var isNumber = require('lodash.isnumber');
var isPlainObject = require('lodash.isplainobject');
var isString = require('lodash.isstring');
var once = require('lodash.once');

var sign_options_schema = Joi.object().keys({
expiresIn: [Joi.number().integer(), Joi.string()],
notBefore: [Joi.number().integer(), Joi.string()],
audience: [Joi.string(), Joi.array()],
algorithm: Joi.string().valid('RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none'),
header: Joi.object(),
encoding: Joi.string(),
issuer: Joi.string(),
subject: Joi.string(),
jwtid: Joi.string(),
noTimestamp: Joi.boolean(),
keyid: Joi.string()
});

var registered_claims_schema = Joi.object().keys({
iat: Joi.number(),
exp: Joi.number(),
nbf: Joi.number()
}).unknown();
var sign_options_schema = {
expiresIn: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' },
notBefore: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"notBefore" should be a number of seconds or string representing a timespan' },
audience: { isValid: function(value) { return isString(value) || isArray(value); }, message: '"audience" must be a string or array' },
algorithm: { isValid: includes.bind(null, ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']), message: '"algorithm" must be a valid string enum value' },
header: { isValid: isPlainObject, message: '"header" must be an object' },
encoding: { isValid: isString, message: '"encoding" must be a string' },
issuer: { isValid: isString, message: '"issuer" must be a string' },
subject: { isValid: isString, message: '"subject" must be a string' },
jwtid: { isValid: isString, message: '"jwtid" must be a string' },
noTimestamp: { isValid: isBoolean, message: '"noTimestamp" must be a boolean' },
keyid: { isValid: isString, message: '"keyid" must be a string' },
};

var registered_claims_schema = {
iat: { isValid: isNumber, message: '"iat" should be a number of seconds' },
exp: { isValid: isNumber, message: '"exp" should be a number of seconds' },
nbf: { isValid: isNumber, message: '"nbf" should be a number of seconds' }
};

function validate(schema, unknown, object) {
if (!isPlainObject(object)) {
throw new Error('Expected object');
}
Object.keys(object)
.forEach(function(key) {
var validator = schema[key];
if (!validator) {
if (!unknown) {
throw new Error('"' + key + '" is not allowed');
}
return;
}
if (!validator.isValid(object[key])) {
throw new Error(validator.message);
}
});
}

var options_to_payload = {
'audience': 'aud',
Expand Down Expand Up @@ -73,12 +97,12 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
if (typeof payload === 'undefined') {
return failure(new Error('payload is required'));
} else if (isObjectPayload) {
var payload_validation_result = registered_claims_schema.validate(payload);

if (payload_validation_result.error) {
return failure(payload_validation_result.error);
try {
validate(registered_claims_schema, true, payload);
}
catch (error) {
return failure(error);
}

payload = xtend(payload);
} else {
var invalid_options = options_for_objects.filter(function (opt) {
Expand All @@ -98,10 +122,11 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
return failure(new Error('Bad "options.notBefore" option the payload already has an "nbf" property.'));
}

var validation_result = sign_options_schema.validate(options);

if (validation_result.error) {
return failure(validation_result.error);
try {
validate(sign_options_schema, false, options);
}
catch (error) {
return failure(error);
}

var timestamp = payload.iat || Math.floor(Date.now() / 1000);
Expand Down
2 changes: 1 addition & 1 deletion test/expires_format.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('expires option', function() {
it('should throw if expires is not an string or number', function () {
expect(function () {
jwt.sign({foo: 123}, '123', { expiresIn: { crazy : 213 } });
}).to.throw(/"expiresIn" must be a number/);
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
});

it('should throw an error if expiresIn and exp are provided', function () {
Expand Down
7 changes: 0 additions & 7 deletions test/iat.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,4 @@ describe('iat', function () {
expect(result.exp).to.be.closeTo(iat + expiresIn, 0.2);
});


it('should throw if iat is not a number', function () {
Copy link
Contributor

@ziluvatar ziluvatar Aug 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you remove it? is it because with this PR it is tested in the schema test? (if so, I'm ok with that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's correct.

expect(function () {
jwt.sign({foo: 123, iat: 'hello'}, '123');
}).to.throw(/"iat" must be a number/);
});

});
136 changes: 136 additions & 0 deletions test/schema.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
var jwt = require('../index');
var expect = require('chai').expect;
var fs = require('fs');

describe('schema', function() {

describe('sign options', function() {

var cert_rsa_priv = fs.readFileSync(__dirname + '/rsa-private.pem');
var cert_ecdsa_priv = fs.readFileSync(__dirname + '/ecdsa-private.pem');

function sign(options) {
var isEcdsa = options.algorithm && options.algorithm.indexOf('ES') === 0;
jwt.sign({foo: 123}, isEcdsa ? cert_ecdsa_priv : cert_rsa_priv, options);
}

it('should validate expiresIn', function () {
expect(function () {
sign({ expiresIn: '1 monkey' });
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
expect(function () {
sign({ expiresIn: 1.1 });
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
sign({ expiresIn: '10s' });
sign({ expiresIn: 10 });
});

it('should validate notBefore', function () {
expect(function () {
sign({ notBefore: '1 monkey' });
}).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/);
expect(function () {
sign({ notBefore: 1.1 });
}).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/);
sign({ notBefore: '10s' });
sign({ notBefore: 10 });
});

it('should validate audience', function () {
expect(function () {
sign({ audience: 10 });
}).to.throw(/"audience" must be a string or array/);
sign({ audience: 'urn:foo' });
sign({ audience: ['urn:foo'] });
});

it('should validate algorithm', function () {
expect(function () {
sign({ algorithm: 'foo' });
}).to.throw(/"algorithm" must be a valid string enum value/);
sign({algorithm: 'RS256'});
sign({algorithm: 'RS384'});
sign({algorithm: 'RS512'});
sign({algorithm: 'ES256'});
sign({algorithm: 'ES384'});
sign({algorithm: 'ES512'});
sign({algorithm: 'HS256'});
sign({algorithm: 'HS384'});
sign({algorithm: 'HS512'});
sign({algorithm: 'none'});
});

it('should validate header', function () {
expect(function () {
sign({ header: 'foo' });
}).to.throw(/"header" must be an object/);
sign({header: {}});
});

it('should validate encoding', function () {
expect(function () {
sign({ encoding: 10 });
}).to.throw(/"encoding" must be a string/);
sign({encoding: 'utf8'});
});

it('should validate issuer', function () {
expect(function () {
sign({ issuer: 10 });
}).to.throw(/"issuer" must be a string/);
sign({issuer: 'foo'});
});

it('should validate subject', function () {
expect(function () {
sign({ subject: 10 });
}).to.throw(/"subject" must be a string/);
sign({subject: 'foo'});
});

it('should validate noTimestamp', function () {
expect(function () {
sign({ noTimestamp: 10 });
}).to.throw(/"noTimestamp" must be a boolean/);
sign({noTimestamp: true});
});

it('should validate keyid', function () {
expect(function () {
sign({ keyid: 10 });
}).to.throw(/"keyid" must be a string/);
sign({keyid: 'foo'});
});

});

describe('sign payload registered claims', function() {

function sign(payload) {
jwt.sign(payload, 'foo123');
}

it('should validate iat', function () {
expect(function () {
sign({ iat: '1 monkey' });
}).to.throw(/"iat" should be a number of seconds/);
sign({ iat: 10.1 });
});

it('should validate exp', function () {
expect(function () {
sign({ exp: '1 monkey' });
}).to.throw(/"exp" should be a number of seconds/);
sign({ exp: 10.1 });
});

it('should validate nbf', function () {
expect(function () {
sign({ nbf: '1 monkey' });
}).to.throw(/"nbf" should be a number of seconds/);
sign({ nbf: 10.1 });
});

});

});