From bcf80974cd9e8d99bc21239aedd4e0975935301b Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Wed, 22 Aug 2018 15:15:13 -0300 Subject: [PATCH 01/10] Adding id_token validation --- package.json | 2 + src/auth/OAUthWithIDTokenValidation.js | 90 +++++++++ src/auth/OAuthAuthenticator.js | 16 +- yarn.lock | 268 +++++++++++++++++++++++++ 4 files changed, 370 insertions(+), 6 deletions(-) create mode 100644 src/auth/OAUthWithIDTokenValidation.js diff --git a/package.json b/package.json index 60c49d951..bfbcccee2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "homepage": "https://github.com/auth0/node-auth0", "dependencies": { "bluebird": "^2.10.2", + "jsonwebtoken": "^8.3.0", + "jwks-rsa": "^1.3.0", "lru-memoizer": "^1.11.1", "object.assign": "^4.0.4", "request": "^2.83.0", diff --git a/src/auth/OAUthWithIDTokenValidation.js b/src/auth/OAUthWithIDTokenValidation.js new file mode 100644 index 000000000..fb5a92596 --- /dev/null +++ b/src/auth/OAUthWithIDTokenValidation.js @@ -0,0 +1,90 @@ +var jwt = require('jsonwebtoken'); +var jwksClient = require('jwks-rsa'); +var Promise = require('bluebird'); + +var ArgumentError = require('rest-facade').ArgumentError; + +/** + * @class + * Abstracts the `oauth.create` method with additional id_token validation\ + * @constructor + * @memberOf module:auth + * + * @param {Object} options Authenticator options. + * @param {String} options.baseUrl The auth0 account URL. + * @param {String} options.domain AuthenticationClient server domain + * @param {String} [options.clientId] Default client ID. + * @param {String} [options.clientSecret] Default client Secret. + */ +var OAUthWithIDTokenValidation = function(oauth, options) { + if (!oauth) { + throw new ArgumentError('Missing authenticator options'); + } + + if (!options) { + throw new ArgumentError('Missing authenticator options'); + } + + if (typeof options !== 'object') { + throw new ArgumentError('The authenticator options must be an object'); + } + + this.oauth = oauth; + this.clientId = options.clientId; + this.clientSecret = options.clientSecret; + this.domain = options.domain; +}; + +/** + * Creates an oauth request + * + * @method create + * @memberOf module:auth.OAuthWithIDTokenValidation.prototype + * + * @param {Object} options Options are passed through + * @param {Function} [callbabck] Callback function + * + * @return {Promise|undefined} + */ +OAUthWithIDTokenValidation.prototype.create = function(params, data, callback) { + const createAndValidate = this.oauth.create(params, data).then(r => { + var _this = this; + if (r.id_token) { + var client = jwksClient({ + jwksUri: 'https://' + this.domain + '/.well-known/jwks.json' + }); + function getKey(header, callback) { + if (header.alg === 'HS256') { + return callback(null, Buffer.from(_this.clientSecret, 'base64')); + } + client.getSigningKey(header.kid, function(err, key) { + var signingKey = key.publicKey || key.rsaPublicKey; + callback(err, signingKey); + }); + } + + return new Promise((res, rej) => { + jwt.verify( + r.id_token, + getKey, + { + algorithms: ['HS256', 'RS256'] + }, + function(err, payload) { + if (err) { + return rej(err); + } + return res(r); + } + ); + }); + } + return r; + }); + if (!callback) { + return createAndValidate; + } + createAndValidate.then(r => callback(null, r)).catch(e => callback(e)); +}; + +module.exports = OAUthWithIDTokenValidation; diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js index 0af66bdf1..0b0005d56 100644 --- a/src/auth/OAuthAuthenticator.js +++ b/src/auth/OAuthAuthenticator.js @@ -3,6 +3,8 @@ var extend = require('util')._extend; var ArgumentError = require('rest-facade').ArgumentError; var RestClient = require('rest-facade').Client; +var OAUthWithIDTokenValidation = require('./OAUthWithIDTokenValidation'); + /** * @class * Abstracts the sign-in, sign-up and change-password processes for Database & @@ -12,6 +14,7 @@ var RestClient = require('rest-facade').Client; * * @param {Object} options Authenticator options. * @param {String} options.baseUrl The auth0 account URL. + * @param {String} options.domain AuthenticationClient server domain * @param {String} [options.clientId] Default client ID. * @param {String} [options.clientSecret] Default client Secret. */ @@ -34,6 +37,7 @@ var OAuthAuthenticator = function(options) { }; this.oauth = new RestClient(options.baseUrl + '/oauth/:type', clientOptions); + this.oauthWithIDTokenValidation = new OAUthWithIDTokenValidation(this.oauth, options); this.clientId = options.clientId; this.clientSecret = options.clientSecret; }; @@ -96,10 +100,10 @@ OAuthAuthenticator.prototype.signIn = function(userData, cb) { } if (cb && cb instanceof Function) { - return this.oauth.create(params, data, cb); + return this.oauthWithIDTokenValidation.create(params, data, cb); } - return this.oauth.create(params, data); + return this.oauthWithIDTokenValidation.create(params, data); }; /** @@ -169,10 +173,10 @@ OAuthAuthenticator.prototype.passwordGrant = function(userData, cb) { } if (cb && cb instanceof Function) { - return this.oauth.create(params, data, cb); + return this.oauthWithIDTokenValidation.create(params, data, cb); } - return this.oauth.create(params, data); + return this.oauthWithIDTokenValidation.create(params, data); }; /** @@ -306,10 +310,10 @@ OAuthAuthenticator.prototype.authorizationCodeGrant = function(options, cb) { } if (cb && cb instanceof Function) { - return this.oauth.create(params, data, cb); + return this.oauthWithIDTokenValidation.create(params, data, cb); } - return this.oauth.create(params, data); + return this.oauthWithIDTokenValidation.create(params, data); }; module.exports = OAuthAuthenticator; diff --git a/yarn.lock b/yarn.lock index 638ad7960..86991ea5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,71 @@ # yarn lockfile v1 +"@types/body-parser@*": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.32" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" + dependencies: + "@types/node" "*" + +"@types/events@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" + +"@types/express-jwt@0.0.34": + version "0.0.34" + resolved "https://registry.yarnpkg.com/@types/express-jwt/-/express-jwt-0.0.34.tgz#fdbee4c6af5c0a246ef2a933f5519973c7717f02" + dependencies: + "@types/express" "*" + "@types/express-unless" "*" + +"@types/express-serve-static-core@*": + version "4.16.0" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz#fdfe777594ddc1fe8eb8eccce52e261b496e43e7" + dependencies: + "@types/events" "*" + "@types/node" "*" + "@types/range-parser" "*" + +"@types/express-unless@*": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.0.32.tgz#783f3cc1fa5e67cc2ed30000f3e1f22501f75d50" + dependencies: + "@types/express" "*" + +"@types/express@*": + version "4.16.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.0.tgz#6d8bc42ccaa6f35cf29a2b7c3333cb47b5a32a19" + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" + +"@types/node@*": + version "10.7.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.7.1.tgz#b704d7c259aa40ee052eec678758a68d07132a2e" + +"@types/range-parser@*": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.2.tgz#fa8e1ad1d474688a757140c91de6dace6f4abc8d" + +"@types/serve-static@*": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" + dependencies: + "@types/express-serve-static-core" "*" + "@types/mime" "*" + abbrev@1, abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -39,6 +104,15 @@ ajv@^5.1.0: json-schema-traverse "^0.3.0" json-stable-stringify "^1.0.1" +ajv@^5.3.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -169,6 +243,10 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + babylon@7.0.0-beta.19: version "7.0.0-beta.19" resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503" @@ -254,6 +332,10 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer@^4.9.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" @@ -409,6 +491,12 @@ color-name@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" +combined-stream@1.0.6, combined-stream@~1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -620,6 +708,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" @@ -730,6 +824,10 @@ extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -744,6 +842,10 @@ fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -812,6 +914,14 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" +form-data@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + formatio@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" @@ -999,6 +1109,13 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +har-validator@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" + dependencies: + ajv "^5.3.0" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -1358,6 +1475,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.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.1.1" + jsprim@^1.2.2: version "1.4.0" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" @@ -1367,6 +1498,32 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.3.6" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jwks-rsa@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-1.3.0.tgz#f37d2a6815af17a3b2e5898ab2a41ad8c168b295" + dependencies: + "@types/express-jwt" "0.0.34" + debug "^2.2.0" + limiter "^1.1.0" + lru-memoizer "^1.6.0" + ms "^2.0.0" + request "^2.73.0" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + kind-of@^3.0.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -1396,6 +1553,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +limiter@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.3.tgz#32e2eb55b2324076943e5d04c1185ffb387968ef" + loader-utils@^0.2.11, loader-utils@^0.2.5, loader-utils@~0.2.2, loader-utils@~0.2.3, loader-utils@~0.2.5: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" @@ -1416,10 +1577,42 @@ lock@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/lock/-/lock-0.1.4.tgz#fec7deaef17e7c3a0a55e1da042803e25d91745d" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.1.tgz#5b7723034dda4d262e5a46fb2c58d7cc22f71420" +lodash@^4.17.4: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + lodash@~4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.5.1.tgz#80e8a074ca5f3893a6b1c10b2a636492d710c316" @@ -1473,6 +1666,15 @@ lru-memoizer@^1.11.1: lru-cache "~4.0.0" very-fast-args "^1.1.0" +lru-memoizer@^1.6.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-1.12.0.tgz#efe65706cc8a9cc653f80f0d5a6ea38ad950e352" + dependencies: + lock "~0.1.2" + lodash "^4.17.4" + lru-cache "~4.0.0" + very-fast-args "^1.1.0" + marked@~0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" @@ -1526,6 +1728,10 @@ mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +mime-db@~1.35.0: + version "1.35.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47" + mime-types@^2.1.12, mime-types@~2.1.7: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" @@ -1538,6 +1744,12 @@ mime-types@~2.1.17: dependencies: mime-db "~1.30.0" +mime-types@~2.1.19: + version "2.1.19" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0" + dependencies: + mime-db "~1.35.0" + mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -1626,6 +1838,10 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +ms@^2.0.0, ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + nan@^2.3.0: version "2.6.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" @@ -1734,6 +1950,10 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -1968,6 +2188,10 @@ pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -1988,6 +2212,10 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -2107,6 +2335,31 @@ request@2.79.0: tunnel-agent "~0.4.1" uuid "^3.0.0" +request@^2.73.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -2209,6 +2462,10 @@ safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + safe-buffer@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" @@ -2568,6 +2825,13 @@ tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -2666,6 +2930,10 @@ uuid@^3.0.0, uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + verror@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" From b77df7143a9ad01894c337b9f43e2173062cd140 Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Thu, 23 Aug 2018 16:59:58 -0300 Subject: [PATCH 02/10] tesssssts --- package.json | 1 + src/auth/OAUthWithIDTokenValidation.js | 18 +- .../oauth-with-idtoken-validation.tests.js | 264 ++++++++++++++++++ test/auth/oauth.tests.js | 33 +++ yarn.lock | 37 +++ 5 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 test/auth/oauth-with-idtoken-validation.tests.js diff --git a/package.json b/package.json index bfbcccee2..f0b1c605d 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "nock": "^3.1.1", "prettier": "^1.12.0", "pretty-quick": "^1.4.1", + "proxyquire": "^2.1.0", "sinon": "^1.17.1", "string-replace-webpack-plugin": "0.0.3", "webpack": "^1.12.14" diff --git a/src/auth/OAUthWithIDTokenValidation.js b/src/auth/OAUthWithIDTokenValidation.js index fb5a92596..8ec9c4b23 100644 --- a/src/auth/OAUthWithIDTokenValidation.js +++ b/src/auth/OAUthWithIDTokenValidation.js @@ -46,20 +46,22 @@ var OAUthWithIDTokenValidation = function(oauth, options) { * * @return {Promise|undefined} */ -OAUthWithIDTokenValidation.prototype.create = function(params, data, callback) { +OAUthWithIDTokenValidation.prototype.create = function(params, data, cb) { const createAndValidate = this.oauth.create(params, data).then(r => { var _this = this; if (r.id_token) { - var client = jwksClient({ - jwksUri: 'https://' + this.domain + '/.well-known/jwks.json' - }); function getKey(header, callback) { if (header.alg === 'HS256') { return callback(null, Buffer.from(_this.clientSecret, 'base64')); } - client.getSigningKey(header.kid, function(err, key) { + jwksClient({ + jwksUri: 'https://' + _this.domain + '/.well-known/jwks.json' + }).getSigningKey(header.kid, function(err, key) { + if (err) { + return callback(err); + } var signingKey = key.publicKey || key.rsaPublicKey; - callback(err, signingKey); + callback(null, signingKey); }); } @@ -81,10 +83,10 @@ OAUthWithIDTokenValidation.prototype.create = function(params, data, callback) { } return r; }); - if (!callback) { + if (!cb) { return createAndValidate; } - createAndValidate.then(r => callback(null, r)).catch(e => callback(e)); + createAndValidate.then(r => cb(null, r)).catch(e => cb(e)); }; module.exports = OAUthWithIDTokenValidation; diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js new file mode 100644 index 000000000..2f6aa5865 --- /dev/null +++ b/test/auth/oauth-with-idtoken-validation.tests.js @@ -0,0 +1,264 @@ +var expect = require('chai').expect; +var Promise = require('bluebird'); +var sinon = require('sinon'); +var proxyquire = require('proxyquire'); + +var jwt = require('jsonwebtoken'); +var jwksClient = require('jwks-rsa'); + +// Constants. +var DOMAIN = 'tenant.auth0.com'; +var CLIENT_ID = 'TEST_CLIENT_ID'; +var CLIENT_SECRET = new Buffer('TEST_CLIENT_SECRET', 'base64'); + +var OAUthWithIDTokenValidation = require('../../src/auth/OAUthWithIDTokenValidation'); +var PARAMS = { params: true }; +var DATA = { data: true }; + +describe('OAUthWithIDTokenValidation2', function() { + describe('#create', function() { + this.afterEach(function() { + if (jwt.verify.restore) { + jwt.verify.restore(); + } + if (jwksClient.restore) { + jwksClient.restore(); + } + }); + it('Calls `oauth.create` with correct params', function(done) { + var oauth = { + create: function(params, data) { + expect(params).to.be.equal(PARAMS); + expect(data).to.be.equal(DATA); + return new Promise(res => res({})); + } + }; + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, {}); + oauthWithValidation.create(PARAMS, DATA, done); + }); + it('Does nothing when there is no id_token', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({})); + } + }; + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, {}); + oauthWithValidation.create(PARAMS, DATA, done); + }); + it('Calls jwt.verify with token and algs', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + expect(idtoken).to.be.equal('foobar'); + expect(options).to.be.eql({ + algorithms: ['HS256', 'RS256'] + }); + done(); + }); + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, {}); + oauthWithValidation.create(PARAMS, DATA); + }); + it('Returns auth result when verify response is successful', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + callback(null, { verification: 'result' }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, {}); + oauthWithValidation.create(PARAMS, DATA).then(function(r) { + expect(r).to.be.eql({ id_token: 'foobar' }); + done(); + }); + }); + it('Returns error when verify response is an error', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + callback({ the: 'error' }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, {}); + oauthWithValidation.create(PARAMS, DATA).catch(function(r) { + expect(r).to.be.eql({ the: 'error' }); + done(); + }); + }); + it('Calls uses secret as key when header.alg === HS256', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + getKey({ alg: 'HS256' }, function(err, key) { + expect(key).to.be.eql(Buffer.from(CLIENT_SECRET, 'base64')); + done(); + }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { + clientSecret: CLIENT_SECRET + }); + oauthWithValidation.create(PARAMS, DATA); + }); + describe('when header.alg !== HS256', function() { + it('creates a jwksClient with the correct jwksUri', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + var jwksClientStub = sinon.spy(function() { + return { + getSigningKey: function(kid, cb) { + cb(null, { publicKey: 'publicKey' }); + } + }; + }); + OAUthWithIDTokenValidationProxy = proxyquire('../../src/auth/OAUthWithIDTokenValidation', { + 'jwks-rsa': jwksClientStub + }); + + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + getKey({ alg: 'RS256' }, function(err, key) { + expect(jwksClientStub.getCall(0).args[0].jwksUri).to.be.equal( + 'https://tenant.auth0.com/.well-known/jwks.json' + ); + done(); + }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidationProxy(oauth, { + domain: DOMAIN, + clientSecret: CLIENT_SECRET + }); + oauthWithValidation.create(PARAMS, DATA); + }); + it('returns the error when available', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + var jwksClientStub = sinon.spy(function() { + return { + getSigningKey: function(kid, cb) { + cb({ the: 'error' }); + } + }; + }); + OAUthWithIDTokenValidationProxy = proxyquire('../../src/auth/OAUthWithIDTokenValidation', { + 'jwks-rsa': jwksClientStub + }); + + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + getKey({ kid: 'kid', alg: 'RS256' }, function(err, key) { + expect(err).to.be.eql({ the: 'error' }); + done(); + }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidationProxy(oauth, { + domain: DOMAIN, + clientSecret: CLIENT_SECRET + }); + oauthWithValidation.create(PARAMS, DATA); + }); + it('uses the publicKey when available', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + var jwksClientStub = sinon.spy(function() { + return { + getSigningKey: function(kid, cb) { + expect(kid).to.be.equal('kid'); + cb(null, { publicKey: 'publicKey' }); + } + }; + }); + OAUthWithIDTokenValidationProxy = proxyquire('../../src/auth/OAUthWithIDTokenValidation', { + 'jwks-rsa': jwksClientStub + }); + + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + getKey({ kid: 'kid', alg: 'RS256' }, function(err, key) { + expect(key).to.be.equal('publicKey'); + done(); + }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidationProxy(oauth, { + domain: DOMAIN, + clientSecret: CLIENT_SECRET + }); + oauthWithValidation.create(PARAMS, DATA); + }); + it('uses the publicKey when both keys (publicKey and rsaPublicKey) available', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + var jwksClientStub = sinon.spy(function() { + return { + getSigningKey: function(kid, cb) { + expect(kid).to.be.equal('kid'); + cb(null, { publicKey: 'publicKey', rsaPublicKey: 'rsaPublicKey' }); + } + }; + }); + OAUthWithIDTokenValidationProxy = proxyquire('../../src/auth/OAUthWithIDTokenValidation', { + 'jwks-rsa': jwksClientStub + }); + + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + getKey({ kid: 'kid', alg: 'RS256' }, function(err, key) { + expect(key).to.be.equal('publicKey'); + done(); + }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidationProxy(oauth, { + domain: DOMAIN, + clientSecret: CLIENT_SECRET + }); + oauthWithValidation.create(PARAMS, DATA); + }); + it('uses the rsaPublicKey when there is no publicKey available', function(done) { + var oauth = { + create: function() { + return new Promise(res => res({ id_token: 'foobar' })); + } + }; + var jwksClientStub = sinon.spy(function() { + return { + getSigningKey: function(kid, cb) { + expect(kid).to.be.equal('kid'); + cb(null, { rsaPublicKey: 'rsaPublicKey' }); + } + }; + }); + OAUthWithIDTokenValidationProxy = proxyquire('../../src/auth/OAUthWithIDTokenValidation', { + 'jwks-rsa': jwksClientStub + }); + + sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { + getKey({ kid: 'kid', alg: 'RS256' }, function(err, key) { + expect(key).to.be.equal('rsaPublicKey'); + done(); + }); + }); + var oauthWithValidation = new OAUthWithIDTokenValidationProxy(oauth, { + domain: DOMAIN, + clientSecret: CLIENT_SECRET + }); + oauthWithValidation.create(PARAMS, DATA); + }); + }); + }); +}); diff --git a/test/auth/oauth.tests.js b/test/auth/oauth.tests.js index 03e652f1d..3dcb436a2 100644 --- a/test/auth/oauth.tests.js +++ b/test/auth/oauth.tests.js @@ -2,6 +2,7 @@ var expect = require('chai').expect; var extend = require('util')._extend; var nock = require('nock'); var Promise = require('bluebird'); +var sinon = require('sinon'); // Constants. var SRC_DIR = '../../src'; @@ -12,6 +13,7 @@ var CLIENT_SECRET = 'TEST_CLIENT_SECRET'; var ArgumentError = require('rest-facade').ArgumentError; var Authenticator = require(SRC_DIR + '/auth/OAuthAuthenticator'); +var OAUthWithIDTokenValidation = require('../../src/auth/OAUthWithIDTokenValidation'); var validOptions = { baseUrl: API_URL, @@ -20,7 +22,11 @@ var validOptions = { }; describe('OAuthAuthenticator', function() { + beforeEach(function() { + sinon.spy(OAUthWithIDTokenValidation.prototype, 'create'); + }); afterEach(function() { + OAUthWithIDTokenValidation.prototype.create.restore(); nock.cleanAll(); }); @@ -218,6 +224,15 @@ describe('OAuthAuthenticator', function() { }) .catch(done); }); + it('should use OAUthWithIDTokenValidation', function(done) { + this.authenticator + .signIn(userData) + .then(function() { + expect(OAUthWithIDTokenValidation.prototype.create.calledOnce).to.be.true; + done(); + }) + .catch(done); + }); }); describe('#passwordGrant', function() { @@ -379,6 +394,15 @@ describe('OAuthAuthenticator', function() { }) .catch(done); }); + it('should use OAUthWithIDTokenValidation', function(done) { + this.authenticator + .passwordGrant(userData) + .then(function() { + expect(OAUthWithIDTokenValidation.prototype.create.calledOnce).to.be.true; + done(); + }) + .catch(done); + }); }); describe('#socialSignIn', function() { @@ -815,5 +839,14 @@ describe('OAuthAuthenticator', function() { }) .catch(done); }); + it('should use OAUthWithIDTokenValidation', function(done) { + this.authenticator + .authorizationCodeGrant(data) + .then(function() { + expect(OAUthWithIDTokenValidation.prototype.create.calledOnce).to.be.true; + done(); + }) + .catch(done); + }); }); }); diff --git a/yarn.lock b/yarn.lock index 86991ea5d..e4c1a919b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -864,6 +864,13 @@ filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" +fill-keys@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" + dependencies: + is-object "~1.0.1" + merge-descriptors "~1.0.0" + fill-range@^2.1.0: version "2.2.3" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" @@ -1329,6 +1336,10 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-object@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -1698,6 +1709,10 @@ memory-fs@~0.3.0: errno "^0.1.3" readable-stream "^2.0.1" +merge-descriptors@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + methods@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -1822,6 +1837,10 @@ mocha@^2.2.4: supports-color "1.2.0" to-iso-string "0.0.2" +module-not-found-error@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" + moment@^2.18.1: version "2.18.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" @@ -2111,6 +2130,10 @@ path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" +path-parse@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + pbkdf2-compat@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288" @@ -2180,6 +2203,14 @@ proxy-agent@2: pac-proxy-agent "^2.0.0" socks-proxy-agent "2" +proxyquire@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.0.tgz#c2263a38bf0725f2ae950facc130e27510edce8d" + dependencies: + fill-keys "^1.0.2" + module-not-found-error "^1.0.0" + resolve "~1.8.1" + prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" @@ -2424,6 +2455,12 @@ resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" +resolve@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + dependencies: + path-parse "^1.0.5" + rest-facade@^1.10.1: version "1.10.1" resolved "https://registry.yarnpkg.com/rest-facade/-/rest-facade-1.10.1.tgz#a9b030ff50df28c9ea1a2719f94e369c47167d20" From 8ba9738713f97fc140069c19aef7da216721e76e Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Wed, 12 Sep 2018 15:57:08 -0300 Subject: [PATCH 03/10] PR review --- src/auth/OAUthWithIDTokenValidation.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/auth/OAUthWithIDTokenValidation.js b/src/auth/OAUthWithIDTokenValidation.js index 8ec9c4b23..3cb3108ba 100644 --- a/src/auth/OAUthWithIDTokenValidation.js +++ b/src/auth/OAUthWithIDTokenValidation.js @@ -6,19 +6,18 @@ var ArgumentError = require('rest-facade').ArgumentError; /** * @class - * Abstracts the `oauth.create` method with additional id_token validation\ + * Abstracts the `oauth.create` method with additional id_token validation * @constructor * @memberOf module:auth * * @param {Object} options Authenticator options. - * @param {String} options.baseUrl The auth0 account URL. - * @param {String} options.domain AuthenticationClient server domain + * @param {String} options.domain AuthenticationClient server domain * @param {String} [options.clientId] Default client ID. * @param {String} [options.clientSecret] Default client Secret. */ var OAUthWithIDTokenValidation = function(oauth, options) { if (!oauth) { - throw new ArgumentError('Missing authenticator options'); + throw new ArgumentError('Missing OAuthAuthenticator param'); } if (!options) { @@ -36,13 +35,14 @@ var OAUthWithIDTokenValidation = function(oauth, options) { }; /** - * Creates an oauth request + * Creates an oauth request and validates the id_token (if any) * * @method create * @memberOf module:auth.OAuthWithIDTokenValidation.prototype * - * @param {Object} options Options are passed through - * @param {Function} [callbabck] Callback function + * @param {Object} params OAuth parameters that are passed through + * @param {Object} data Custom parameters sent to the OAuth endpoint + * @param {Function} [callback] Callback function * * @return {Promise|undefined} */ @@ -61,7 +61,7 @@ OAUthWithIDTokenValidation.prototype.create = function(params, data, cb) { return callback(err); } var signingKey = key.publicKey || key.rsaPublicKey; - callback(null, signingKey); + return callback(null, signingKey); }); } From 763ea3ce689519d61b22773131a7314c4394771a Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Wed, 12 Sep 2018 18:21:20 -0300 Subject: [PATCH 04/10] Adding supportedAlgorithms param + integration tests --- src/auth/OAUthWithIDTokenValidation.js | 15 ++- src/auth/index.js | 13 +- .../oauth-with-idtoken-validation.tests.js | 118 +++++++++++++++++- 3 files changed, 131 insertions(+), 15 deletions(-) diff --git a/src/auth/OAUthWithIDTokenValidation.js b/src/auth/OAUthWithIDTokenValidation.js index 3cb3108ba..8d248d38d 100644 --- a/src/auth/OAUthWithIDTokenValidation.js +++ b/src/auth/OAUthWithIDTokenValidation.js @@ -10,10 +10,11 @@ var ArgumentError = require('rest-facade').ArgumentError; * @constructor * @memberOf module:auth * - * @param {Object} options Authenticator options. - * @param {String} options.domain AuthenticationClient server domain - * @param {String} [options.clientId] Default client ID. - * @param {String} [options.clientSecret] Default client Secret. + * @param {Object} options Authenticator options. + * @param {String} options.domain AuthenticationClient server domain + * @param {String} [options.clientId] Default client ID. + * @param {String} [options.clientSecret] Default client Secret. + * @param {String} [options.supportedAlgorithms] Algorithms that your application expects to receive */ var OAUthWithIDTokenValidation = function(oauth, options) { if (!oauth) { @@ -32,6 +33,7 @@ var OAUthWithIDTokenValidation = function(oauth, options) { this.clientId = options.clientId; this.clientSecret = options.clientSecret; this.domain = options.domain; + this.supportedAlgorithms = options.supportedAlgorithms || ['HS256', 'RS256']; }; /** @@ -64,13 +66,14 @@ OAUthWithIDTokenValidation.prototype.create = function(params, data, cb) { return callback(null, signingKey); }); } - return new Promise((res, rej) => { jwt.verify( r.id_token, getKey, { - algorithms: ['HS256', 'RS256'] + algorithms: this.supportedAlgorithms, + audience: this.clientId, + issuer: 'https://' + this.domain + '/' }, function(err, payload) { if (err) { diff --git a/src/auth/index.js b/src/auth/index.js index 56df17e83..fa410aa9c 100644 --- a/src/auth/index.js +++ b/src/auth/index.js @@ -39,11 +39,11 @@ var BASE_URL_FORMAT = 'https://%s'; * clientId: '{OPTIONAL_CLIENT_ID}' * }); * - * @param {Object} options Options for the Authentication Client - * SDK. - * @param {String} options.domain AuthenticationClient server domain. - * @param {String} [options.clientId] Default client ID. - * @param {String} [options.clientSecret] Default client Secret. + * @param {Object} options Options for the Authentication Client SDK. + * @param {String} options.domain AuthenticationClient server domain. + * @param {String} [options.clientId] Default client ID. + * @param {String} [options.clientSecret] Default client Secret. + * @param {String} [options.supportedAlgorithms] Algorithms that your application expects to receive */ var AuthenticationClient = function(options) { if (!options || typeof options !== 'object') { @@ -62,7 +62,8 @@ var AuthenticationClient = function(options) { 'User-agent': 'node.js/' + process.version.replace('v', ''), 'Content-Type': 'application/json' }, - baseUrl: util.format(BASE_URL_FORMAT, options.domain) + baseUrl: util.format(BASE_URL_FORMAT, options.domain), + supportedAlgorithms: options.supportedAlgorithms }; if (options.telemetry !== false) { diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js index 2f6aa5865..28c7b8c1a 100644 --- a/test/auth/oauth-with-idtoken-validation.tests.js +++ b/test/auth/oauth-with-idtoken-validation.tests.js @@ -15,7 +15,7 @@ var OAUthWithIDTokenValidation = require('../../src/auth/OAUthWithIDTokenValidat var PARAMS = { params: true }; var DATA = { data: true }; -describe('OAUthWithIDTokenValidation2', function() { +describe('OAUthWithIDTokenValidation', function() { describe('#create', function() { this.afterEach(function() { if (jwt.verify.restore) { @@ -54,11 +54,16 @@ describe('OAUthWithIDTokenValidation2', function() { sinon.stub(jwt, 'verify', function(idtoken, getKey, options, callback) { expect(idtoken).to.be.equal('foobar'); expect(options).to.be.eql({ - algorithms: ['HS256', 'RS256'] + audience: CLIENT_ID, + algorithms: ['HS256', 'RS256'], + issuer: 'https://' + DOMAIN + '/' }); done(); }); - var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, {}); + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { + clientId: CLIENT_ID, + domain: DOMAIN + }); oauthWithValidation.create(PARAMS, DATA); }); it('Returns auth result when verify response is successful', function(done) { @@ -260,5 +265,112 @@ describe('OAUthWithIDTokenValidation2', function() { oauthWithValidation.create(PARAMS, DATA); }); }); + describe('#integration', function() { + it('with a HS256 id_token and `options.supportedAlgorithms===RS256`', done => { + var oauth = { + create: function() { + return new Promise(res => + res({ + id_token: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMDo1MjoxMS4zMDZaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9hdXRoLmJydWNrZS5jbHViLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg1NTMxLCJleHAiOjE1MzY4MjE1MzF9.mZGsJyJYyp_mkINcnV0JRJ6QPsTXUE8FrpRTruAIqhE' + }) + ); + } + }; + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { + clientSecret: CLIENT_SECRET, + supportedAlgorithms: ['RS256'] + }); + oauthWithValidation.create(PARAMS, DATA, function(e) { + expect(e.message).to.eq('invalid algorithm'); + done(); + }); + }); + it('when `token.header.alg===RS256` and `options.supportedAlgorithms===HS256`', done => { + var oauth = { + create: function() { + return new Promise(res => + res({ + id_token: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMDo1NTozMi4xMTlaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9hdXRoLmJydWNrZS5jbHViLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg1NzMyLCJleHAiOjE1MzY4MjE3MzJ9.i8iBJntBiSPRLIJdLmgTwnT_FXamc4ug8al8Ws1X-P7UAxbEaaa3irjqfBnDf50tDAQkHFcwIKiMDIrEHHBEPPEc7MH8dlxDAr80Pr8-T-M_ls8U6KccBGfrsurlJaU6qMVSfUP25kmZm5torI0D81c9rZRWcdpb64EnZCvqpUPWZjap__PoC-G88NRH_28jT_hV-bGYgbjJ3FqL_xTZ2u866bQljt1oJlOf3vvLIL4tW9MYdYxOvh7VZXWji9TirrjCb6cuq-CZ5ZWTSpV_NRC24BMdGx_Mu-4EBUMb8uWiaLBrjJgb_NtOZXY6p6PeJQuX5S2MeD2z_SCXOcwukQ' + }) + ); + } + }; + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { + clientSecret: CLIENT_SECRET, + domain: 'brucke.auth0.com', + supportedAlgorithms: ['HS256'] + }); + oauthWithValidation.create(PARAMS, DATA, function(e) { + expect(e.message).to.eq('invalid algorithm'); + done(); + }); + }); + it('when `token.aud` is invalid', done => { + var oauth = { + create: function() { + return new Promise(res => + res({ + id_token: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMDo1NTozMi4xMTlaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9hdXRoLmJydWNrZS5jbHViLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg1NzMyLCJleHAiOjE1MzY4MjE3MzJ9.i8iBJntBiSPRLIJdLmgTwnT_FXamc4ug8al8Ws1X-P7UAxbEaaa3irjqfBnDf50tDAQkHFcwIKiMDIrEHHBEPPEc7MH8dlxDAr80Pr8-T-M_ls8U6KccBGfrsurlJaU6qMVSfUP25kmZm5torI0D81c9rZRWcdpb64EnZCvqpUPWZjap__PoC-G88NRH_28jT_hV-bGYgbjJ3FqL_xTZ2u866bQljt1oJlOf3vvLIL4tW9MYdYxOvh7VZXWji9TirrjCb6cuq-CZ5ZWTSpV_NRC24BMdGx_Mu-4EBUMb8uWiaLBrjJgb_NtOZXY6p6PeJQuX5S2MeD2z_SCXOcwukQ' + }) + ); + } + }; + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { + clientId: 'foobar', + clientSecret: CLIENT_SECRET, + domain: 'brucke.auth0.com', + supportedAlgorithms: ['RS256'] + }); + oauthWithValidation.create(PARAMS, DATA, function(e) { + expect(e.message).to.eq('jwt audience invalid. expected: foobar'); + done(); + }); + }); + it('when `token.iss` is invalid', done => { + var oauth = { + create: function() { + return new Promise(res => + res({ + id_token: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMToxMzo1OS42OTNaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg2ODM5LCJleHAiOjE1MzY4MjI4Mzl9.BkWn4lTu-_GWBC9QfjN1fux4yNe7TfjoyVwsd6tWc7GpuIsAb6GtZbiijfVBkiCchp7V28U2APMTi5Wt1luKkdD0OgI28GOFKKp6qM1qbpt1kexr3So5TgfTb9xbQF-B2HCrqE-fGughAOD4qc4N4UPS_6vRz24fyb4Y8O1wPtdfg9h49ioDa-c3-gyYsaWtUqRJfFoVU9AXBZaIJnKoefz5Oz-_cRWxOI5ci_zWWE2BoxkIMqWVN3Xzzr2njdFKM22HrIIzCz23neNW7bzyMotGjG0B4dbKaY4oIGiK7nI4OopDKbK1AD8KZpTjw97SpL8MbnIhN8-1c1fIVOxKEA' + }) + ); + } + }; + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { + clientSecret: CLIENT_SECRET, + domain: 'auth.brucke.club', + supportedAlgorithms: ['RS256'] + }); + oauthWithValidation.create(PARAMS, DATA, function(e, d) { + expect(e.message).to.eq('jwt issuer invalid. expected: https://auth.brucke.club/'); + done(); + }); + }); + it('when `token.exp` is expired', done => { + var oauth = { + create: function() { + return new Promise(res => + res({ + id_token: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMToxNzoyNy41NjVaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg3MDQ3LCJleHAiOjE1MzY3ODcwNTJ9.Wn6ie7sXQ7jG94MDumSa2vciKkt5qrDN8LGWw1U9cz8Oh15JxFZOxtPJxWST5t6i8biJ4l7fvjez7KkoibRf9TPXpe0VxE2SsQCy-H2TRlUSnodBg25WRPPKmXvA6tB_CeaZjDplaTV21fnvcRq7kCwl_O91meWS7Qs3rEWvrD_M63LvDPvAReKcNFRg42p_nZS5fnq2CLC6OHUBznkZfMforNJ8YC0GufcrBd2lRaNljF57Z6fHSupfwY9vLIxfp-nx7yYl7H1vjp75f-08h8mOLRgZdpCjG3z8QKCBwsY_5t8dnQfZiUsGhRFx6hsTb6BC35JHkNHSyOw75tfl9A' + }) + ); + } + }; + var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { + clientSecret: CLIENT_SECRET, + domain: 'auth.brucke.club', + supportedAlgorithms: ['RS256'] + }); + oauthWithValidation.create(PARAMS, DATA, function(e, d) { + expect(e.message).to.eq('jwt expired'); + done(); + }); + }); + }); }); }); From 872195266a89c9adf9bc10bd426f45b76c655b0c Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Thu, 20 Sep 2018 17:03:50 -0300 Subject: [PATCH 05/10] simple PR fixes --- src/auth/OAUthWithIDTokenValidation.js | 1 + src/auth/OAuthAuthenticator.js | 4 ++-- test/auth/oauth-with-idtoken-validation.tests.js | 12 ++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/auth/OAUthWithIDTokenValidation.js b/src/auth/OAUthWithIDTokenValidation.js index 8d248d38d..3d8c66a06 100644 --- a/src/auth/OAUthWithIDTokenValidation.js +++ b/src/auth/OAUthWithIDTokenValidation.js @@ -10,6 +10,7 @@ var ArgumentError = require('rest-facade').ArgumentError; * @constructor * @memberOf module:auth * + * @param {Object} oauth An instance of @type {OAuthAuthenticator} * @param {Object} options Authenticator options. * @param {String} options.domain AuthenticationClient server domain * @param {String} [options.clientId] Default client ID. diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js index 0b0005d56..0d2b33d81 100644 --- a/src/auth/OAuthAuthenticator.js +++ b/src/auth/OAuthAuthenticator.js @@ -13,8 +13,8 @@ var OAUthWithIDTokenValidation = require('./OAUthWithIDTokenValidation'); * @memberOf module:auth * * @param {Object} options Authenticator options. - * @param {String} options.baseUrl The auth0 account URL. - * @param {String} options.domain AuthenticationClient server domain + * @param {String} options.baseUrl The Auth0 account URL. + * @param {String} options.domain AuthenticationClient server domain * @param {String} [options.clientId] Default client ID. * @param {String} [options.clientSecret] Default client Secret. */ diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js index 28c7b8c1a..13da6fd6c 100644 --- a/test/auth/oauth-with-idtoken-validation.tests.js +++ b/test/auth/oauth-with-idtoken-validation.tests.js @@ -15,7 +15,7 @@ var OAUthWithIDTokenValidation = require('../../src/auth/OAUthWithIDTokenValidat var PARAMS = { params: true }; var DATA = { data: true }; -describe('OAUthWithIDTokenValidation', function() { +describe.only('OAUthWithIDTokenValidation', function() { describe('#create', function() { this.afterEach(function() { if (jwt.verify.restore) { @@ -266,7 +266,7 @@ describe('OAUthWithIDTokenValidation', function() { }); }); describe('#integration', function() { - it('with a HS256 id_token and `options.supportedAlgorithms===RS256`', done => { + it('fails with a HS256 id_token and `options.supportedAlgorithms===RS256`', done => { var oauth = { create: function() { return new Promise(res => @@ -286,7 +286,7 @@ describe('OAUthWithIDTokenValidation', function() { done(); }); }); - it('when `token.header.alg===RS256` and `options.supportedAlgorithms===HS256`', done => { + it('fails with a RS256 id_token and `options.supportedAlgorithms===HS256`', done => { var oauth = { create: function() { return new Promise(res => @@ -307,7 +307,7 @@ describe('OAUthWithIDTokenValidation', function() { done(); }); }); - it('when `token.aud` is invalid', done => { + it('fails when `token.aud` is invalid', done => { var oauth = { create: function() { return new Promise(res => @@ -329,7 +329,7 @@ describe('OAUthWithIDTokenValidation', function() { done(); }); }); - it('when `token.iss` is invalid', done => { + it('fails when `token.iss` is invalid', done => { var oauth = { create: function() { return new Promise(res => @@ -350,7 +350,7 @@ describe('OAUthWithIDTokenValidation', function() { done(); }); }); - it('when `token.exp` is expired', done => { + it('fails when `token.exp` is expired', done => { var oauth = { create: function() { return new Promise(res => From 16c1751b4ecebf1e460d532f8c9121173413fb81 Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Fri, 21 Sep 2018 15:13:42 -0300 Subject: [PATCH 06/10] Adding integration tests with a new certificate for each test --- package.json | 1 + .../oauth-with-idtoken-validation.tests.js | 141 ++++++++++++------ yarn.lock | 23 ++- 3 files changed, 121 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index f0b1c605d..539b2e801 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "mocha-multi": "^0.11.0", "moment": "^2.18.1", "nock": "^3.1.1", + "pem": "^1.13.1", "prettier": "^1.12.0", "pretty-quick": "^1.4.1", "proxyquire": "^2.1.0", diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js index 13da6fd6c..2063823cd 100644 --- a/test/auth/oauth-with-idtoken-validation.tests.js +++ b/test/auth/oauth-with-idtoken-validation.tests.js @@ -5,6 +5,7 @@ var proxyquire = require('proxyquire'); var jwt = require('jsonwebtoken'); var jwksClient = require('jwks-rsa'); +var pem = require('pem'); // Constants. var DOMAIN = 'tenant.auth0.com'; @@ -15,6 +16,20 @@ var OAUthWithIDTokenValidation = require('../../src/auth/OAUthWithIDTokenValidat var PARAMS = { params: true }; var DATA = { data: true }; +var createCertificate = function(cb) { + pem.createCertificate({ days: 1, selfSigned: true }, function(err, keys) { + if (err) { + throw err; + } + pem.getPublicKey(keys.certificate, function(e, p) { + if (e) { + throw e; + } + cb({ serviceKey: keys.serviceKey, certificate: keys.certificate, publicKey: p.publicKey }); + }); + }); +}; + describe.only('OAUthWithIDTokenValidation', function() { describe('#create', function() { this.afterEach(function() { @@ -307,35 +322,13 @@ describe.only('OAUthWithIDTokenValidation', function() { done(); }); }); - it('fails when `token.aud` is invalid', done => { - var oauth = { - create: function() { - return new Promise(res => - res({ - id_token: - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMDo1NTozMi4xMTlaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9hdXRoLmJydWNrZS5jbHViLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg1NzMyLCJleHAiOjE1MzY4MjE3MzJ9.i8iBJntBiSPRLIJdLmgTwnT_FXamc4ug8al8Ws1X-P7UAxbEaaa3irjqfBnDf50tDAQkHFcwIKiMDIrEHHBEPPEc7MH8dlxDAr80Pr8-T-M_ls8U6KccBGfrsurlJaU6qMVSfUP25kmZm5torI0D81c9rZRWcdpb64EnZCvqpUPWZjap__PoC-G88NRH_28jT_hV-bGYgbjJ3FqL_xTZ2u866bQljt1oJlOf3vvLIL4tW9MYdYxOvh7VZXWji9TirrjCb6cuq-CZ5ZWTSpV_NRC24BMdGx_Mu-4EBUMb8uWiaLBrjJgb_NtOZXY6p6PeJQuX5S2MeD2z_SCXOcwukQ' - }) - ); - } - }; - var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { - clientId: 'foobar', - clientSecret: CLIENT_SECRET, - domain: 'brucke.auth0.com', - supportedAlgorithms: ['RS256'] - }); - oauthWithValidation.create(PARAMS, DATA, function(e) { - expect(e.message).to.eq('jwt audience invalid. expected: foobar'); - done(); - }); - }); - it('fails when `token.iss` is invalid', done => { + it('fails when `token.exp` is expired', done => { var oauth = { create: function() { return new Promise(res => res({ id_token: - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMToxMzo1OS42OTNaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg2ODM5LCJleHAiOjE1MzY4MjI4Mzl9.BkWn4lTu-_GWBC9QfjN1fux4yNe7TfjoyVwsd6tWc7GpuIsAb6GtZbiijfVBkiCchp7V28U2APMTi5Wt1luKkdD0OgI28GOFKKp6qM1qbpt1kexr3So5TgfTb9xbQF-B2HCrqE-fGughAOD4qc4N4UPS_6vRz24fyb4Y8O1wPtdfg9h49ioDa-c3-gyYsaWtUqRJfFoVU9AXBZaIJnKoefz5Oz-_cRWxOI5ci_zWWE2BoxkIMqWVN3Xzzr2njdFKM22HrIIzCz23neNW7bzyMotGjG0B4dbKaY4oIGiK7nI4OopDKbK1AD8KZpTjw97SpL8MbnIhN8-1c1fIVOxKEA' + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMToxNzoyNy41NjVaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg3MDQ3LCJleHAiOjE1MzY3ODcwNTJ9.Wn6ie7sXQ7jG94MDumSa2vciKkt5qrDN8LGWw1U9cz8Oh15JxFZOxtPJxWST5t6i8biJ4l7fvjez7KkoibRf9TPXpe0VxE2SsQCy-H2TRlUSnodBg25WRPPKmXvA6tB_CeaZjDplaTV21fnvcRq7kCwl_O91meWS7Qs3rEWvrD_M63LvDPvAReKcNFRg42p_nZS5fnq2CLC6OHUBznkZfMforNJ8YC0GufcrBd2lRaNljF57Z6fHSupfwY9vLIxfp-nx7yYl7H1vjp75f-08h8mOLRgZdpCjG3z8QKCBwsY_5t8dnQfZiUsGhRFx6hsTb6BC35JHkNHSyOw75tfl9A' }) ); } @@ -346,29 +339,93 @@ describe.only('OAUthWithIDTokenValidation', function() { supportedAlgorithms: ['RS256'] }); oauthWithValidation.create(PARAMS, DATA, function(e, d) { - expect(e.message).to.eq('jwt issuer invalid. expected: https://auth.brucke.club/'); + expect(e.message).to.eq('jwt expired'); done(); }); }); - it('fails when `token.exp` is expired', done => { - var oauth = { - create: function() { - return new Promise(res => - res({ - id_token: - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wOS0xMlQyMToxNzoyNy41NjVaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6IkVxbjdHTUV3VzhDbmN1S2FhcFRuNWs5VEJ0MzRQdldmIiwiaWF0IjoxNTM2Nzg3MDQ3LCJleHAiOjE1MzY3ODcwNTJ9.Wn6ie7sXQ7jG94MDumSa2vciKkt5qrDN8LGWw1U9cz8Oh15JxFZOxtPJxWST5t6i8biJ4l7fvjez7KkoibRf9TPXpe0VxE2SsQCy-H2TRlUSnodBg25WRPPKmXvA6tB_CeaZjDplaTV21fnvcRq7kCwl_O91meWS7Qs3rEWvrD_M63LvDPvAReKcNFRg42p_nZS5fnq2CLC6OHUBznkZfMforNJ8YC0GufcrBd2lRaNljF57Z6fHSupfwY9vLIxfp-nx7yYl7H1vjp75f-08h8mOLRgZdpCjG3z8QKCBwsY_5t8dnQfZiUsGhRFx6hsTb6BC35JHkNHSyOw75tfl9A' - }) + describe('when using a valid token', function() { + it('fails when `token.aud` is invalid', done => { + createCertificate(function(c) { + var idtoken = jwt.sign({ foo: 'bar' }, c.serviceKey, { + algorithm: 'RS256', + audience: 'wrong_audience', + expiresIn: '1h' + }); + var oauth = { + create: function() { + return new Promise(res => + res({ + id_token: idtoken + }) + ); + } + }; + var jwksClientStub = sinon.spy(function() { + return { + getSigningKey: function(kid, cb) { + cb(null, { publicKey: c.publicKey }); + } + }; + }); + OAUthWithIDTokenValidationProxy = proxyquire( + '../../src/auth/OAUthWithIDTokenValidation', + { + 'jwks-rsa': jwksClientStub + } ); - } - }; - var oauthWithValidation = new OAUthWithIDTokenValidation(oauth, { - clientSecret: CLIENT_SECRET, - domain: 'auth.brucke.club', - supportedAlgorithms: ['RS256'] + var oauthWithValidation = new OAUthWithIDTokenValidationProxy(oauth, { + clientId: 'foobar', + clientSecret: CLIENT_SECRET, + domain: 'brucke.auth0.com', + supportedAlgorithms: ['RS256'] + }); + oauthWithValidation.create(PARAMS, DATA, function(e) { + expect(e.message).to.eq('jwt audience invalid. expected: foobar'); + done(); + }); + }); }); - oauthWithValidation.create(PARAMS, DATA, function(e, d) { - expect(e.message).to.eq('jwt expired'); - done(); + it('fails when `token.iss` is invalid', done => { + createCertificate(function(c) { + var idtoken = jwt.sign({ foo: 'bar' }, c.serviceKey, { + algorithm: 'RS256', + issuer: 'wrong_issuer', + audience: 'foobar', + expiresIn: '1h' + }); + var oauth = { + create: function() { + return new Promise(res => + res({ + id_token: idtoken + }) + ); + } + }; + var jwksClientStub = sinon.spy(function() { + return { + getSigningKey: function(kid, cb) { + cb(null, { publicKey: c.publicKey }); + } + }; + }); + OAUthWithIDTokenValidationProxy = proxyquire( + '../../src/auth/OAUthWithIDTokenValidation', + { + 'jwks-rsa': jwksClientStub + } + ); + var oauthWithValidation = new OAUthWithIDTokenValidationProxy(oauth, { + clientId: 'foobar', + clientSecret: CLIENT_SECRET, + domain: 'brucke.auth0.com', + supportedAlgorithms: ['RS256'] + }); + oauthWithValidation.create(PARAMS, DATA, function(e) { + expect(e.message).to.eq('jwt issuer invalid. expected: https://brucke.auth0.com/'); + done(); + }); + }); }); }); }); diff --git a/yarn.lock b/yarn.lock index e4c1a919b..8531b7c3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -742,6 +742,10 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +es6-promisify@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.0.0.tgz#b526a75eaa5ca600e960bf3d5ad98c40d75c7203" + escape-string-regexp@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" @@ -1690,7 +1694,7 @@ marked@~0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" -md5@^2.1.0: +md5@^2.1.0, md5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" dependencies: @@ -2028,7 +2032,7 @@ os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-tmpdir@^1.0.0: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -2138,6 +2142,15 @@ pbkdf2-compat@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288" +pem@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/pem/-/pem-1.13.1.tgz#57dd3e0c044fbcf709db026a737e1aad7dc8330f" + dependencies: + es6-promisify "^6.0.0" + md5 "^2.2.1" + os-tmpdir "^1.0.1" + which "^1.3.1" + performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" @@ -3034,6 +3047,12 @@ which@^1.2.9: dependencies: isexe "^2.0.0" +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" From f9e69b9b54421d33380c1f4b331e793cd593c4ff Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Fri, 21 Sep 2018 15:45:03 -0300 Subject: [PATCH 07/10] remove .only --- test/auth/oauth-with-idtoken-validation.tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js index 2063823cd..84f4cba26 100644 --- a/test/auth/oauth-with-idtoken-validation.tests.js +++ b/test/auth/oauth-with-idtoken-validation.tests.js @@ -30,7 +30,7 @@ var createCertificate = function(cb) { }); }; -describe.only('OAUthWithIDTokenValidation', function() { +describe('OAUthWithIDTokenValidation', function() { describe('#create', function() { this.afterEach(function() { if (jwt.verify.restore) { From 6d39e106db39b544ad610b1aaeee982f1119b76d Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Fri, 21 Sep 2018 18:04:15 -0300 Subject: [PATCH 08/10] renaming test section --- test/auth/oauth-with-idtoken-validation.tests.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js index 84f4cba26..4fc364bc7 100644 --- a/test/auth/oauth-with-idtoken-validation.tests.js +++ b/test/auth/oauth-with-idtoken-validation.tests.js @@ -343,7 +343,7 @@ describe('OAUthWithIDTokenValidation', function() { done(); }); }); - describe('when using a valid token', function() { + describe('when using a valid certificate to generate an invalid id_token', function() { it('fails when `token.aud` is invalid', done => { createCertificate(function(c) { var idtoken = jwt.sign({ foo: 'bar' }, c.serviceKey, { @@ -386,11 +386,12 @@ describe('OAUthWithIDTokenValidation', function() { }); }); it('fails when `token.iss` is invalid', done => { + const TEST_AUDIENCE = 'foobar'; createCertificate(function(c) { var idtoken = jwt.sign({ foo: 'bar' }, c.serviceKey, { algorithm: 'RS256', issuer: 'wrong_issuer', - audience: 'foobar', + audience: TEST_AUDIENCE, expiresIn: '1h' }); var oauth = { From c514e61798b33dcf14ffbc495a480f25eff01175 Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Fri, 21 Sep 2018 18:32:55 -0300 Subject: [PATCH 09/10] Add missig tests --- .../auth/oauth-with-idtoken-validation.tests.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/auth/oauth-with-idtoken-validation.tests.js b/test/auth/oauth-with-idtoken-validation.tests.js index 4fc364bc7..8e53a2490 100644 --- a/test/auth/oauth-with-idtoken-validation.tests.js +++ b/test/auth/oauth-with-idtoken-validation.tests.js @@ -31,6 +31,23 @@ var createCertificate = function(cb) { }; describe('OAUthWithIDTokenValidation', function() { + describe('constructor', function() { + it('validates `oauth` is required', function() { + expect(function() { + new OAUthWithIDTokenValidation(); + }).to.throw('Missing OAuthAuthenticator param'); + }); + it('validates `options` is required', function() { + expect(function() { + new OAUthWithIDTokenValidation({}); + }).to.throw('Missing authenticator options'); + }); + it('validates `oauth` is required', function() { + expect(function() { + new OAUthWithIDTokenValidation({}, 'asd'); + }).to.throw('The authenticator options must be an object'); + }); + }); describe('#create', function() { this.afterEach(function() { if (jwt.verify.restore) { From 6d9f6b675e3e213640b9460c4221f029f68006c2 Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Mon, 24 Sep 2018 10:59:19 -0300 Subject: [PATCH 10/10] create jwksClient in the constructor --- src/auth/OAUthWithIDTokenValidation.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/auth/OAUthWithIDTokenValidation.js b/src/auth/OAUthWithIDTokenValidation.js index 3d8c66a06..b9833f948 100644 --- a/src/auth/OAUthWithIDTokenValidation.js +++ b/src/auth/OAUthWithIDTokenValidation.js @@ -35,6 +35,9 @@ var OAUthWithIDTokenValidation = function(oauth, options) { this.clientSecret = options.clientSecret; this.domain = options.domain; this.supportedAlgorithms = options.supportedAlgorithms || ['HS256', 'RS256']; + this._jwksClient = jwksClient({ + jwksUri: 'https://' + options.domain + '/.well-known/jwks.json' + }); }; /** @@ -57,9 +60,7 @@ OAUthWithIDTokenValidation.prototype.create = function(params, data, cb) { if (header.alg === 'HS256') { return callback(null, Buffer.from(_this.clientSecret, 'base64')); } - jwksClient({ - jwksUri: 'https://' + _this.domain + '/.well-known/jwks.json' - }).getSigningKey(header.kid, function(err, key) { + _this._jwksClient.getSigningKey(header.kid, function(err, key) { if (err) { return callback(err); }