From e987049e43335cd8b80570477a3dd4596c318264 Mon Sep 17 00:00:00 2001 From: hsimah Date: Fri, 26 Jul 2019 13:19:21 -0700 Subject: [PATCH] fix hard coding of agent --- lib/metadata.js | 9 ++- lib/oidcstrategy.js | 184 ++++++++++++++++++++++---------------------- package.json | 4 +- 3 files changed, 102 insertions(+), 95 deletions(-) diff --git a/lib/metadata.js b/lib/metadata.js index dac83a10..ffe4aac4 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -46,7 +46,10 @@ function Metadata(url, authtype, options) { this.metadata = null; this.authtype = authtype; this.loggingNoPII = options.loggingNoPII; - this.httpsProxyAgent = new HttpsProxyAgent(options.proxy); + if (options.proxy) { + // if user has specified proxy settings instantiate agent + this.httpsProxyAgent = new HttpsProxyAgent(options.proxy); + } } Object.defineProperty(Metadata, 'url', { @@ -169,14 +172,14 @@ Metadata.prototype.generateOidcPEM = function generateOidcPEM(kid) { else throw new Error(`a key with kid %s cannot be found`, kid); } - + if (!pubKey) { if (self.loggingNoPII) throw new Error('generating public key pem failed'); else throw new Error(`generating public key pem failed for kid: %s`, kid); } - + return pubKey; }; diff --git a/lib/oidcstrategy.js b/lib/oidcstrategy.js index 19e3e671..8bd6d9cf 100644 --- a/lib/oidcstrategy.js +++ b/lib/oidcstrategy.js @@ -66,7 +66,7 @@ const ttl = 1800; // 30 minutes cache // time is configurable by user. // 2. NONCE_MAX_AMOUNT is the max amount of tuples a user's session can have. We limit it to 10. // This value limits the amount of microsoft login page tabs a user can open before the user types -// username and password to 10. If the user opens more than 10 login tabs, we only honor the most +// username and password to 10. If the user opens more than 10 login tabs, we only honor the most // recent 10 tabs within the life time. const NONCE_MAX_AMOUNT = 10; const NONCE_LIFE_TIME = 3600; // second @@ -132,8 +132,8 @@ function onProfileLoaded(strategy, args) { * * function(token, done) { ... } * or - * function(req, token, done) { .... } - * + * function(req, token, done) { .... } + * * (passReqToCallback must be set true in options in order to use the second signature.) * * `token` is the verified and decoded bearer token provided as a credential. @@ -153,7 +153,7 @@ function onProfileLoaded(strategy, args) { * - `identityMetadata` (1) Required * (2) must be a https url string * (3) Description: - * the metadata endpoint provided by the Microsoft Identity Portal that provides + * the metadata endpoint provided by the Microsoft Identity Portal that provides * the keys and other important info at runtime. Examples: * <1> v1 tenant-specific endpoint * - https://login.microsoftonline.com/your_tenant_name.onmicrosoft.com/.well-known/openid-configuration @@ -190,17 +190,17 @@ function onProfileLoaded(strategy, args) { * (1) Required to set to true if you want to use http url for redirectUrl * (2) Description: * The default value is false. It's OK to use http like 'http://localhost:3000' in the - * dev environment, but in production environment https should always be used. + * dev environment, but in production environment https should always be used. * * - `clientSecret` (1) This option only applies when `responseType` is 'code', 'id_token code' or 'code id_token'. * To redeem an authorization code, we can use either client secret flow or client assertion flow. * (1.1) For B2C, clientSecret is required since client assertion is not supported - * (1.2) For non-B2C, both flows are supported. Developer must provide either clientSecret, or - * thumbprint and privatePEMKey. We use clientSecret if it is provided, otherwise we use + * (1.2) For non-B2C, both flows are supported. Developer must provide either clientSecret, or + * thumbprint and privatePEMKey. We use clientSecret if it is provided, otherwise we use * thumbprint and privatePEMKey for the client assertion flow. * (2) must be a string * (3) Description: - * The app key of your app from AAD. + * The app key of your app from AAD. * NOTE: For B2C, the app key sometimes contains '\', please replace '\' with '\\' in the app key, otherwise * '\' will be treated as the beginning of a escaping character * @@ -234,19 +234,19 @@ function onProfileLoaded(strategy, args) { * - `passReqToCallback` (1) Required to set true if you want to use the `function(req, token, done)` signature for the verify function, default is false * (2) Description: * Set `passReqToCallback` to true use the `function(req, token, done)` signature for the verify function - * Set `passReqToCallback` to false use the `function(token, done)` signature for the verify function + * Set `passReqToCallback` to false use the `function(token, done)` signature for the verify function * * - `useCookieInsteadOfSession` * (1) Required to set true if you don't want to use session. Default value is false. * (2) Description: - * Passport-azure-ad needs to save state and nonce somewhere for validation purpose. If this option is set true, it will encrypt + * Passport-azure-ad needs to save state and nonce somewhere for validation purpose. If this option is set true, it will encrypt * state and nonce and put them into cookie. If this option is false, we save state and nonce in session. * * - `cookieEncryptionKeys` * (1) Required if `useCookieInsteadOfSession` is true. * (2) Description: * This must be an array of key items. Each key item has the form { key: '...', iv: '...' }, where key is any string of length 32, - * and iv is any string of length 12. + * and iv is any string of length 12. * We always use the first key item with AES-256-GCM algorithm to encrypt cookie, but we will try every key item when we decrypt * cookie. This helps when you want to do key roll over. * @@ -259,19 +259,19 @@ function onProfileLoaded(strategy, args) { * (2) must be a string or an array of strings * (3) Description: * list of scope values indicating the required scope of the access token for accessing the requested - * resource. Ex: ['email', 'profile']. + * resource. Ex: ['email', 'profile']. * We send 'openid' by default. For B2C, we also send 'offline_access' (to get refresh_token) and * clientID (to get access_token) by default. * * - `loggingLevel` (1) Optional * (2) must be 'info', 'warn', 'error' - * (3) Description: - * logging level + * (3) Description: + * logging level * * - `loggingNoPII` (1) Optional, default value is true * (2) Description: * If this is set to true, no personal information such as tokens and claims will be logged. - * + * * - `nonceLifetime` (1) Optional * (2) must be a positive integer * (3) Description: @@ -294,7 +294,7 @@ function onProfileLoaded(strategy, args) { * clientID: config.creds.clientID, * responseType: config.creds.responseType, * responseMode: config.creds.responseMode - * redirectUrl: config.creds.redirectUrl, + * redirectUrl: config.creds.redirectUrl, * allowHttpForRedirectUrl: config.creds.allowHttpForRedirectUrl, * clientSecret: config.creds.clientSecret, * thumbprint: config.creds.thumbprint, @@ -333,8 +333,8 @@ function Strategy(options, verify) { * Caution when you want to change these values in the member functions of * Strategy, don't use `this`, since `this` points to a subclass of `Strategy`. * To get `Strategy`, use Object.getPrototypeOf(this). - * - * More comments at the beginning of `Strategy.prototype.authenticate`. + * + * More comments at the beginning of `Strategy.prototype.authenticate`. */ this._options = options; this.name = 'azuread-openidconnect'; @@ -350,7 +350,7 @@ function Strategy(options, verify) { if (!this._useCookieInsteadOfSession) { // For each microsoft login page tab, we generate a {state, nonce, policy, timeStamp} tuple, - // normally user won't keep opening microsoft login page in new tabs without putting their + // normally user won't keep opening microsoft login page in new tabs without putting their // password for more than 10 tabs, so we only keep the most recent 10 tuples in session. // The lifetime of each tuple is 60 minutes or user specified. this._sessionContentHandler = new SessionContentHandler(options.nonceMaxAmount || NONCE_MAX_AMOUNT, options.nonceLifetime || NONCE_LIFE_TIME); @@ -360,25 +360,25 @@ function Strategy(options, verify) { /* When a user is authenticated for the first time, passport adds a new field * to req.session called 'passport', and puts a 'user' property inside (or your - * choice of field name and property name if you change passport._key and - * passport._userProperty values). req.session['passport']['user'] is usually + * choice of field name and property name if you change passport._key and + * passport._userProperty values). req.session['passport']['user'] is usually * user_id (or something similar) of the authenticated user to reduce the size * of session. When the user logs out, req.session['passport']['user'] will be * destroyed. Any request between login (when authenticated for the first time) * and logout will have the 'user_id' in req.session['passport']['user'], so * passport can fetch it, find the user object in database and put the user - * object into a new field: req.user. Then the subsequent middlewares and the - * app can use the user object. This is how passport keeps user authenticated. + * object into a new field: req.user. Then the subsequent middlewares and the + * app can use the user object. This is how passport keeps user authenticated. * * For state validation, we also take advantage of req.session. we create a new - * field: req.session[sessionKey], where the sessionKey is our choice (in fact, + * field: req.session[sessionKey], where the sessionKey is our choice (in fact, * this._key, see below). When we send a request with state, we put state into * req.session[sessionKey].state; when we expect a request with state in query - * or body, we compare the state in query/body with the one stored in + * or body, we compare the state in query/body with the one stored in * req.session[sessionKey].state, and then destroy req.session[sessionKey].state. * User can provide a state by using `authenticate(Strategy, {state: 'xxx'})`, or * one will be generated automatically. This is essentially how passport-oauth2 - * library does the state validation, and we use the same way in our library. + * library does the state validation, and we use the same way in our library. * * request structure will look like the following. In real request some fields * might not be there depending on the purpose of the request. @@ -386,7 +386,7 @@ function Strategy(options, verify) { * request ---|--- sessionID * |--- session --- |--- ... * | |--- 'passport' ---| --- 'user': 'user_id etc' - * | |--- sessionKey---| --- state: 'xxx' + * | |--- sessionKey---| --- state: 'xxx' * |--- ... * |--- 'user': full user info */ @@ -402,7 +402,7 @@ function Strategy(options, verify) { if (options.loggingLevel) { log.levels('console', options.loggingLevel); } this.log = log; - if (options.loggingNoPII != false) + if (options.loggingNoPII != false) options.loggingNoPII = true; // clock skew. Must be a postive integer @@ -427,7 +427,7 @@ function Strategy(options, verify) { // check if we are using the common endpoint options.isCommonEndpoint = (options.identityMetadata.indexOf('/common/') != -1); - // isB2C is false by default + // isB2C is false by default if (options.isB2C !== true) options.isB2C = false; @@ -444,10 +444,10 @@ function Strategy(options, verify) { * Take care of issuer and audience * (1) We use user provided `issuer`, and the issuer value from metadata if the metadata * comes from tenant-specific endpoint (in other words, either the identityMetadata - * is tenant-specific, or it is common but you provide tenantIdOrName in - * passport.authenticate). + * is tenant-specific, or it is common but you provide tenantIdOrName in + * passport.authenticate). * - * For common endpoint, if `issuer` is not provided by user, and `tenantIdOrName` is + * For common endpoint, if `issuer` is not provided by user, and `tenantIdOrName` is * not used in passport.authenticate, then we don't know the issuer, and `validateIssuer` * must be set to false * (2) `validateIssuer` is true by default. we validate issuer unless validateIssuer is set false @@ -494,7 +494,7 @@ function Strategy(options, verify) { options.responseType = 'code id_token'; var itemsToValidate = {}; - aadutils.copyObjectFields(options, itemsToValidate, ['clientID', 'redirectUrl', 'responseType', 'responseMode', 'identityMetadata']); + aadutils.copyObjectFields(options, itemsToValidate, ['clientID', 'redirectUrl', 'responseType', 'responseMode', 'identityMetadata']); var validatorConfiguration = { clientID: Validator.isNonEmpty, @@ -502,7 +502,7 @@ function Strategy(options, verify) { responseMode: Validator.isModeLegal, identityMetadata: Validator.isHttpsURL }; - + // redirectUrl is https by default if (options.allowHttpForRedirectUrl === true) validatorConfiguration.redirectUrl = Validator.isURL; @@ -544,23 +544,23 @@ util.inherits(Strategy, passport.Strategy); * @api protected */ Strategy.prototype.authenticate = function authenticateStrategy(req, options) { - /* + /* * We should be careful using 'this'. Avoid the usage like `this.xxx = ...` * unless you know what you are doing. * - * In the passport source code + * In the passport source code * (https://github.com/jaredhanson/passport/blob/master/lib/middleware/authenticate.js) * when it attempts to call the `oidcstrategy.authenticate` function, passport - * creates an instance inherting oidcstrategy and then calls `instance.authenticate`. + * creates an instance inherting oidcstrategy and then calls `instance.authenticate`. * Therefore, when we come here, `this` is the instance, its prototype is the * actual oidcstrategy, i.e. the `Strategy`. This means: * (1) `this._options = `, `this._verify = `, etc only adds new fields to the - * instance, it doesn't change the values in oidcstrategy, i.e. `Strategy`. + * instance, it doesn't change the values in oidcstrategy, i.e. `Strategy`. * (2) `this._options`, `this._verify`, etc returns the field in the instance, * and if there is none, returns the field in oidcstrategy, i.e. `strategy`. * (3) each time we call `authenticate`, we will get a brand new instance - * - * If you want to change the values in `Strategy`, use + * + * If you want to change the values in `Strategy`, use * const oidcstrategy = Object.getPrototypeOf(self); * to get the strategy first. * @@ -581,7 +581,7 @@ Strategy.prototype.authenticate = function authenticateStrategy(req, options) { * | -- __proto__: * | -- authenticate: function(req, options) * | -- ... - */ + */ const self = this; var resource = options && options.resourceURL; @@ -595,11 +595,11 @@ Strategy.prototype.authenticate = function authenticateStrategy(req, options) { var response = options && options.response || req.res; // 'params': items we get from the request or metadata, such as id_token, code, policy, metadata, cacheKey, etc - var params = { 'proxy': self._options.proxy, 'tenantIdOrName': tenantIdOrName, 'extraAuthReqQueryParams': extraAuthReqQueryParams, 'extraTokenReqQueryParams': extraTokenReqQueryParams }; + var params = { 'proxy': self._options.proxy, 'tenantIdOrName': tenantIdOrName, 'extraAuthReqQueryParams': extraAuthReqQueryParams, 'extraTokenReqQueryParams': extraTokenReqQueryParams }; // 'oauthConfig': items needed for oauth flow (like redirection, code redemption), such as token_endpoint, userinfo_endpoint, etc var oauthConfig = { 'proxy': self._options.proxy, 'resource': resource, 'customState': customState, 'domain_hint': domain_hint, 'login_hint': login_hint, 'prompt': prompt, 'response': response }; - // 'optionsToValidate': items we need to validate id_token against, such as issuer, audience, etc - var optionsToValidate = {}; + // 'optionsToValidate': items we need to validate id_token against, such as issuer, audience, etc + var optionsToValidate = {}; async.waterfall( [ @@ -618,7 +618,7 @@ Strategy.prototype.authenticate = function authenticateStrategy(req, options) { return self.setOptions(params, oauthConfig, optionsToValidate, next); }, - /***************************************************************************** + /***************************************************************************** * Step 3. Handle the flows *---------------------------------------------------------------------------- * (1) implicit flow (response_type = 'id_token') @@ -627,8 +627,8 @@ Strategy.prototype.authenticate = function authenticateStrategy(req, options) { * This case we get both 'id_token' and 'code' * (3) authorization code flow (response_type = 'code') * This case we get a 'code', we will use it to get 'access_token' and 'id_token' - * (4) for any other request, we will ask for authorization and initialize - * the authorization process + * (4) for any other request, we will ask for authorization and initialize + * the authorization process ****************************************************************************/ (next) => { if (params.err) { @@ -662,8 +662,8 @@ Strategy.prototype.authenticate = function authenticateStrategy(req, options) { /** * Collect information from the request, for instance, code, err, id_token etc * - * @param {Object} params - * @param {Object} req + * @param {Object} params + * @param {Object} req * @param {Object} next */ Strategy.prototype.collectInfoFromReq = function(params, req, next, response) { @@ -696,7 +696,7 @@ Strategy.prototype.collectInfoFromReq = function(params, req, next, response) { params.code = source.code; params.state = source.state; if (source.state && source.state.length >= 38) { - // the random generated state always has 32 characters. This state is longer than 32 + // the random generated state always has 32 characters. This state is longer than 32 // so it must be a custom state. We added 'CUSTOM' prefix and a random 32 byte long // string in front of the original custom state, now we change it back. if (!source.state.startsWith('CUSTOM')) @@ -709,7 +709,7 @@ Strategy.prototype.collectInfoFromReq = function(params, req, next, response) { // ------------------------------------------------------------------------- // If we received code, id_token or err, we must have received state, now we // find the state/nonce/policy tuple from session. - // If we received none of them, find policy in query + // If we received none of them, find policy in query // ------------------------------------------------------------------------- if (params.id_token || params.code || params.err) { if (!params.state) @@ -735,7 +735,7 @@ Strategy.prototype.collectInfoFromReq = function(params, req, next, response) { log.info('user provided tenantIdOrName is ignored for redirectUrl, we will use the one stored in session'); else log.info(`user provided tenantIdOrName '${params.tenantIdOrName}' is ignored for redirectUrl, we will use the one stored in session`); - } + } params.tenantIdOrName = tuple['tenantIdOrName']; } else { params.policy = req.query.p ? req.query.p.toLowerCase() : null; @@ -750,7 +750,7 @@ Strategy.prototype.collectInfoFromReq = function(params, req, next, response) { params.tenantIdOrName = null; } - // if we are using the common endpoint and we want to validate issuer, then user has to + // if we are using the common endpoint and we want to validate issuer, then user has to // provide issuer in config, or provide tenant id or name using tenantIdOrName option in // passport.authenticate. Otherwise we won't know the issuer. if (self._options.isCommonEndpoint && self._options.validateIssuer && @@ -765,10 +765,10 @@ Strategy.prototype.collectInfoFromReq = function(params, req, next, response) { return next(new Error('In collectInfoFromReq: we are using common endpoint for B2C but tenantIdOrName is not provided')); // ------------------------------------------------------------------------- - // calculate metadataUrl, create a cachekey and an Metadata object instance - // we will fetch the metadata, save it into the object using the cachekey - // ------------------------------------------------------------------------- - var metadataUrl = self._options.identityMetadata; + // calculate metadataUrl, create a cachekey and an Metadata object instance + // we will fetch the metadata, save it into the object using the cachekey + // ------------------------------------------------------------------------- + var metadataUrl = self._options.identityMetadata; // if we are using common endpoint and we are given the tenantIdOrName, let's replace it if (self._options.isCommonEndpoint && params.tenantIdOrName) { @@ -801,7 +801,7 @@ Strategy.prototype.collectInfoFromReq = function(params, req, next, response) { * @param {Object} params -- parameters we get from the request * @param {Object} oauthConfig -- the items we need for oauth flow * @param {Object} optionsToValidate -- the items we need to validate id_token - * @param {Function} done -- the callback + * @param {Function} done -- the callback */ Strategy.prototype.setOptions = function setOptions(params, oauthConfig, optionsToValidate, done) { const self = this; @@ -814,7 +814,7 @@ Strategy.prototype.setOptions = function setOptions(params, oauthConfig, options memoryCache.wrap(params.cachekey, (cacheCallback) => { params.metadata.fetch((fetchMetadataError) => { if (fetchMetadataError) { - return cacheCallback(fetchMetadataError); + return cacheCallback(fetchMetadataError); } return cacheCallback(null, params.metadata); }); @@ -828,7 +828,7 @@ Strategy.prototype.setOptions = function setOptions(params, oauthConfig, options if (!metadata.oidc) return next(new Error('In setOptions: failed to load metadata')); params.metadata = metadata; - + // copy the fields needed into 'oauthConfig' aadutils.copyObjectFields(metadata.oidc, oauthConfig, ['authorization_endpoint', 'token_endpoint', 'userinfo_endpoint']); aadutils.copyObjectFields(self._options, oauthConfig, ['clientID', 'clientSecret', 'privatePEMKey', 'thumbprint', 'responseType', 'responseMode', 'scope', 'redirectUrl']); @@ -854,7 +854,7 @@ Strategy.prototype.setOptions = function setOptions(params, oauthConfig, options // for B2C, verify the endpoints in oauthConfig has the correct policy if (self._options.isB2C){ var policyChecker = (endpoint, policy) => { - var u = {}; + var u = {}; try { u = url.parse(endpoint); } catch (ex) { @@ -867,13 +867,13 @@ Strategy.prototype.setOptions = function setOptions(params, oauthConfig, options return next(new Error('invalid policy')); else return next(new Error(`policy in ${oauthConfig.authorization_endpoint} should be ${params.policy}`)); - } + } if (!policyChecker(oauthConfig.token_endpoint, params.policy)) { if (self._options.loggingNoPII) return next(new Error('invalid policy')); else return next(new Error(`policy in ${oauthConfig.token_endpoint} should be ${params.policy}`)); - } + } } return next(null, metadata); @@ -889,7 +889,7 @@ Strategy.prototype.setOptions = function setOptions(params, oauthConfig, options return next(null); // set items from self._options - aadutils.copyObjectFields(self._options, optionsToValidate, + aadutils.copyObjectFields(self._options, optionsToValidate, ['validateIssuer', 'audience', 'allowMultiAudiencesInToken', 'ignoreExpiration', 'allowMultiAudiencesInToken']); // algorithms @@ -1041,7 +1041,7 @@ Strategy.prototype._validateResponse = function validateResponse(params, options // check c_hash if (jwtClaims.c_hash) { // checkHashValueRS256 checks if code is null, so we don't bother here - if (!aadutils.checkHashValueRS256(code, jwtClaims.c_hash)) + if (!aadutils.checkHashValueRS256(code, jwtClaims.c_hash)) return next(new Error("In _validateResponse: invalid c_hash")); } @@ -1062,7 +1062,7 @@ Strategy.prototype._validateResponse = function validateResponse(params, options /** * handle error response * - * @params {String} err + * @params {String} err * @params {String} err_description * @params {Function} next -- callback to pass error to async.waterfall */ @@ -1073,9 +1073,9 @@ Strategy.prototype._errorResponseHandler = function errorResponseHandler(err, er if (err_description && !self._options.loggingNoPII) log.info('Error description received in the response was: ', err_description); - // Unfortunately, we cannot return the 'error description' to the user, since + // Unfortunately, we cannot return the 'error description' to the user, since // it goes to http header by default and it usually contains characters that - // http header doesn't like, which causes the program to crash. + // http header doesn't like, which causes the program to crash. return next(new Error(err)); }; @@ -1083,7 +1083,7 @@ Strategy.prototype._errorResponseHandler = function errorResponseHandler(err, er * handle the response where we only get 'id_token' in the response * * @params {Object} params - * @params {Object} optionsToValidate + * @params {Object} optionsToValidate * @params {Object} req * @params {Function} next -- callback to pass error to async.waterfall */ @@ -1125,14 +1125,14 @@ Strategy.prototype._implicitFlowHandler = function implicitFlowHandler(params, o * * @params {Object} params * @params {Object} oauthConfig - * @params {Object} optionsToValidate + * @params {Object} optionsToValidate * @params {Object} req * @params {Function} next -- callback to pass error to async.waterfall */ Strategy.prototype._hybridFlowHandler = function hybridFlowHandler(params, oauthConfig, optionsToValidate, req, next) { /* we will do the following things in order * (1) validate the id_token and the code - * (2) if there is no userinfo token needed (or ignored if using AAD v2 ), we use + * (2) if there is no userinfo token needed (or ignored if using AAD v2 ), we use * the claims in id_token for user's profile * (3) if userinfo token is needed, we will use the 'code' and the authorization code flow */ @@ -1143,7 +1143,7 @@ Strategy.prototype._hybridFlowHandler = function hybridFlowHandler(params, oauth else log.info('entering Strategy.prototype._hybridFlowHandler, received code: ' + params.code + ', received id_token: ' + params.id_token); - // save nonce, since if we use the authorization code flow later, we have to check + // save nonce, since if we use the authorization code flow later, we have to check // nonce again. // validate the id_token and the code @@ -1166,13 +1166,13 @@ Strategy.prototype._hybridFlowHandler = function hybridFlowHandler(params, oauth * * @params {Object} params * @params {Object} oauthConfig - * @params {Object} optionsToValidate + * @params {Object} optionsToValidate * @params {Object} req * @params {Function} next -- callback to pass error to async.waterfall - * // the following are required if you used 'code id_token' flow then call this function to - * // redeem the code for another id_token from the token endpoint. iss and sub are those + * // the following are required if you used 'code id_token' flow then call this function to + * // redeem the code for another id_token from the token endpoint. iss and sub are those * // in the id_token from authorization endpoint, and they should match those in the id_token - * // from the token endpoint + * // from the token endpoint * @params {String} iss * @params {String} sub */ @@ -1201,24 +1201,24 @@ Strategy.prototype._authCodeFlowHandler = function authCodeFlowHandler(params, o else return next(new Error(`In _authCodeFlowHandler: failed to redeem authorization code: ${aadutils.getErrorMessage(getOAuthAccessTokenError)}`)); } - + var access_token = items.access_token; var refresh_token = items.refresh_token; // id_token should be present if (!items.id_token) return next(new Error('In _authCodeFlowHandler: id_token is not received')); - + // if we get access token, we must have token_type as well if (items.access_token && !items.token_type) return next(new Error('In _authCodeFlowHandler: token_type is missing')); - + // token_type must be 'Bearer' ignoring the case if (items.token_type && items.token_type.toLowerCase() !== 'bearer') { log.info('token_type received is: ', items.token_type); return next(new Error(`In _authCodeFlowHandler: token_type received is not 'Bearer' ignoring the case`)); } - + if (!self._options.loggingNoPII) { log.info('received id_token: ', items.id_token); log.info('received access_token: ', access_token); @@ -1239,9 +1239,9 @@ Strategy.prototype._authCodeFlowHandler = function authCodeFlowHandler(params, o const sub = jwtClaims.sub; const iss = jwtClaims.iss; - + // load the userinfo, if this is not v2 and if we don't specify the resource - // for v1 if we don't specify the resource, then the access_token we got is for userinfo endpoint, so we can use it to get userinfo + // for v1 if we don't specify the resource, then the access_token we got is for userinfo endpoint, so we can use it to get userinfo if (!self._options._isV2 && !params.resource) { // make sure we get an access_token if (!access_token) @@ -1260,7 +1260,7 @@ Strategy.prototype._authCodeFlowHandler = function authCodeFlowHandler(params, o parsedUrl.query.schema = 'openid'; delete parsedUrl.search; // delete operations are slow; should we rather just overwrite it with {} const userInfoURL = url.format(parsedUrl); - + // ask oauth2 to use authorization header to bearer access token var oauth2 = createOauth2Instance(oauthConfig); oauth2.useAuthorizationHeaderforGET(true); @@ -1270,7 +1270,7 @@ Strategy.prototype._authCodeFlowHandler = function authCodeFlowHandler(params, o return next(new Error('In _authCodeFlowHandler: failed to fetch user profile')); else return next(new Error(`In _authCodeFlowHandler: failed to fetch user profile: ${aadutils.getErrorMessage(getUserInfoError)}`)); - } + } if (self._options.loggingNoPII) log.info('Profile is loaded from MS identity'); @@ -1375,7 +1375,7 @@ Strategy.prototype._flowInitializationHandler = function flowInitializationHandl return next(new Error('In _flowInitializationHandler: the given policy in the request is invalid')); else return next(new Error(`In _flowInitializationHandler: the given policy ${policy} given in the request is invalid`)); - } + } } // add state/nonce/policy/timeStamp tuple to session or cookie @@ -1401,7 +1401,7 @@ Strategy.prototype._flowInitializationHandler = function flowInitializationHandl // expiration when we set cookie self._cookieContentHandler.add(req, oauthConfig.response, tuple); } - + // add scope params.scope = oauthConfig.scope; @@ -1453,7 +1453,7 @@ Strategy.prototype._getAccessTokenBySecretOrAssertion = function getAccessTokenB log.info('In _getAccessTokenBySecretOrAssertion: we are using client secret'); } else { // otherwise generate a client assertion - post_params['client_assertion_type'] = CONSTANTS.CLIENT_ASSERTION_TYPE; + post_params['client_assertion_type'] = CONSTANTS.CLIENT_ASSERTION_TYPE; jwt.generateClientAssertion(oauthConfig.clientID, oauthConfig.token_endpoint, oauthConfig.privatePEMKey, oauthConfig.thumbprint, (err, assertion) => { @@ -1470,7 +1470,7 @@ Strategy.prototype._getAccessTokenBySecretOrAssertion = function getAccessTokenB }; var post_data = querystring.stringify(post_params); - + var oauth2 = createOauth2Instance(oauthConfig); oauth2._request('POST', oauthConfig.token_endpoint, post_headers, post_data, null, (error, data, response) => { @@ -1484,7 +1484,7 @@ Strategy.prototype._getAccessTokenBySecretOrAssertion = function getAccessTokenB } callback(null, results); } - }); + }); }; /** @@ -1518,7 +1518,11 @@ var createOauth2Instance = function(oauthConfig) { libraryVersionParameterName : libraryVersion} // customHeaders ); - oauth2.setAgent(new HttpsProxyAgent(oauthConfig.proxy)); + if (oauthConfig.proxy) { + // if user has specified proxy settings instantiate agent + oauth2.setAgent(new HttpsProxyAgent(oauthConfig.proxy)); + } + return oauth2; }; diff --git a/package.json b/package.json index 950f4b41..962f2256 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,14 @@ "grunt-contrib-nodeunit": "^1.0.0", "grunt-mocha-test": "^0.12.7", "mocha": "^3.0.2", - "nodeunit": "^0.9.1" + "nodeunit": "^0.11.3" }, "dependencies": { "async": "^1.5.2", "base64url": "^3.0.0", "bunyan": "^1.8.0", "cache-manager": "^2.0.0", - "https-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.2", "jwk-to-pem": "^1.2.6", "jws": "^3.1.3", "lodash": "^4.11.2",