passport-azure-ad
Advanced tools
Comparing version 4.3.1-beta.0 to 4.3.1
<a name="4.0.0"></a> | ||
# 4.3.1 | ||
## Bugs | ||
- Replace `jwk-to-pem` with `node-jose` to remove dependency on `elliptic`: #3868 | ||
- Update `https-proxy-agent` to v5: #3996 | ||
# 4.3.0 | ||
@@ -4,0 +10,0 @@ |
@@ -1,34 +0,17 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the "Software"), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
const base64url = require('base64url'); | ||
const crypto = require('crypto'); | ||
const util = require('util'); | ||
"use strict"; | ||
exports.getLibraryProduct = () => { return 'passport-azure-ad' }; | ||
const base64url = require("base64url"); | ||
const crypto = require("crypto"); | ||
const util = require("util"); | ||
exports.getLibraryProduct = () => { return "passport-azure-ad" }; | ||
exports.getLibraryVersionParameterName = () => { return "x-client-Ver" }; | ||
exports.getLibraryProductParameterName = () => { return 'x-client-SKU' }; | ||
exports.getLibraryProductParameterName = () => { return "x-client-SKU" }; | ||
exports.getLibraryVersion = () => { | ||
return '4.0.0'; | ||
return "4.3.0"; | ||
}; | ||
@@ -67,5 +50,5 @@ | ||
const headers = req.headers; | ||
const protocol = (req.connection.encrypted || req.headers['x-forwarded-proto'] === 'https') ? 'https' : 'http'; | ||
const protocol = (req.connection.encrypted || req.headers["x-forwarded-proto"] === "https") ? "https" : "http"; | ||
const host = headers.host; | ||
const path = req.url || ''; | ||
const path = req.url || ""; | ||
return `${protocol}://${host}${path}`; | ||
@@ -108,3 +91,3 @@ }; | ||
exports.uid = (len) => { | ||
var bytes = crypto.randomBytes(Math.ceil(len * 3 / 4)); | ||
const bytes = crypto.randomBytes(Math.ceil(len * 3 / 4)); | ||
return base64url.encode(bytes).slice(0,len); | ||
@@ -115,3 +98,3 @@ }; | ||
const msb = hexStr[0]; | ||
if (msb < '0' || msb > '7') { | ||
if (msb < "0" || msb > "7") { | ||
return `00${hexStr}`; | ||
@@ -130,5 +113,7 @@ } | ||
// encode ASN.1 DER length field | ||
// if <=127, short form | ||
// if >=128, long form | ||
/* | ||
* encode ASN.1 DER length field | ||
* if <=127, short form | ||
* if >=128, long form | ||
*/ | ||
function encodeLengthHex(n) { | ||
@@ -145,7 +130,7 @@ if (n <= 127) { | ||
exports.rsaPublicKeyPem = (modulusB64, exponentB64) => { | ||
const modulus = new Buffer(modulusB64, 'base64'); | ||
const exponent = new Buffer(exponentB64, 'base64'); | ||
const modulus = new Buffer(modulusB64, "base64"); // eslint-disable-line security/detect-new-buffer -- Legacy code, Buffer needed | ||
const exponent = new Buffer(exponentB64, "base64"); // eslint-disable-line security/detect-new-buffer -- Legacy code, Buffer needed | ||
const modulusHex = prepadSigned(modulus.toString('hex')); | ||
const exponentHex = prepadSigned(exponent.toString('hex')); | ||
const modulusHex = prepadSigned(modulus.toString("hex")); | ||
const exponentHex = prepadSigned(exponent.toString("hex")); | ||
@@ -164,5 +149,5 @@ const modlen = modulusHex.length / 2; | ||
const derB64 = new Buffer(encodedPubkey, 'hex').toString('base64'); | ||
const derB64 = new Buffer(encodedPubkey, "hex").toString("base64"); // eslint-disable-line security/detect-new-buffer -- Legacy code, Buffer needed | ||
const pem = `-----BEGIN RSA PUBLIC KEY-----\n${derB64.match(/.{1,64}/g).join('\n')}\n-----END RSA PUBLIC KEY-----\n`; | ||
const pem = `-----BEGIN RSA PUBLIC KEY-----\n${derB64.match(/.{1,64}/g).join("\n")}\n-----END RSA PUBLIC KEY-----\n`; | ||
@@ -172,5 +157,7 @@ return pem; | ||
// used for c_hash and at_hash validation | ||
// case (1): content = access_token, hashProvided = at_hash | ||
// case (2): content = code, hashProvided = c_hash | ||
/* | ||
* used for c_hash and at_hash validation | ||
* case (1): content = access_token, hashProvided = at_hash | ||
* case (2): content = code, hashProvided = c_hash | ||
*/ | ||
exports.checkHashValueRS256 = (content, hashProvided) => { | ||
@@ -181,11 +168,11 @@ if (!content) | ||
// step 1. hash the content | ||
var digest = crypto.createHash('sha256').update(content, 'ascii').digest(); | ||
const digest = crypto.createHash("sha256").update(content, "ascii").digest(); | ||
// step2. take the first half of the digest, and save it in a buffer | ||
var buffer = new Buffer(digest.length/2); | ||
for (var i = 0; i < buffer.length; i++) | ||
const buffer = new Buffer(digest.length/2); // eslint-disable-line security/detect-new-buffer -- Legacy code, Buffer needed | ||
for (let i = 0; i < buffer.length; i++) | ||
buffer[i] = digest[i]; | ||
// step 3. base64url encode the buffer to get the hash | ||
var hashComputed = base64url(buffer); | ||
const hashComputed = base64url(buffer); | ||
@@ -195,5 +182,7 @@ return (hashProvided === hashComputed); | ||
// This function is used for handling the tuples containing nonce/state/policy/timeStamp in session | ||
// remove the additional tuples from array starting from the oldest ones | ||
// remove expired tuples in array | ||
/* | ||
* This function is used for handling the tuples containing nonce/state/policy/timeStamp in session | ||
* remove the additional tuples from array starting from the oldest ones | ||
* remove expired tuples in array | ||
*/ | ||
exports.processArray = function(array, maxAmount, maxAge) { | ||
@@ -205,5 +194,5 @@ // remove the additional tuples, start from the oldest ones | ||
// count the number of those already expired | ||
var count = 0; | ||
for (var i = 0; i < array.length; i++) { | ||
var tuple = array[i]; | ||
let count = 0; | ||
for (let i = 0; i < array.length; i++) { | ||
const tuple = array[i]; | ||
if (tuple.timeStamp + maxAge * 1000 <= Date.now()) | ||
@@ -220,6 +209,8 @@ count++; | ||
// This function is used to find the tuple matching the given state, remove the tuple | ||
// from the array and return the tuple | ||
// @array - array of {state: x, nonce: x, policy: x, timeStamp: x} tuples | ||
// @state - the tuple which matches the given state | ||
/* | ||
* This function is used to find the tuple matching the given state, remove the tuple | ||
* from the array and return the tuple | ||
* @array - array of {state: x, nonce: x, policy: x, timeStamp: x} tuples | ||
* @state - the tuple which matches the given state | ||
*/ | ||
exports.findAndDeleteTupleByState = (array, state) => { | ||
@@ -229,5 +220,5 @@ if (!array) | ||
for (var i = 0; i < array.length; i++) { | ||
var tuple = array[i]; | ||
if (tuple['state'] === state) { | ||
for (let i = 0; i < array.length; i++) { | ||
const tuple = array[i]; | ||
if (tuple["state"] === state) { | ||
// remove the tuple from the array | ||
@@ -247,3 +238,3 @@ array.splice(i, 1); | ||
for (var i = 0; i < fields.length; i++) | ||
for (let i = 0; i < fields.length; i++) | ||
dest[fields[i]] = source[fields[i]]; | ||
@@ -253,3 +244,3 @@ }; | ||
exports.getErrorMessage = (err) => { | ||
if (typeof err === 'string') | ||
if (typeof err === "string") | ||
return err; | ||
@@ -260,3 +251,3 @@ if (err instanceof Error) | ||
// if not string or Error, we try to stringify it | ||
var str; | ||
let str; | ||
try { | ||
@@ -271,31 +262,36 @@ str = JSON.stringify(err); | ||
exports.concatUrl = (url, rest) => { | ||
if (typeof rest === 'string' || rest instanceof String) { | ||
rest = [rest]; | ||
let validRest; | ||
if (typeof rest === "string" || rest instanceof String) { | ||
validRest = [rest]; | ||
} else { | ||
validRest = rest; | ||
} | ||
if (!url) { | ||
return `?${rest.join('&')}`; | ||
return `?${validRest.join("&")}`; | ||
} | ||
var hasParam = url.indexOf('?') !== -1; | ||
return rest ? url.concat(hasParam ? '&' : '?').concat(rest.join('&')) : url; | ||
const hasParam = url.indexOf("?") !== -1; | ||
return validRest ? url.concat(hasParam ? "&" : "?").concat(validRest.join("&")) : url; | ||
}; | ||
// This is a list maintained by the AAD server team, will need to keep an eye on this | ||
// as things change. Not ideal, but it is what it is | ||
// in general, a change like this (adding a new cookie attribute) | ||
// should be backward compatible as RFC 6265 specifies that browsers should | ||
// ignore unknown cookie attributes. However, for the specific case of the | ||
// SameSite attribute, some browsers incorrectly implemented the attribute or | ||
// implemented an earlier draft which had contradictory behavior. | ||
// For these browsers which attempted to support SameSite, | ||
// but have bugs in their support, we want to omit the SameSite=None attribute. | ||
// See Chromium official guidance here: https://www.chromium.org/updates/same-site/incompatible-clients | ||
/* | ||
* This is a list maintained by the AAD server team. | ||
* In general, a change like this (adding a new cookie attribute) | ||
* should be backward compatible as RFC 6265 specifies that browsers should | ||
* ignore unknown cookie attributes. However, for the specific case of the | ||
* SameSite attribute, some browsers incorrectly implemented the attribute or | ||
* implemented an earlier draft which had contradictory behavior. | ||
* For these browsers which attempted to support SameSite, | ||
* but have bugs in their support, we want to omit the SameSite=None attribute. | ||
* See Chromium official guidance here: https://www.chromium.org/updates/same-site/incompatible-clients | ||
*/ | ||
exports.sameSiteNotAllowed = userAgent => { | ||
// Cover all iOS based browsers here. This includes: | ||
// - Safari on iOS 12 for iPhone, iPod Touch, iPad | ||
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad | ||
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad | ||
// All of which are broken by SameSite=None, because they use the iOS networking stack | ||
/* | ||
* Cover all iOS based browsers here. This includes: | ||
* - Safari on iOS 12 for iPhone, iPod Touch, iPad | ||
* - WkWebview on iOS 12 for iPhone, iPod Touch, iPad | ||
* - Chrome on iOS 12 for iPhone, iPod Touch, iPad | ||
* All of which are broken by SameSite=None, because they use the iOS networking stack | ||
*/ | ||
if (userAgent.includes("CPU iPhone OS 12") || userAgent.includes("iPad; CPU OS 12")) { | ||
@@ -305,9 +301,11 @@ return true; | ||
// Cover Mac OS X based browsers that use the Mac OS networking stack. This includes: | ||
// - Safari on Mac OS X | ||
// - Internal browser on Mac OS X | ||
// This does not include: | ||
// - Chrome on Mac OS X | ||
// - Chromium on Mac OS X | ||
// Because they do not use the Mac OS networking stack. | ||
/* | ||
* Cover Mac OS X based browsers that use the Mac OS networking stack. This includes: | ||
* - Safari on Mac OS X | ||
* - Internal browser on Mac OS X | ||
* This does not include: | ||
* - Chrome on Mac OS X | ||
* - Chromium on Mac OS X | ||
* Because they do not use the Mac OS networking stack. | ||
*/ | ||
if (userAgent.includes("Macintosh; Intel Mac OS X 10_14") && !userAgent.includes("Chrome/") && !userAgent.includes("Chromium")) { | ||
@@ -317,5 +315,7 @@ return true; | ||
// Cover Chrome 50-69, because some versions are broken by SameSite=None, and none in this range require it. | ||
// Note: this covers some pre-Chromium Edge versions, but pre-Chromim Edge does not require SameSite=None, so this is fine. | ||
// Note: this regex applies to Windows, Mac OS X, and Linux, deliberately. | ||
/* | ||
* Cover Chrome 50-69, because some versions are broken by SameSite=None, and none in this range require it. | ||
* Note: this covers some pre-Chromium Edge versions, but pre-Chromim Edge does not require SameSite=None, so this is fine. | ||
* Note: this regex applies to Windows, Mac OS X, and Linux, deliberately. | ||
*/ | ||
if (userAgent.includes("Chrome/5") || userAgent.includes("Chrome/6")) { | ||
@@ -325,4 +325,6 @@ return true; | ||
// Unreal Engine runs Chromium 59, but does not advertise as Chrome until 4.23. Treat versions of Unreal | ||
// that don't specify their Chrome version as lacking support for SameSite=None. | ||
/* | ||
* Unreal Engine runs Chromium 59, but does not advertise as Chrome until 4.23. Treat versions of Unreal | ||
* that don't specify their Chrome version as lacking support for SameSite=None. | ||
*/ | ||
if (userAgent.includes("UnrealEngine") && !userAgent.includes("Chrome")) { | ||
@@ -332,5 +334,7 @@ return true; | ||
// UCBrowser < 12.13.2 ignores Set-Cookie headers with SameSite=None. | ||
// NB: this rule isn't complete - you need regex to make a complete rule. | ||
// See: https://www.chromium.org/updates/same-site/incompatible-clients | ||
/* | ||
* UCBrowser < 12.13.2 ignores Set-Cookie headers with SameSite=None. | ||
* NB: this rule isn't complete - you need regex to make a complete rule. | ||
* See: https://www.chromium.org/updates/same-site/incompatible-clients | ||
*/ | ||
if (userAgent.includes("UCBrowser/12") || userAgent.includes("UCBrowser/11")) { | ||
@@ -337,0 +341,0 @@ return true; |
@@ -1,42 +0,25 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the 'Software'), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
/* eslint no-underscore-dangle: 0 */ | ||
const async = require('async'); | ||
const cacheManager = require('cache-manager'); | ||
const jws = require('jws'); | ||
const passport = require('passport'); | ||
const util = require('util'); | ||
const async = require("async"); | ||
const cacheManager = require("cache-manager"); | ||
const jws = require("jws"); | ||
const passport = require("passport"); | ||
const util = require("util"); | ||
const aadutils = require('./aadutils'); | ||
const CONSTANTS = require('./constants'); | ||
const jwt = require('./jsonWebToken'); | ||
const Metadata = require('./metadata').Metadata; | ||
const Log = require('./logging').getLogger; | ||
const UrlValidator = require('valid-url'); | ||
const aadutils = require("./aadutils"); | ||
const CONSTANTS = require("./constants"); | ||
const jwt = require("./jsonWebToken"); | ||
const Metadata = require("./metadata").Metadata; | ||
const Log = require("./logging").getLogger; | ||
const UrlValidator = require("valid-url"); | ||
const log = new Log('AzureAD: Bearer Strategy'); | ||
const memoryCache = cacheManager.caching({ store: 'memory', max: 3600, ttl: 1800 /* seconds */ }); | ||
const log = new Log("AzureAD: Bearer Strategy"); | ||
const memoryCache = cacheManager.caching({ store: "memory", max: 3600, ttl: 1800 /* seconds */ }); | ||
const ttl = 1800; // 30 minutes cache | ||
@@ -69,3 +52,2 @@ | ||
* | ||
* | ||
* Options: | ||
@@ -184,8 +166,8 @@ * | ||
passport.Strategy.call(this); | ||
this.name = 'oauth-bearer'; // Me, a name I call myself. | ||
this.name = "oauth-bearer"; | ||
if (!options) | ||
throw new Error('In BearerStrategy constructor: options is required'); | ||
if (!verifyFn || typeof verifyFn !== 'function') | ||
throw new Error('In BearerStrategy constructor: verifyFn is required and it must be a function'); | ||
throw new Error("In BearerStrategy constructor: options is required"); | ||
if (!verifyFn || typeof verifyFn !== "function") | ||
throw new Error("In BearerStrategy constructor: verifyFn is required and it must be a function"); | ||
@@ -195,9 +177,9 @@ this._verify = verifyFn; | ||
//--------------------------------------------------------------------------- | ||
// Set up the default values | ||
//--------------------------------------------------------------------------- | ||
/* | ||
* Set up the default values | ||
*/ | ||
// clock skew. Must be a postive integer | ||
if (options.clockSkew && (typeof options.clockSkew !== 'number' || options.clockSkew <= 0 || options.clockSkew % 1 !== 0)) | ||
throw new Error('clockSkew must be a positive integer'); | ||
if (options.clockSkew && (typeof options.clockSkew !== "number" || options.clockSkew <= 0 || options.clockSkew % 1 !== 0)) | ||
throw new Error("clockSkew must be a positive integer"); | ||
if (!options.clockSkew) | ||
@@ -218,8 +200,10 @@ options.clockSkew = CONSTANTS.CLOCK_SKEW; | ||
// if options.audience is a string or an array of string, then we use it; | ||
// otherwise we use the clientID | ||
if (options.audience && typeof options.audience === 'string') | ||
/* | ||
* if options.audience is a string or an array of string, then we use it; | ||
* otherwise we use the clientID | ||
*/ | ||
if (options.audience && typeof options.audience === "string") | ||
options.audience = [options.audience]; | ||
else if (!options.audience || !Array.isArray(options.audience) || options.length === 0) | ||
options.audience = [options.clientID, 'spn:' + options.clientID]; | ||
options.audience = [options.clientID, "spn:" + options.clientID]; | ||
@@ -231,3 +215,3 @@ // default value of isB2C is false | ||
// turn issuer into an array | ||
if (options.issuer === '') | ||
if (options.issuer === "") | ||
options.issuer = null; | ||
@@ -239,34 +223,34 @@ if (options.issuer && Array.isArray(options.issuer) && options.issuer.length === 0) | ||
//--------------------------------------------------------------------------- | ||
// validate the things in options | ||
//--------------------------------------------------------------------------- | ||
/* | ||
* validate the things in options | ||
*/ | ||
// clientID should not be empty | ||
if (!options.clientID || options.clientID === '') | ||
throw new Error('In BearerStrategy constructor: clientID cannot be empty'); | ||
if (!options.clientID || options.clientID === "") | ||
throw new Error("In BearerStrategy constructor: clientID cannot be empty"); | ||
// identityMetadata must be https url | ||
if (!options.identityMetadata || !UrlValidator.isHttpsUri(options.identityMetadata)) | ||
throw new Error('In BearerStrategy constructor: identityMetadata must be provided and must be a https url'); | ||
throw new Error("In BearerStrategy constructor: identityMetadata must be provided and must be a https url"); | ||
// if scope is provided, it must be an array | ||
if (options.scope && (!Array.isArray(options.scope) || options.scope.length === 0)) | ||
throw new Error('In BearerStrategy constructor: scope must be a non-empty array'); | ||
throw new Error("In BearerStrategy constructor: scope must be a non-empty array"); | ||
//--------------------------------------------------------------------------- | ||
// treatment of common endpoint and issuer | ||
//--------------------------------------------------------------------------- | ||
/* | ||
* treatment of common endpoint and issuer | ||
*/ | ||
// check if we are using the common endpoint | ||
options._isCommonEndpoint = (options.identityMetadata.indexOf('/common/') != -1); | ||
options._isCommonEndpoint = (options.identityMetadata.indexOf("/common/") !== -1); | ||
// give a warning if user is not validating issuer | ||
if (!options.validateIssuer) | ||
log.warn(`Production environments should always validate the issuer.`); | ||
log.warn("Production environments should always validate the issuer."); | ||
//--------------------------------------------------------------------------- | ||
// B2C. | ||
// (1) policy must be provided and must have the valid prefix | ||
// (2) common endpoint is not supported | ||
//--------------------------------------------------------------------------- | ||
/* | ||
* B2C. | ||
* (1) policy must be provided and must have the valid prefix | ||
* (2) common endpoint is not supported | ||
*/ | ||
@@ -276,13 +260,13 @@ // for B2C, | ||
if (!options.policyName || !CONSTANTS.POLICY_REGEX.test(options.policyName)) | ||
throw new Error('In BearerStrategy constructor: invalid policy for B2C'); | ||
throw new Error("In BearerStrategy constructor: invalid policy for B2C"); | ||
} | ||
// if logging level specified, switch to it. | ||
if (options.loggingLevel) { log.levels('console', options.loggingLevel); } | ||
if (options.loggingLevel) { log.levels("console", options.loggingLevel); } | ||
if (options.loggingNoPII != false) | ||
if (options.loggingNoPII !== false) | ||
options.loggingNoPII = true; | ||
if (options.loggingNoPII) | ||
log.info('In BearerStrategy constructor: strategy created'); | ||
log.info("In BearerStrategy constructor: strategy created"); | ||
else | ||
@@ -306,14 +290,16 @@ log.info(`In BearerStrategy constructor: created strategy with options ${JSON.stringify(options)}`); | ||
if (decoded == null) { | ||
return done(null, false, 'In Strategy.prototype.jwtVerify: Invalid JWT token.'); | ||
if (decoded === null) { | ||
return done(null, false, "In Strategy.prototype.jwtVerify: Invalid JWT token."); | ||
} | ||
if (self._options.loggingNoPII) | ||
log.info('In Strategy.prototype.jwtVerify: token is decoded'); | ||
log.info("In Strategy.prototype.jwtVerify: token is decoded"); | ||
else | ||
log.info('In Strategy.prototype.jwtVerify: token decoded: ', decoded); | ||
log.info("In Strategy.prototype.jwtVerify: token decoded: ", decoded); | ||
// When we generate the PEMkey, there are two different types of token signatures | ||
// we have to validate here. One provides x5t and the other a kid. We need to call | ||
// the right one. | ||
/* | ||
* When we generate the PEMkey, there are two different types of token signatures | ||
* we have to validate here. One provides x5t and the other a kid. We need to call | ||
* the right one. | ||
*/ | ||
try { | ||
@@ -325,12 +311,12 @@ if (decoded.header.x5t) { | ||
} else { | ||
return self.failWithLog('In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate'); | ||
return self.failWithLog("In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate"); | ||
} | ||
} catch (error) { | ||
return self.failWithLog('In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate'); | ||
return self.failWithLog("In Strategy.prototype.jwtVerify: We did not receive a token we know how to validate"); | ||
} | ||
if (self._options.loggingNoPII) | ||
log.info('PEMkey generated'); | ||
log.info("PEMkey generated"); | ||
else | ||
log.info('PEMkey generated: ' + PEMkey); | ||
log.info("PEMkey generated: " + PEMkey); | ||
@@ -342,3 +328,3 @@ jwt.verify(token, PEMkey, optionsToValidate, (err, verifiedToken) => { | ||
else | ||
return self.failWithLog('In Strategy.prototype.jwtVerify: cannot verify token'); | ||
return self.failWithLog("In Strategy.prototype.jwtVerify: cannot verify token"); | ||
} | ||
@@ -349,8 +335,8 @@ | ||
if (!verifiedToken.scp) | ||
return self.failWithLog('In Strategy.prototype.jwtVerify: scope is not found in token'); | ||
return self.failWithLog("In Strategy.prototype.jwtVerify: scope is not found in token"); | ||
// split scope by blanks and remove empty elements in the array | ||
var scopesInToken = verifiedToken.scp.split(/[ ]+/).filter(Boolean); | ||
var hasValidScopeInToken = false; | ||
for (var i = 0; i < scopesInToken.length; i++) { | ||
const scopesInToken = verifiedToken.scp.split(/[ ]+/).filter(Boolean); | ||
let hasValidScopeInToken = false; | ||
for (let i = 0; i < scopesInToken.length; i++) { | ||
if (optionsToValidate.scope.indexOf(scopesInToken[i]) !== -1) { | ||
@@ -364,3 +350,3 @@ hasValidScopeInToken = true; | ||
if (self._options.loggingNoPII) | ||
return self.failWithLog('In Strategy.prototype.jwtVerify: none of the scopes in token is accepted'); | ||
return self.failWithLog("In Strategy.prototype.jwtVerify: none of the scopes in token is accepted"); | ||
else | ||
@@ -372,11 +358,11 @@ return self.failWithLog(`In Strategy.prototype.jwtVerify: none of the scopes '${verifiedToken.scp}' in token is accepted`); | ||
if (self._options.loggingNoPII) | ||
log.info('In Strategy.prototype.jwtVerify: token is verified'); | ||
log.info("In Strategy.prototype.jwtVerify: token is verified"); | ||
else | ||
log.info('In Strategy.prototype.jwtVerify: VerifiedToken: ', verifiedToken); | ||
log.info("In Strategy.prototype.jwtVerify: VerifiedToken: ", verifiedToken); | ||
if (self._options.passReqToCallback) { | ||
log.info('In Strategy.prototype.jwtVerify: We did pass Req back to Callback'); | ||
log.info("In Strategy.prototype.jwtVerify: We did pass Req back to Callback"); | ||
return self._verify(req, verifiedToken, done); | ||
} else { | ||
log.info('In Strategy.prototype.jwtVerify: We did not pass Req back to Callback'); | ||
log.info("In Strategy.prototype.jwtVerify: We did not pass Req back to Callback"); | ||
return self._verify(verifiedToken, done); | ||
@@ -393,10 +379,11 @@ } | ||
const self = this; | ||
var params = {}; | ||
var optionsToValidate = {}; | ||
var tenantIdOrName = options && options.tenantIdOrName; | ||
const params = {}; | ||
const optionsToValidate = {}; | ||
let tenantIdOrName = options && options.tenantIdOrName; | ||
/* Some introduction to async.waterfall (from the following link): | ||
/* | ||
* Some introduction to async.waterfall (from the following link): | ||
* http://stackoverflow.com/questions/28908180/what-is-a-simple-implementation-of-async-waterfall | ||
* | ||
* Runs the tasks array of functions in series, each passing their results | ||
* Runs the tasks array of functions in series, each passing their results | ||
* to the next in the array. However, if any of the tasks pass an error to | ||
@@ -438,3 +425,3 @@ * their own callback, the next function is not executed, and the main callback | ||
if (self._options.loggingNoPII) | ||
log.info('identityMetadata is tenant-specific, so we ignore the provided tenantIdOrName'); | ||
log.info("identityMetadata is tenant-specific, so we ignore the provided tenantIdOrName"); | ||
else | ||
@@ -447,5 +434,5 @@ log.info(`identityMetadata is tenant-specific, so we ignore the tenantIdOrName '${tenantIdOrName}'`); | ||
if (self._options._isCommonEndpoint && tenantIdOrName) { | ||
params.metadataURL = params.metadataURL.replace('/common/', `/${tenantIdOrName}/`); | ||
params.metadataURL = params.metadataURL.replace("/common/", `/${tenantIdOrName}/`); | ||
if (self._options.loggingNoPII) | ||
log.info(`We are replacing 'common' with the provided tenantIdOrName`); | ||
log.info("We are replacing 'common' with the provided tenantIdOrName"); | ||
else | ||
@@ -455,12 +442,14 @@ log.info(`we are replacing 'common' with the tenantIdOrName ${tenantIdOrName}`); | ||
// 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 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 && | ||
(!self._options.issuer && !tenantIdOrName)) | ||
return next(new Error('In passport.authenticate: issuer or tenantIdOrName must be provided in order to validate issuer on common endpoint')); | ||
return next(new Error("In passport.authenticate: issuer or tenantIdOrName must be provided in order to validate issuer on common endpoint")); | ||
// for B2C, if we are using common endpoint, we must have tenantIdOrName provided | ||
if (self._options.isB2C && self._options._isCommonEndpoint && !tenantIdOrName) | ||
return next(new Error('In passport.authenticate: we are using common endpoint for B2C but tenantIdOrName is not provided')); | ||
return next(new Error("In passport.authenticate: we are using common endpoint for B2C but tenantIdOrName is not provided")); | ||
@@ -487,3 +476,3 @@ if (self._options.isB2C) | ||
if (self._options.loggingNoPII) | ||
log.info('In Strategy.prototype.authenticate: received metadata'); | ||
log.info("In Strategy.prototype.authenticate: received metadata"); | ||
else | ||
@@ -518,3 +507,3 @@ log.info(`In Strategy.prototype.authenticate: received metadata: ${JSON.stringify(metadata)}`); | ||
if (self._options.loggingNoPII) | ||
log.info(`In Strategy.prototype.authenticate: we will validate the options`); | ||
log.info("In Strategy.prototype.authenticate: we will validate the options"); | ||
else | ||
@@ -526,13 +515,15 @@ log.info(`In Strategy.prototype.authenticate: we will validate the following options: ${JSON.stringify(optionsToValidate)}`); | ||
// extract the access token from the request, after getting the token, it | ||
// will call `jwtVerify` to verify the token. If token is verified, `jwtVerify` | ||
// will provide the token payload to self._verify function. self._verify is | ||
// provided by the developer, it's up to the developer to decide if the token | ||
// payload is considered authenticated. If authenticated, self._verify will | ||
// provide `user` object (developer's decision of its content) to `verified` | ||
// function here, and the `verified` function does the final work of stuffing | ||
// the `user` obejct into req.user, so the following middleware can use it. | ||
// This is basically how bearerStrategy works. | ||
(next) => { | ||
var token; | ||
/* | ||
* extract the access token from the request, after getting the token, it | ||
* will call `jwtVerify` to verify the token. If token is verified, `jwtVerify` | ||
* will provide the token payload to self._verify function. self._verify is | ||
* provided by the developer, it's up to the developer to decide if the token | ||
* payload is considered authenticated. If authenticated, self._verify will | ||
* provide `user` object (developer's decision of its content) to `verified` | ||
* function here, and the `verified` function does the final work of stuffing | ||
* the `user` obejct into req.user, so the following middleware can use it. | ||
* This is basically how bearerStrategy works. | ||
*/ | ||
(next) => { // eslint-disable-line no-unused-vars -- Next used in async.waterfall | ||
let token; | ||
@@ -542,11 +533,11 @@ // token could be in header or body. query is not supported. | ||
if (req.query && req.query.access_token) | ||
return self.failWithLog('In Strategy.prototype.authenticate: access_token should be passed in request header or body. query is unsupported'); | ||
return self.failWithLog("In Strategy.prototype.authenticate: access_token should be passed in request header or body. query is unsupported"); | ||
if (req.headers && req.headers.authorization) { | ||
var auth_components = req.headers.authorization.split(' '); | ||
if (auth_components.length == 2 &&auth_components[0].toLowerCase() === 'bearer') { | ||
const auth_components = req.headers.authorization.split(" "); | ||
if (auth_components.length === 2 && auth_components[0].toLowerCase() === "bearer") { | ||
token = auth_components[1]; | ||
if (token !== '') { | ||
if (token !== "") { // eslint-disable-line security/detect-possible-timing-attacks -- Timing for comparison to empty string should be the same every time | ||
if (self._options.loggingNoPII) | ||
log.info('In Strategy.prototype.authenticate: access_token is received from request header'); | ||
log.info("In Strategy.prototype.authenticate: access_token is received from request header"); | ||
else | ||
@@ -556,3 +547,3 @@ log.info(`In Strategy.prototype.authenticate: received access_token from request header: ${token}`); | ||
else | ||
return self.failWithLog('In Strategy.prototype.authenticate: missing access_token in the header'); | ||
return self.failWithLog("In Strategy.prototype.authenticate: missing access_token in the header"); | ||
} | ||
@@ -563,7 +554,7 @@ } | ||
if (token) | ||
return self.failWithLog('In Strategy.prototype.authenticate: access_token cannot be passed in both request header and body'); | ||
return self.failWithLog("In Strategy.prototype.authenticate: access_token cannot be passed in both request header and body"); | ||
token = req.body.access_token; | ||
if (token) { | ||
if (self._options.loggingNoPII) | ||
log.info('In Strategy.prototype.authenticate: access_token is received from request body'); | ||
log.info("In Strategy.prototype.authenticate: access_token is received from request body"); | ||
else | ||
@@ -575,3 +566,3 @@ log.info(`In Strategy.prototype.authenticate: received access_token from request body: ${token}`); | ||
if (!token) | ||
return self.failWithLog('token is not found'); | ||
return self.failWithLog("token is not found"); | ||
@@ -583,10 +574,10 @@ function verified(err, user, info) { | ||
if (!user) { | ||
var err_message = 'error: invalid_token'; | ||
if (info && typeof info == 'string') | ||
err_message += ', error description: ' + info; | ||
let err_message = "error: invalid_token"; | ||
if (info && typeof info === "string") | ||
err_message += ", error description: " + info; | ||
else if (info) | ||
err_message += ', error description: ' + JSON.stringify(info); | ||
err_message += ", error description: " + JSON.stringify(info); | ||
if (self._options.loggingNoPII) | ||
return self.failWithLog('error: invalid_token'); | ||
return self.failWithLog("error: invalid_token"); | ||
else | ||
@@ -613,3 +604,3 @@ return self.failWithLog(err_message); | ||
const self = this; | ||
var metadata = new Metadata(params.metadataURL, 'oidc', self._options); | ||
const metadata = new Metadata(params.metadataURL, "oidc", self._options); | ||
@@ -616,0 +607,0 @@ // fetch metadata |
@@ -1,29 +0,11 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the 'Software'), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
// constants that are not strategy specific | ||
var CONSTANTS = {}; | ||
const CONSTANTS = {}; | ||
@@ -35,4 +17,4 @@ CONSTANTS.POLICY_REGEX = /^b2c_1a?_[0-9a-z._-]+$/i; // policy is case insensitive | ||
CONSTANTS.CLIENT_ASSERTION_JWT_LIFETIME = 600; // 10 minutes | ||
CONSTANTS.CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; | ||
CONSTANTS.CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; | ||
module.exports = CONSTANTS; |
@@ -1,29 +0,11 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the "Software"), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use restrict'; | ||
"use restrict"; | ||
var crypto = require('crypto'); | ||
var createBuffer = require('./jwe').createBuffer; | ||
var sameSiteNotAllowed = require('./aadutils').sameSiteNotAllowed; | ||
const crypto = require("crypto"); | ||
const createBuffer = require("./jwe").createBuffer; | ||
const sameSiteNotAllowed = require("./aadutils").sameSiteNotAllowed; | ||
@@ -39,20 +21,20 @@ /* | ||
function CookieContentHandler(maxAmount, maxAge, cookieEncryptionKeys, domain, cookieSameSite) { | ||
if (!maxAge || (typeof maxAge !== 'number' || maxAge <= 0)) | ||
throw new Error('CookieContentHandler: maxAge must be a positive number'); | ||
if (!maxAge || (typeof maxAge !== "number" || maxAge <= 0)) | ||
throw new Error("CookieContentHandler: maxAge must be a positive number"); | ||
this.maxAge = maxAge; // seconds | ||
if (!maxAmount || (typeof maxAmount !== 'number' || maxAmount <= 0 || maxAmount % 1 !== 0)) | ||
throw new Error('CookieContentHandler: maxAmount must be a positive integer'); | ||
if (!maxAmount || (typeof maxAmount !== "number" || maxAmount <= 0 || maxAmount % 1 !== 0)) | ||
throw new Error("CookieContentHandler: maxAmount must be a positive integer"); | ||
this.maxAmount = maxAmount; | ||
if (!cookieEncryptionKeys || !Array.isArray(cookieEncryptionKeys) || cookieEncryptionKeys.length === 0) | ||
throw new Error('CookieContentHandler: cookieEncryptionKeys must be a non-emptry array'); | ||
throw new Error("CookieContentHandler: cookieEncryptionKeys must be a non-emptry array"); | ||
if (typeof cookieSameSite !== 'boolean') { | ||
throw new Error('CookieContentHandler: cookieSameSite must be a boolean'); | ||
if (typeof cookieSameSite !== "boolean") { | ||
throw new Error("CookieContentHandler: cookieSameSite must be a boolean"); | ||
} | ||
this.cookieSameSite = cookieSameSite | ||
for (var i = 0; i < cookieEncryptionKeys.length; i++) { | ||
var item = cookieEncryptionKeys[i]; | ||
for (let i = 0; i < cookieEncryptionKeys.length; i++) { | ||
const item = cookieEncryptionKeys[i]; | ||
if (!item.key || !item.iv) | ||
@@ -73,20 +55,20 @@ throw new Error(`CookieContentHandler: array item ${i+1} in cookieEncryptionKeys must have the form { key: , iv: }`); | ||
if (!req.cookies) | ||
throw new Error('Cookie is not found in request. Did you forget to use cookie parsing middleware such as cookie-parser?'); | ||
throw new Error("Cookie is not found in request. Did you forget to use cookie parsing middleware such as cookie-parser?"); | ||
var cookieEncryptionKeys = this.cookieEncryptionKeys; | ||
const cookieEncryptionKeys = this.cookieEncryptionKeys; | ||
var tuple = null; | ||
let tuple = null; | ||
// try every key and every cookie | ||
for (var i = 0; i < cookieEncryptionKeys.length; i++) { | ||
var item = cookieEncryptionKeys[i]; | ||
var key = createBuffer(item.key); | ||
var iv = createBuffer(item.iv); | ||
for (let i = 0; i < cookieEncryptionKeys.length; i++) { | ||
const item = cookieEncryptionKeys[i]; | ||
const key = createBuffer(item.key); | ||
const iv = createBuffer(item.iv); | ||
for (var cookie in req.cookies) { | ||
if (req.cookies.hasOwnProperty(cookie) && cookie.startsWith('passport-aad.')) { | ||
var encrypted = cookie.substring(13); | ||
for (const cookie in req.cookies) { | ||
if (Object.prototype.hasOwnProperty.call(req.cookies, cookie) && cookie.startsWith("passport-aad.")) { | ||
const encrypted = cookie.substring(13); | ||
try { | ||
var decrypted = decryptCookie(encrypted, key, iv); | ||
const decrypted = decryptCookie(encrypted, key, iv); | ||
tuple = JSON.parse(decrypted); | ||
@@ -109,7 +91,7 @@ } catch (ex) { | ||
CookieContentHandler.prototype.add = function(req, res, tupleToAdd) { | ||
var cookies = []; | ||
const cookies = []; | ||
// collect the related cookies | ||
for (var cookie in req.cookies) { | ||
if (req.cookies.hasOwnProperty(cookie) && cookie.startsWith('passport-aad.')) | ||
for (const cookie in req.cookies) { | ||
if (Object.prototype.hasOwnProperty.call(req.cookies, cookie) && cookie.startsWith("passport-aad.")) | ||
cookies.push(cookie); | ||
@@ -122,5 +104,5 @@ } | ||
var numberToRemove = cookies.length - (this.maxAmount - 1); | ||
const numberToRemove = cookies.length - (this.maxAmount - 1); | ||
for (var i = 0; i < numberToRemove; i++) { | ||
for (let i = 0; i < numberToRemove; i++) { | ||
res.clearCookie(cookies[0]); | ||
@@ -133,11 +115,11 @@ cookies.shift(); | ||
var tupleString = JSON.stringify(tupleToAdd); | ||
const tupleString = JSON.stringify(tupleToAdd); | ||
var item = this.cookieEncryptionKeys[0]; | ||
var key = createBuffer(item.key); | ||
var iv = createBuffer(item.iv); | ||
const item = this.cookieEncryptionKeys[0]; | ||
const key = createBuffer(item.key); | ||
const iv = createBuffer(item.iv); | ||
var encrypted = encryptCookie(tupleString, key, iv); | ||
const encrypted = encryptCookie(tupleString, key, iv); | ||
let options = { maxAge: this.maxAge * 1000, httpOnly: true } | ||
const options = { maxAge: this.maxAge * 1000, httpOnly: true } | ||
if (this.domain) { | ||
@@ -147,33 +129,33 @@ options.domain = this.domain; | ||
if (this.cookieSameSite && !sameSiteNotAllowed(req.get('User-Agent'))) { | ||
options.sameSite = 'none'; | ||
if (this.cookieSameSite && !sameSiteNotAllowed(req.get("User-Agent"))) { | ||
options.sameSite = "none"; | ||
options.secure = true; | ||
} | ||
res.cookie('passport-aad.' + Date.now() + '.' + encrypted, 0, options); | ||
res.cookie("passport-aad." + Date.now() + "." + encrypted, 0, options); | ||
}; | ||
var encryptCookie = function(content, key, iv) { | ||
var cipher = crypto.createCipheriv('aes-256-gcm', key, iv); | ||
const encryptCookie = function(content, key, iv) { | ||
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv); | ||
var encrypted = cipher.update(content, 'utf8', 'hex'); | ||
encrypted += cipher.final('hex'); | ||
var authTag = cipher.getAuthTag().toString('hex'); | ||
let encrypted = cipher.update(content, "utf8", "hex"); | ||
encrypted += cipher.final("hex"); | ||
const authTag = cipher.getAuthTag().toString("hex"); | ||
return encrypted + '.' + authTag; | ||
return encrypted + "." + authTag; | ||
}; | ||
var decryptCookie = function(encrypted, key, iv) { | ||
var parts = encrypted.split('.'); | ||
const decryptCookie = function(encrypted, key, iv) { | ||
const parts = encrypted.split("."); | ||
if (parts.length !== 3) | ||
throw new Error('invalid cookie'); | ||
throw new Error("invalid cookie"); | ||
// the first part is timestamp, ignore it | ||
var content = createBuffer(parts[1], 'hex'); | ||
var authTag = createBuffer(parts[2], 'hex'); | ||
const content = createBuffer(parts[1], "hex"); | ||
const authTag = createBuffer(parts[2], "hex"); | ||
var decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); | ||
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv); | ||
decipher.setAuthTag(authTag); | ||
var decrypted = decipher.update(content, 'hex', 'utf8'); | ||
decrypted += decipher.final('utf8'); | ||
let decrypted = decipher.update(content, "hex", "utf8"); | ||
decrypted += decipher.final("utf8"); | ||
@@ -180,0 +162,0 @@ return decrypted; |
@@ -1,3 +0,8 @@ | ||
'use strict'; | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
"use strict"; | ||
/** | ||
@@ -11,3 +16,3 @@ * `BadRequestError` error. | ||
Error.captureStackTrace(this, BadRequestError); | ||
this.name = 'BadRequestError'; | ||
this.name = "BadRequestError"; | ||
this.message = message || null; | ||
@@ -14,0 +19,0 @@ } |
@@ -1,3 +0,8 @@ | ||
'use strict'; | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
"use strict"; | ||
/** | ||
@@ -15,3 +20,3 @@ * `InternalOAuthError` error. | ||
Error.captureStackTrace(this, InternalOAuthError); | ||
this.name = 'InternalOAuthError'; | ||
this.name = "InternalOAuthError"; | ||
this.message = message; | ||
@@ -18,0 +23,0 @@ this.oauthError = err; |
@@ -1,3 +0,8 @@ | ||
'use strict'; | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
"use strict"; | ||
/** | ||
@@ -15,3 +20,3 @@ * `InternalOpenIDError` error. | ||
Error.captureStackTrace(this, InternalOpenIDError); | ||
this.name = 'InternalOpenIDError'; | ||
this.name = "InternalOpenIDError"; | ||
this.message = message; | ||
@@ -18,0 +23,0 @@ this.openidError = err; |
@@ -1,25 +0,7 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the 'Software'), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
@@ -29,3 +11,3 @@ /** | ||
*/ | ||
module.exports.BearerStrategy = require('./bearerstrategy'); | ||
module.exports.OIDCStrategy = require('./oidcstrategy'); | ||
module.exports.BearerStrategy = require("./bearerstrategy"); | ||
module.exports.OIDCStrategy = require("./oidcstrategy"); |
@@ -1,33 +0,15 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the 'Software'), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use restrict'; | ||
"use restrict"; | ||
const aadutils = require('./aadutils'); | ||
const CONSTANTS = require('./constants'); | ||
const jws = require('jws'); | ||
const aadutils = require("./aadutils"); | ||
const CONSTANTS = require("./constants"); | ||
const jws = require("jws"); | ||
// check if two arrays have common elements | ||
var hasCommonElem = (array1, array2) => { | ||
for (var i = 0; i < array1.length; i++) { | ||
const hasCommonElem = (array1, array2) => { | ||
for (let i = 0; i < array1.length; i++) { | ||
if (array2.indexOf(array1[i]) !== -1) | ||
@@ -39,3 +21,4 @@ return true; | ||
/* Verify the token and return the payload | ||
/* | ||
* Verify the token and return the payload | ||
* | ||
@@ -57,25 +40,29 @@ * @jwtString | ||
/********************************************************************* | ||
/** | ||
* Checking parameters | ||
********************************************************************/ | ||
*/ | ||
// check the existence of callback function and options, if we don't have them, that means we have | ||
// less than 4 parameters passed. This is a server (code) error, we should throw. | ||
/* | ||
* check the existence of callback function and options, if we don't have them, that means we have | ||
* less than 4 parameters passed. This is a server (code) error, we should throw. | ||
*/ | ||
if (!callback) | ||
throw new Error('callback must be provided in jsonWebToken.verify'); | ||
if (typeof callback !== 'function') | ||
throw new Error('callback in jsonWebToken.verify must be a function'); | ||
throw new Error("callback must be provided in jsonWebToken.verify"); | ||
if (typeof callback !== "function") | ||
throw new Error("callback in jsonWebToken.verify must be a function"); | ||
if (!options) | ||
throw new Error('options must be provided in jsonWebToken.verify'); | ||
throw new Error("options must be provided in jsonWebToken.verify"); | ||
// check jwtString and PEMKey are provided. Since jwtString and PEMKey are generated, this is | ||
// a non-server error, we shouldn't throw, we just give the error back and let authentication fail. | ||
if (!jwtString || jwtString === '') | ||
return done(new Error('jwtString must be provided in jsonWebToken.verify')); | ||
if (!PEMKey || PEMKey === '') | ||
return done(new Error('PEMKey must be provided in jsonWebToken.verify')); | ||
/* | ||
* check jwtString and PEMKey are provided. Since jwtString and PEMKey are generated, this is | ||
* a non-server error, we shouldn't throw, we just give the error back and let authentication fail. | ||
*/ | ||
if (!jwtString || jwtString === "") | ||
return done(new Error("jwtString must be provided in jsonWebToken.verify")); | ||
if (!PEMKey || PEMKey === "") | ||
return done(new Error("PEMKey must be provided in jsonWebToken.verify")); | ||
// asynchronous wrapper for callback | ||
var done = function() { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
const done = function() { | ||
const args = Array.prototype.slice.call(arguments, 0); | ||
return process.nextTick(function() { | ||
@@ -87,60 +74,60 @@ callback.apply(null, args); | ||
// make sure we have the required fields in options | ||
if (!(options.audience && (typeof options.audience === 'string' || | ||
if (!(options.audience && (typeof options.audience === "string" || | ||
(Array.isArray(options.audience) && options.audience.length > 0)))) | ||
return done(new Error('invalid options.audience value is provided in jsonWebToken.verify')); | ||
return done(new Error("invalid options.audience value is provided in jsonWebToken.verify")); | ||
if (!options.algorithms) | ||
return done(new Error('options.algorithms is missing in jsonWebToken.verify')); | ||
if (!Array.isArray(options.algorithms) || options.algorithms.length == 0 || | ||
(options.algorithms.length === 1 && options.algorithms[0] === 'none')) | ||
return done(new Error('options.algorithms must be an array containing at least one algorithm')); | ||
return done(new Error("options.algorithms is missing in jsonWebToken.verify")); | ||
if (!Array.isArray(options.algorithms) || options.algorithms.length === 0 || | ||
(options.algorithms.length === 1 && options.algorithms[0] === "none")) | ||
return done(new Error("options.algorithms must be an array containing at least one algorithm")); | ||
/********************************************************************* | ||
/** | ||
* Checking jwtString structure, getting header, payload and signature | ||
********************************************************************/ | ||
*/ | ||
// split jwtString, make sure we have three parts and we have signature | ||
var parts = jwtString.split('.'); | ||
const parts = jwtString.split("."); | ||
if (parts.length !== 3) | ||
return done(new Error('jwtString is malformed')); | ||
if (parts[2] === '') | ||
return done(new Error('signature is missing in jwtString')); | ||
return done(new Error("jwtString is malformed")); | ||
if (parts[2] === "") | ||
return done(new Error("signature is missing in jwtString")); | ||
// decode jwsString | ||
var decodedToken; | ||
let decodedToken; | ||
try { | ||
decodedToken = jws.decode(jwtString); | ||
} catch(err) { | ||
return done(new Error('failed to decode the token')); | ||
return done(new Error("failed to decode the token")); | ||
} | ||
if (!decodedToken) { | ||
return done(new Error('invalid token')); | ||
return done(new Error("invalid token")); | ||
} | ||
// get header, payload and signature | ||
var header = decodedToken.header; | ||
var payload = decodedToken.payload; | ||
var signature = decodedToken.signature; | ||
const header = decodedToken.header; | ||
const payload = decodedToken.payload; | ||
const signature = decodedToken.signature; | ||
if (!header) | ||
return done(new Error('missing header in the token')); | ||
return done(new Error("missing header in the token")); | ||
if (!payload) | ||
return done(new Error('missing payload in the token')); | ||
return done(new Error("missing payload in the token")); | ||
if (!signature) | ||
return done(new Error('missing signature in the token')); | ||
return done(new Error("missing signature in the token")); | ||
/********************************************************************* | ||
/** | ||
* validate algorithm and signature | ||
********************************************************************/ | ||
*/ | ||
// header.alg should be one of the algorithms provided in options.algorithms | ||
if (typeof header.alg !== 'string' || header.alg === '' || header.alg === 'none' || | ||
options.algorithms.indexOf(header.alg) == -1) { | ||
return done(new Error('invalid algorithm')); | ||
if (typeof header.alg !== "string" || header.alg === "" || header.alg === "none" || | ||
options.algorithms.indexOf(header.alg) === -1) { | ||
return done(new Error("invalid algorithm")); | ||
} | ||
try { | ||
var valid = jws.verify(jwtString, header.alg, PEMKey); | ||
const valid = jws.verify(jwtString, header.alg, PEMKey); | ||
if (!valid) | ||
return done(new Error('invalid signature')); | ||
return done(new Error("invalid signature")); | ||
} catch (e) { | ||
@@ -150,15 +137,17 @@ return done(e); | ||
/********************************************************************* | ||
/** | ||
* validate payload content | ||
********************************************************************/ | ||
*/ | ||
// (1) issuer | ||
// - check the existence and the format of payload.iss | ||
// - validate if options.issuer is set | ||
if (typeof payload.iss !== 'string' || payload.iss === '') | ||
return done(new Error('invalid iss value in payload')); | ||
/* | ||
* (1) issuer | ||
* - check the existence and the format of payload.iss | ||
* - validate if options.issuer is set | ||
*/ | ||
if (typeof payload.iss !== "string" || payload.iss === "") | ||
return done(new Error("invalid iss value in payload")); | ||
if (options.validateIssuer !== false) { | ||
if (!options.issuer || options.issuer === '' || (Array.isArray(options.issuer) && options.issuer.length === 0)) | ||
return done(new Error('options.issuer is missing')); | ||
var valid = false; | ||
if (!options.issuer || options.issuer === "" || (Array.isArray(options.issuer) && options.issuer.length === 0)) | ||
return done(new Error("options.issuer is missing")); | ||
let valid = false; | ||
if (Array.isArray(options.issuer)) | ||
@@ -169,58 +158,65 @@ valid = (options.issuer.indexOf(payload.iss) !== -1); | ||
if (!valid) | ||
return done(new Error('jwt issuer is invalid')); | ||
return done(new Error("jwt issuer is invalid")); | ||
} | ||
// (2) subject (id_token only. We don't check subject for access_token) | ||
// - check the existence and the format of payload.sub | ||
// - validate if options.subject is set | ||
/* | ||
* (2) subject (id_token only. We don't check subject for access_token) | ||
* - check the existence and the format of payload.sub | ||
* - validate if options.subject is set | ||
*/ | ||
if (options.isAccessToken !== true) { | ||
if (typeof payload.sub !== 'string' || payload.sub === '') | ||
return done(new Error('invalid sub value in payload')); | ||
if (typeof payload.sub !== "string" || payload.sub === "") | ||
return done(new Error("invalid sub value in payload")); | ||
if (options.subject && options.subject !== payload.sub) | ||
return done(new Error('jwt subject is invalid. expected')); | ||
return done(new Error("jwt subject is invalid. expected")); | ||
} | ||
// (3) audience | ||
// - always validate | ||
// - allow payload.aud to be an array of audience | ||
// - options.audience must be a string | ||
// - if there are multiple audiences, then azp claim must exist and must equal client_id | ||
if (typeof options.audience === 'string') | ||
options.audience = [options.audience, 'spn:' + options.audience]; | ||
/* | ||
* (3) audience | ||
* - always validate | ||
* - allow payload.aud to be an array of audience | ||
* - options.audience must be a string | ||
* - if there are multiple audiences, then azp claim must exist and must equal client_id | ||
*/ | ||
if (typeof options.audience === "string") | ||
options.audience = [options.audience, "spn:" + options.audience]; | ||
if (options.allowMultiAudiencesInToken === false && Array.isArray(payload.aud) && payload.aud.length > 1) | ||
return done(new Error('mulitple audience in token is not allowed')); | ||
var payload_audience = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; | ||
return done(new Error("mulitple audience in token is not allowed")); | ||
const payload_audience = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; | ||
if (!hasCommonElem(options.audience, payload_audience)) | ||
return done(new Error('jwt audience is invalid')); | ||
return done(new Error("jwt audience is invalid")); | ||
if (payload_audience.length > 1) { | ||
if (typeof payload.azp !== 'string' || payload.azp !== options.clientID) | ||
return done(new Error('jwt azp is invalid')); | ||
if (typeof payload.azp !== "string" || payload.azp !== options.clientID) | ||
return done(new Error("jwt azp is invalid")); | ||
} | ||
// (4) expiration | ||
// - check the existence and the format of payload.exp | ||
// - validate unless options.ignoreExpiration is set true | ||
if (typeof payload.exp !== 'number') | ||
return done(new Error('invalid exp value in payload')); | ||
/* | ||
* (4) expiration | ||
* - check the existence and the format of payload.exp | ||
* - validate unless options.ignoreExpiration is set true | ||
*/ | ||
if (typeof payload.exp !== "number") | ||
return done(new Error("invalid exp value in payload")); | ||
if (!options.ignoreExpiration) { | ||
if (Math.floor(Date.now() / 1000) >= payload.exp + options.clockSkew) { | ||
return done(new Error('jwt is expired')); | ||
return done(new Error("jwt is expired")); | ||
} | ||
} | ||
// (5) nbf | ||
// - check if it exists | ||
/* | ||
* (5) nbf | ||
* - check if it exists | ||
*/ | ||
if (payload.nbf) { | ||
if (typeof payload.nbf !== 'number') | ||
return done(new Error('nbf value in payload is not a number')); | ||
if (typeof payload.nbf !== "number") | ||
return done(new Error("nbf value in payload is not a number")); | ||
if (payload.nbf >= payload.exp) | ||
return done(new Error('nbf value in payload is not before the exp value')); | ||
return done(new Error("nbf value in payload is not before the exp value")); | ||
if (payload.nbf >= Math.floor(Date.now() / 1000) + options.clockSkew) | ||
return done(new Error('jwt is not active')); | ||
return done(new Error("jwt is not active")); | ||
} | ||
/********************************************************************* | ||
/** | ||
* return the payload content | ||
********************************************************************/ | ||
*/ | ||
@@ -230,3 +226,4 @@ return done(null, payload); | ||
/* Generate client assertion | ||
/* | ||
* Generate client assertion | ||
* | ||
@@ -240,4 +237,4 @@ * @params {String} clientID | ||
exports.generateClientAssertion = function(clientID, token_endpoint, privatePEMKey, thumbprint, callback) { | ||
var header = { 'x5t': thumbprint, 'alg': 'RS256', 'typ': 'JWT' }; | ||
var payload = { | ||
const header = { "x5t": thumbprint, "alg": "RS256", "typ": "JWT" }; | ||
const payload = { | ||
sub: clientID, | ||
@@ -251,4 +248,4 @@ iss: clientID, | ||
var clientAssertion; | ||
var exception = null; | ||
let clientAssertion; | ||
let exception = null; | ||
@@ -255,0 +252,0 @@ try { |
395
lib/jwe.js
@@ -1,37 +0,17 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the "Software"), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
/* eslint-disable no-new */ | ||
'use strict'; | ||
"use strict"; | ||
var crypto = require('crypto'); | ||
var constants = require('constants'); | ||
const base64url = require('base64url'); | ||
const crypto = require("crypto"); | ||
const constants = require("constants"); | ||
const base64url = require("base64url"); | ||
var jose = require('node-jose'); | ||
const jose = require("node-jose"); | ||
/****************************************************************************** | ||
/** | ||
* utility functions | ||
*****************************************************************************/ | ||
*/ | ||
@@ -45,10 +25,10 @@ /** | ||
*/ | ||
var createBuffer = (data, encoding) => { | ||
if (!Buffer.isBuffer(data) && typeof data !== 'string' && typeof data !== 'number') | ||
throw new Error('in createBuffer, data must be a buffer, string or number'); | ||
const createBuffer = (data, encoding) => { | ||
if (!Buffer.isBuffer(data) && typeof data !== "string" && typeof data !== "number") | ||
throw new Error("in createBuffer, data must be a buffer, string or number"); | ||
if (process.version >= 'v6') { | ||
if (typeof data === 'string') | ||
if (process.version >= "v6") { | ||
if (typeof data === "string") | ||
return Buffer.from(data, encoding); | ||
else if (typeof data === 'number') | ||
else if (typeof data === "number") | ||
return Buffer.alloc(data); | ||
@@ -58,6 +38,6 @@ else | ||
} else { | ||
if (typeof data === 'string') | ||
return new Buffer(data, encoding); | ||
if (typeof data === "string") | ||
return new Buffer(data, encoding); // eslint-disable-line security/detect-new-buffer -- Legacy code, Buffer needed | ||
else | ||
return new Buffer(data); | ||
return new Buffer(data); // eslint-disable-line security/detect-new-buffer -- Legacy code, Buffer needed | ||
} | ||
@@ -73,4 +53,4 @@ }; | ||
*/ | ||
var xor = (a, b) => { | ||
var c1, c2; | ||
const xor = (a, b) => { | ||
let c1, c2; | ||
if (a.length > b.length) { | ||
@@ -81,4 +61,4 @@ c1 = a; c2 = b; | ||
} | ||
var c = createBuffer(c1); | ||
for (var i = 1; i <= c2.length; i++) | ||
const c = createBuffer(c1); | ||
for (let i = 1; i <= c2.length; i++) | ||
c[c1.length-i] = c[c1.length-i] ^ c2[c2.length-i]; | ||
@@ -88,5 +68,5 @@ return c; | ||
/****************************************************************************** | ||
/** | ||
* AES key unwrap algorithms | ||
*****************************************************************************/ | ||
*/ | ||
@@ -101,14 +81,14 @@ /** | ||
*/ | ||
var AESKeyUnWrap = (algorithm, wrapped_cek, kek) => { | ||
const AESKeyUnWrap = (algorithm, wrapped_cek, kek) => { | ||
/**************************************************************************** | ||
/** | ||
* Inputs: CipherText, (n+1) 64-bit values {C0, C1, ..., Cn}, and | ||
* Key, K (the KEK) | ||
* Outputs: Plaintext, n 64-bit values {P0, P1, K, Pn} | ||
***************************************************************************/ | ||
var C = wrapped_cek; | ||
var n = C.length/8-1; | ||
var K = kek; | ||
*/ | ||
const C = wrapped_cek; | ||
const n = C.length/8-1; | ||
const K = kek; | ||
/**************************************************************************** | ||
/** | ||
* 1) Initialize variables | ||
@@ -118,9 +98,9 @@ * Set A = C[0] | ||
* R[i] = C[i] | ||
***************************************************************************/ | ||
var A = C.slice(0,8); | ||
var R = [createBuffer(1)]; | ||
for (var i = 1; i <= n; i++) | ||
*/ | ||
let A = C.slice(0,8); | ||
const R = [createBuffer(1)]; | ||
for (let i = 1; i <= n; i++) | ||
R.push(C.slice(8*i, 8*i+8)); | ||
/**************************************************************************** | ||
/** | ||
* 2) compute intermediate values | ||
@@ -132,17 +112,17 @@ * For j = 5 to 0 | ||
* R[i] = LSB(64, B) | ||
***************************************************************************/ | ||
for(var j=5; j >= 0; j--) { | ||
for(var i=n; i >= 1; i--) { | ||
*/ | ||
for(let j=5; j >= 0; j--) { | ||
for(let i=n; i >= 1; i--) { | ||
// turn t = n*j+i into buffer | ||
var str = (n*j+i).toString(16); | ||
let str = (n*j+i).toString(16); | ||
if (str.length %2 !== 0) | ||
str = '0' + str; | ||
var t = createBuffer(str, 'hex'); | ||
str = "0" + str; | ||
const t = createBuffer(str, "hex"); | ||
// B = AES-1(K, (A^t) | R[i]) | ||
var aes = crypto.createDecipheriv(algorithm, K, ''); | ||
const aes = crypto.createDecipheriv(algorithm, K, ""); | ||
aes.setAutoPadding(false); | ||
var B = aes.update(Buffer.concat([xor(A, t), R[i]]), null, 'hex'); | ||
B += aes.final('hex'); | ||
B = createBuffer(B, 'hex'); | ||
let B = aes.update(Buffer.concat([xor(A, t), R[i]]), null, "hex"); | ||
B += aes.final("hex"); | ||
B = createBuffer(B, "hex"); | ||
@@ -157,3 +137,3 @@ // A = MSB(64, B) | ||
/**************************************************************************** | ||
/** | ||
* 3) Output results. | ||
@@ -166,8 +146,8 @@ * If A is an appropriate initial value | ||
* Return an error | ||
***************************************************************************/ | ||
*/ | ||
// check A | ||
if (A.toString('hex').toUpperCase() === 'A6A6A6A6A6A6A6A6') { | ||
var result = R[1]; | ||
for (var i = 2; i <= n; i++) | ||
if (A.toString("hex").toUpperCase() === "A6A6A6A6A6A6A6A6") { | ||
let result = R[1]; | ||
for (let i = 2; i <= n; i++) | ||
result = Buffer.concat([result, R[i]]); | ||
@@ -177,9 +157,9 @@ | ||
} else { | ||
throw new Error('aes decryption failed: invalid key'); | ||
throw new Error("aes decryption failed: invalid key"); | ||
} | ||
}; | ||
/****************************************************************************** | ||
/** | ||
* AES-CBC-HMAC-SHA2 decryption | ||
*****************************************************************************/ | ||
*/ | ||
@@ -197,20 +177,22 @@ /** | ||
*/ | ||
var decrypt_AES_CBC_HMAC_SHA2 = (algorithm, key, cipherText, iv, aad, authTag) => { | ||
// algorithm information | ||
// note ENC_KEY_LEN = MAC_KEY_LEN = T_LEN = len | ||
var algInfo = { | ||
'aes-128-cbc-hmac-sha-256': { | ||
'cipher_algo': 'aes-128-cbc', | ||
'hash_algo': 'sha256', | ||
'len': 16 | ||
const decrypt_AES_CBC_HMAC_SHA2 = (algorithm, key, cipherText, iv, aad, authTag) => { | ||
/* | ||
* algorithm information | ||
* note ENC_KEY_LEN = MAC_KEY_LEN = T_LEN = len | ||
*/ | ||
const algInfo = { | ||
"aes-128-cbc-hmac-sha-256": { | ||
"cipher_algo": "aes-128-cbc", | ||
"hash_algo": "sha256", | ||
"len": 16 | ||
}, | ||
'aes-192-cbc-hmac-sha-384': { | ||
'cipher_algo': 'aes-192-cbc', | ||
'hash_algo': 'sha384', | ||
'len': 24 | ||
"aes-192-cbc-hmac-sha-384": { | ||
"cipher_algo": "aes-192-cbc", | ||
"hash_algo": "sha384", | ||
"len": 24 | ||
}, | ||
'aes-256-cbc-hmac-sha-512': { | ||
'cipher_algo': 'aes-256-cbc', | ||
'hash_algo': 'sha512', | ||
'len': 32 | ||
"aes-256-cbc-hmac-sha-512": { | ||
"cipher_algo": "aes-256-cbc", | ||
"hash_algo": "sha512", | ||
"len": 32 | ||
} | ||
@@ -223,29 +205,29 @@ }; | ||
if (!algorithm) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: algorithm is not provided'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: algorithm is not provided"); | ||
if (!algInfo[algorithm]) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: unsupported algorithm: ' + algorithm); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: unsupported algorithm: " + algorithm); | ||
var algo = algInfo[algorithm]; | ||
const algo = algInfo[algorithm]; | ||
// check the size of key | ||
if (!key) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: key is not provided'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: key is not provided"); | ||
if (!(key instanceof Buffer)) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: key must be a buffer'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: key must be a buffer"); | ||
if (key.length !== algInfo[algorithm].len * 2) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: key has size ' + key.length + ', it must have size ' + algo.len * 2); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: key has size " + key.length + ", it must have size " + algo.len * 2); | ||
// check the size of iv | ||
if (!iv) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: iv is not provided'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: iv is not provided"); | ||
if (!(iv instanceof Buffer)) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: iv must be a buffer'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: iv must be a buffer"); | ||
if (iv.length !== 16) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: iv has size ' + iv.length + ', it must have size 16'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: iv has size " + iv.length + ", it must have size 16"); | ||
// check the existence of aad | ||
if (!aad) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: aad is not provided'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: aad is not provided"); | ||
if (!(aad instanceof Buffer)) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: aad must be a buffer'); | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: aad must be a buffer"); | ||
@@ -255,12 +237,14 @@ // 2. Split key | ||
// first half is mac_key and the second half is enc_key | ||
var mac_key = key.slice(0, algo.len); | ||
var enc_key = key.slice(algo.len); | ||
const mac_key = key.slice(0, algo.len); | ||
const enc_key = key.slice(algo.len); | ||
// 3. Verify tag | ||
// tag should be: hash(aad + iv + cipherText + aad_len) where aad_len is the | ||
// number of bits in aad expressed as a 64-bit unsigned big-endian integer | ||
/* | ||
* tag should be: hash(aad + iv + cipherText + aad_len) where aad_len is the | ||
* number of bits in aad expressed as a 64-bit unsigned big-endian integer | ||
*/ | ||
// 3.1 compute aad_len | ||
var aad_len = createBuffer(8); | ||
const aad_len = createBuffer(8); | ||
aad_len.writeUInt32BE(aad.length * 8, 4); | ||
@@ -270,3 +254,3 @@ aad_len.writeUInt32BE(0, 0); | ||
// 3.2 create hmac algorithm | ||
var hmac = crypto.createHmac(algo.hash_algo, mac_key); | ||
const hmac = crypto.createHmac(algo.hash_algo, mac_key); | ||
hmac.write(aad); | ||
@@ -276,11 +260,11 @@ hmac.write(iv); | ||
hmac.update(aad_len); | ||
var computed_authTag = hmac.digest().slice(0, algo.len); | ||
const computed_authTag = hmac.digest().slice(0, algo.len); | ||
// 3.3 verify the tag | ||
if (!authTag || !computed_authTag || (authTag.toString('hex') !== computed_authTag.toString('hex'))) | ||
throw new Error('In decrypt_AES_CBC_HMAC_SHA2: invalid authentication tag'); | ||
if (!authTag || !computed_authTag || (authTag.toString("hex") !== computed_authTag.toString("hex"))) | ||
throw new Error("In decrypt_AES_CBC_HMAC_SHA2: invalid authentication tag"); | ||
// 4. Decrypt cipherText | ||
var decipher = crypto.createDecipheriv(algo.cipher_algo, enc_key, iv); | ||
var plainText = decipher.update(cipherText); | ||
const decipher = crypto.createDecipheriv(algo.cipher_algo, enc_key, iv); | ||
let plainText = decipher.update(cipherText); | ||
plainText = Buffer.concat([plainText, decipher.final()]); | ||
@@ -291,6 +275,5 @@ | ||
/****************************************************************************** | ||
/** | ||
* JWE decryption | ||
*****************************************************************************/ | ||
*/ | ||
@@ -305,33 +288,33 @@ /** | ||
*/ | ||
var decryptCEKHelper = (alg, encrypted_cek, key, log) => { | ||
var error = null; | ||
var cek = null; | ||
const decryptCEKHelper = (alg, encrypted_cek, key, log) => { | ||
let error = null; | ||
let cek = null; | ||
try { | ||
var key_to_use; | ||
let key_to_use; | ||
if (alg === 'RSA1_5' || alg === 'RSA-OAEP') { | ||
if (key['privatePemKey']) { | ||
log.info('using RSA privatePemKey to decrypt cek'); | ||
key_to_use = key['privatePemKey']; | ||
if (alg === "RSA1_5" || alg === "RSA-OAEP") { | ||
if (key["privatePemKey"]) { | ||
log.info("using RSA privatePemKey to decrypt cek"); | ||
key_to_use = key["privatePemKey"]; | ||
} else { | ||
log.info('converting jwk to RSA privatePemKey to decrypt cek'); | ||
log.info("converting jwk to RSA privatePemKey to decrypt cek"); | ||
return { "error": "converting jwk to RSA privatePemKey to decrypt cek", cek: key }; | ||
} | ||
} else if (alg === 'A128KW' || alg === 'A256KW') { | ||
log.info('using symmetric key to decrypt cek'); | ||
} else if (alg === "A128KW" || alg === "A256KW") { | ||
log.info("using symmetric key to decrypt cek"); | ||
key_to_use = base64url.toBuffer(key.k); | ||
} else { | ||
log.info('unsupported alg: ' + alg); | ||
return {'error': error, 'cek': null}; | ||
log.info("unsupported alg: " + alg); | ||
return {"error": error, "cek": null}; | ||
} | ||
if (alg === 'RSA1_5') | ||
if (alg === "RSA1_5") | ||
cek = crypto.privateDecrypt({ key: key_to_use, padding: constants.RSA_PKCS1_PADDING }, encrypted_cek); | ||
else if (alg === 'RSA-OAEP') | ||
else if (alg === "RSA-OAEP") | ||
cek = crypto.privateDecrypt({ key: key_to_use, padding: constants.RSA_PKCS1_OAEP_PADDING }, encrypted_cek); | ||
else if (alg === 'A128KW') | ||
cek = AESKeyUnWrap('aes-128-ecb', encrypted_cek, key_to_use); | ||
else if (alg === 'A256KW') | ||
cek = AESKeyUnWrap('aes-256-ecb', encrypted_cek, key_to_use); | ||
else if (alg === "A128KW") | ||
cek = AESKeyUnWrap("aes-128-ecb", encrypted_cek, key_to_use); | ||
else if (alg === "A256KW") | ||
cek = AESKeyUnWrap("aes-256-ecb", encrypted_cek, key_to_use); | ||
else | ||
@@ -343,3 +326,3 @@ cek = key_to_use; // dir | ||
return {'error': error, 'cek': cek}; | ||
return {"error": error, "cek": cek}; | ||
}; | ||
@@ -355,17 +338,17 @@ | ||
*/ | ||
var decryptCEK = (header, encrypted_cek, jweKeyStore, log) => { | ||
var algKtyMapper = { 'RSA1_5': 'RSA', 'RSA-OAEP': 'RSA', 'A128KW': 'oct', 'A256KW': 'oct'}; | ||
const decryptCEK = (header, encrypted_cek, jweKeyStore, log) => { | ||
const algKtyMapper = { "RSA1_5": "RSA", "RSA-OAEP": "RSA", "A128KW": "oct", "A256KW": "oct"}; | ||
if (!header.alg) | ||
return { 'error': new Error('alg is missing in JWE header'), 'cek': null }; | ||
if(['RSA1_5', 'RSA-OAEP', 'dir', 'A128KW', 'A256KW'].indexOf(header.alg) === -1) | ||
return { 'error' : new Error('Unsupported alg in JWE header: ' + header.alg), 'cek': null }; | ||
return { "error": new Error("alg is missing in JWE header"), "cek": null }; | ||
if(["RSA1_5", "RSA-OAEP", "dir", "A128KW", "A256KW"].indexOf(header.alg) === -1) | ||
return { "error" : new Error("Unsupported alg in JWE header: " + header.alg), "cek": null }; | ||
var key = null; | ||
let key = null; | ||
if (header.kid) { | ||
for (var i = 0; i < jweKeyStore.length; i++) { | ||
for (let i = 0; i < jweKeyStore.length; i++) { | ||
if (header.kid === jweKeyStore[i].kid && algKtyMapper[header.alg] === jweKeyStore[i].kty) { | ||
key = jweKeyStore[i]; | ||
log.info('found a key matching kid: ' + header.kid); | ||
log.info("found a key matching kid: " + header.kid); | ||
return decryptCEKHelper(header.alg, encrypted_cek, key, log); | ||
@@ -375,11 +358,11 @@ } | ||
return { 'error': new Error('cannot find a key matching kid: ' + header.kid), 'cek': null }; | ||
return { "error": new Error("cannot find a key matching kid: " + header.kid), "cek": null }; | ||
} | ||
log.info('kid is not provided in JWE header, now we try all the possible keys'); | ||
log.info("kid is not provided in JWE header, now we try all the possible keys"); | ||
// kid matching failed, now we try every possible key | ||
for(var i = 0; i < jweKeyStore.length; i++) { | ||
for(let i = 0; i < jweKeyStore.length; i++) { | ||
if (jweKeyStore[i].kty === algKtyMapper[header.alg]) { | ||
var result = decryptCEKHelper(header.alg, encrypted_cek, jweKeyStore[i], log); | ||
const result = decryptCEKHelper(header.alg, encrypted_cek, jweKeyStore[i], log); | ||
if (result.cek) { | ||
@@ -391,3 +374,3 @@ return result; | ||
return { 'error': new Error('tried all keys to decrypt cek but none of them works'), 'cek': null }; | ||
return { "error": new Error("tried all keys to decrypt cek but none of them works"), "cek": null }; | ||
}; | ||
@@ -406,21 +389,21 @@ | ||
*/ | ||
var decryptContent = (header, cek, cipherText, iv, authTag, aad, log) => { | ||
const decryptContent = (header, cek, cipherText, iv, authTag, aad) => { | ||
if (!header.enc) | ||
return { 'error': new Error('enc is missing in JWE header'), 'content': null }; | ||
if (['A128GCM', 'A256GCM', 'A128CBC-HS256', 'A192CBC-HS384', 'A256CBC-HS512'].indexOf(header.enc) === -1) | ||
return { 'error' : new Error('Unsupported enc in JWE header: ' + header.enc), 'content': null }; | ||
return { "error": new Error("enc is missing in JWE header"), "content": null }; | ||
if (["A128GCM", "A256GCM", "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"].indexOf(header.enc) === -1) | ||
return { "error" : new Error("Unsupported enc in JWE header: " + header.enc), "content": null }; | ||
var mapper = { | ||
'A128GCM': 'aes-128-gcm', | ||
'A256GCM': 'aes-256-gcm', | ||
'A128CBC-HS256': 'aes-128-cbc-hmac-sha-256', | ||
'A192CBC-HS384': 'aes-192-cbc-hmac-sha-384', | ||
'A256CBC-HS512': 'aes-256-cbc-hmac-sha-512' | ||
const mapper = { | ||
"A128GCM": "aes-128-gcm", | ||
"A256GCM": "aes-256-gcm", | ||
"A128CBC-HS256": "aes-128-cbc-hmac-sha-256", | ||
"A192CBC-HS384": "aes-192-cbc-hmac-sha-384", | ||
"A256CBC-HS512": "aes-256-cbc-hmac-sha-512" | ||
}; | ||
try { | ||
var content = null; | ||
let content = null; | ||
if (header.enc === 'A128GCM' || header.enc === 'A256GCM') { | ||
var decipher = crypto.createDecipheriv(mapper[header.enc], cek, iv); | ||
if (header.enc === "A128GCM" || header.enc === "A256GCM") { | ||
const decipher = crypto.createDecipheriv(mapper[header.enc], cek, iv); | ||
decipher.setAAD(aad); | ||
@@ -434,5 +417,5 @@ decipher.setAuthTag(authTag); | ||
return { 'error': null, 'content': content.toString() }; | ||
return { "error": null, "content": content.toString() }; | ||
} catch (ex) { | ||
return { 'error': ex, 'content': null }; | ||
return { "error": ex, "content": null }; | ||
} | ||
@@ -452,11 +435,11 @@ }; | ||
*/ | ||
var decryptContentForDir = (header, jweKeyStore, cipherText, iv, authTag, aad, log) => { | ||
var key = null; | ||
const decryptContentForDir = (header, jweKeyStore, cipherText, iv, authTag, aad, log) => { | ||
let key = null; | ||
// try the key with the corresponding kid | ||
if (header.kid) { | ||
for (var i = 0; i < jweKeyStore.length; i++) { | ||
if (header.kid === jweKeyStore[i].kid && jweKeyStore[i].kty === 'oct') { | ||
for (let i = 0; i < jweKeyStore.length; i++) { | ||
if (header.kid === jweKeyStore[i].kid && jweKeyStore[i].kty === "oct") { | ||
key = jweKeyStore[i]; | ||
log.info('Decrypting JWE content, header.alg == dir, found a key matching kid: ' + header.kid); | ||
log.info("Decrypting JWE content, header.alg == dir, found a key matching kid: " + header.kid); | ||
break; | ||
@@ -469,11 +452,11 @@ } | ||
else | ||
return { 'error': new Error('cannot find a key matching kid: ' + header.kid), 'cek': null }; | ||
return { "error": new Error("cannot find a key matching kid: " + header.kid), "cek": null }; | ||
} | ||
log.info('In decryptContentForDir: kid is not provided in JWE header, now we try all the possible keys'); | ||
log.info("In decryptContentForDir: kid is not provided in JWE header, now we try all the possible keys"); | ||
// kid matching failed, now we try every possible key | ||
for(var i = 0; i < jweKeyStore.length; i++) { | ||
if (jweKeyStore[i].kty === 'oct') { | ||
var result = decryptContent(header, base64url.toBuffer(jweKeyStore[i].k), cipherText, iv, authTag, aad, log); | ||
for(let i = 0; i < jweKeyStore.length; i++) { | ||
if (jweKeyStore[i].kty === "oct") { | ||
const result = decryptContent(header, base64url.toBuffer(jweKeyStore[i].k), cipherText, iv, authTag, aad, log); | ||
if (!result.error) | ||
@@ -484,4 +467,4 @@ return result; | ||
log.info('In decryptContentForDir: tried all keys to decrypt the content but all failed'); | ||
return { error: new Error('In decryptContentForDir: tried all keys to decrypt the content but all failed'), content_result: null }; | ||
log.info("In decryptContentForDir: tried all keys to decrypt the content but all failed"); | ||
return { error: new Error("In decryptContentForDir: tried all keys to decrypt the content but all failed"), content_result: null }; | ||
}; | ||
@@ -498,5 +481,5 @@ | ||
exports.decrypt = (jweString, jweKeyStore, log, callback) => { | ||
/**************************************************************************** | ||
* JWE compact format structure | ||
**************************************************************************** | ||
/** | ||
* JWE compact format structure | ||
* | ||
* BASE64URL(UTF8(JWE Protected Header)) || '.' || | ||
@@ -507,33 +490,33 @@ * BASE64URL(JWE Encrypted Key) || '.' || | ||
* BASE64URL(JWE Authentication Tag) | ||
***************************************************************************/ | ||
var parts = jweString.split('.'); | ||
*/ | ||
const parts = jweString.split("."); | ||
if (parts.length !== 5) | ||
return callback(new Error('In jwe.decrypt: invalid JWE string, it has ' + parts.length + ' parts instead of 5'), null); | ||
return callback(new Error("In jwe.decrypt: invalid JWE string, it has " + parts.length + " parts instead of 5"), null); | ||
var header; | ||
let header; | ||
try { | ||
header = JSON.parse(base64url.decode(parts[0], 'binary')); | ||
header = JSON.parse(base64url.decode(parts[0], "binary")); | ||
} catch (ex) { | ||
return callback(new Error('In jwe.decrypt: failed to parse JWE header'), null); | ||
return callback(new Error("In jwe.decrypt: failed to parse JWE header"), null); | ||
} | ||
var aad = createBuffer(parts[0]); | ||
var encrypted_cek = base64url.toBuffer(parts[1]); | ||
var iv = base64url.toBuffer(parts[2]); | ||
var cipherText = base64url.toBuffer(parts[3]); | ||
var authTag = base64url.toBuffer(parts[4]); | ||
const aad = createBuffer(parts[0]); | ||
const encrypted_cek = base64url.toBuffer(parts[1]); | ||
const iv = base64url.toBuffer(parts[2]); | ||
const cipherText = base64url.toBuffer(parts[3]); | ||
const authTag = base64url.toBuffer(parts[4]); | ||
log.info('In jwe.decrypt: the header is ' + JSON.stringify(header)); | ||
log.info("In jwe.decrypt: the header is " + JSON.stringify(header)); | ||
var cek_result; | ||
var content_result; | ||
let cek_result; | ||
let content_result; | ||
// special treatment of 'dir' | ||
if (header.alg === 'dir') { | ||
if (header.alg === "dir") { | ||
content_result = decryptContentForDir(header, jweKeyStore, cipherText, iv, authTag, aad, log); | ||
} else { | ||
/**************************************************************************** | ||
/** | ||
* cek decryption | ||
***************************************************************************/ | ||
var cek_result = decryptCEK(header, encrypted_cek, jweKeyStore, log); | ||
*/ | ||
cek_result = decryptCEK(header, encrypted_cek, jweKeyStore, log); | ||
if (cek_result.error) { | ||
@@ -543,9 +526,9 @@ if (cek_result.error === "converting jwk to RSA privatePemKey to decrypt cek") { | ||
let cek; | ||
if (header.alg === 'RSA1_5') | ||
if (header.alg === "RSA1_5") | ||
cek = crypto.privateDecrypt({ key: pemKey.toPEM(true), padding: constants.RSA_PKCS1_PADDING }, encrypted_cek); | ||
else if (header.alg === 'RSA-OAEP') | ||
else if (header.alg === "RSA-OAEP") | ||
cek = crypto.privateDecrypt({ key: pemKey.toPEM(true), padding: constants.RSA_PKCS1_OAEP_PADDING }, encrypted_cek); | ||
var decryptedPemResult = decryptContent(header, cek, cipherText, iv, authTag, aad, log); | ||
const decryptedPemResult = decryptContent(header, cek, cipherText, iv, authTag, aad, log); | ||
if (!decryptedPemResult.error) { | ||
log.info('In jwe.decrypt: successfully decrypted id_token'); | ||
log.info("In jwe.decrypt: successfully decrypted id_token"); | ||
} | ||
@@ -559,10 +542,10 @@ return callback(decryptedPemResult.error, decryptedPemResult.content); | ||
/**************************************************************************** | ||
/** | ||
* content decryption | ||
***************************************************************************/ | ||
var content_result = decryptContent(header, cek_result.cek, cipherText, iv, authTag, aad, log); | ||
*/ | ||
content_result = decryptContent(header, cek_result.cek, cipherText, iv, authTag, aad, log); | ||
} | ||
if (!content_result.error) { | ||
log.info('In jwe.decrypt: successfully decrypted id_token'); | ||
log.info("In jwe.decrypt: successfully decrypted id_token"); | ||
} | ||
@@ -575,3 +558,1 @@ | ||
@@ -1,27 +0,10 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the 'Software'), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
const bunyan = require('bunyan'); | ||
"use strict"; | ||
const bunyan = require("bunyan"); | ||
function getLogger(name) { | ||
@@ -32,8 +15,8 @@ const log = bunyan.createLogger({ | ||
stream: process.stderr, | ||
level: 'error', | ||
name: 'error', | ||
level: "error", | ||
name: "error", | ||
}, { | ||
stream: process.stdout, | ||
level: 'warn', | ||
name: 'console', | ||
level: "warn", | ||
name: "console", | ||
}], | ||
@@ -40,0 +23,0 @@ }); |
@@ -1,44 +0,26 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the 'Software'), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
const request = require('request'); | ||
const async = require('async'); | ||
const aadutils = require('./aadutils'); | ||
const HttpsProxyAgent = require('https-proxy-agent'); | ||
const Log = require('./logging').getLogger; | ||
const request = require("request"); | ||
const async = require("async"); | ||
const aadutils = require("./aadutils"); | ||
const HttpsProxyAgent = require("https-proxy-agent"); | ||
const Log = require("./logging").getLogger; | ||
const log = new Log('AzureAD: Metadata Parser'); | ||
const log = new Log("AzureAD: Metadata Parser"); | ||
function Metadata(url, authtype, options) { | ||
if (!url) { | ||
throw new Error('Metadata: url is a required argument'); | ||
throw new Error("Metadata: url is a required argument"); | ||
} | ||
if (!authtype || authtype !== 'oidc') { | ||
throw new Error(`Invalid authtype. authtype must be 'oidc'`); | ||
if (!authtype || authtype !== "oidc") { | ||
throw new Error("Invalid authtype. authtype must be 'oidc'"); | ||
} | ||
// if logging level specified, switch to it. | ||
if (options.loggingLevel) { log.levels('console', options.loggingLevel); } | ||
if (options.loggingLevel) { log.levels("console", options.loggingLevel); } | ||
@@ -55,3 +37,3 @@ this.url = url; | ||
Object.defineProperty(Metadata, 'url', { | ||
Object.defineProperty(Metadata, "url", { | ||
get: function getUrl() { | ||
@@ -62,3 +44,3 @@ return this.url; | ||
Object.defineProperty(Metadata, 'oidc', { | ||
Object.defineProperty(Metadata, "oidc", { | ||
get: function getOidc() { | ||
@@ -69,3 +51,3 @@ return this.oidc; | ||
Object.defineProperty(Metadata, 'metadata', { | ||
Object.defineProperty(Metadata, "metadata", { | ||
get: function getMetadata() { | ||
@@ -76,3 +58,3 @@ return this.metadata; | ||
Object.defineProperty(Metadata, 'httpsProxyAgent', { | ||
Object.defineProperty(Metadata, "httpsProxyAgent", { | ||
get: function getHttpsProxyAgent() { | ||
@@ -84,7 +66,7 @@ return this.httpsProxyAgent; | ||
Metadata.prototype.updateOidcMetadata = function updateOidcMetadata(doc, next) { | ||
log.info('Request to update the Open ID Connect Metadata'); | ||
log.info("Request to update the Open ID Connect Metadata"); | ||
const self = this; | ||
var oidc = {}; | ||
const oidc = {}; | ||
oidc.algorithms = doc.id_token_signing_alg_values_supported; | ||
@@ -101,9 +83,9 @@ oidc.authorization_endpoint = doc.authorization_endpoint; | ||
if (!self.loggingNoPII) { | ||
log.info('Algorithm retrieved was: ', self.oidc.algorithms); | ||
log.info('Issuer we are using is: ', self.oidc.issuer); | ||
log.info('Key Endpoint we will use is: ', jwksUri); | ||
log.info('Authentication endpoint we will use is: ', self.oidc.authorization_endpoint); | ||
log.info('Token endpoint we will use is: ', self.oidc.token_endpoint); | ||
log.info('User info endpoint we will use is: ', self.oidc.userinfo_endpoint); | ||
log.info('The logout endpoint we will use is: ', self.oidc.end_session_endpoint); | ||
log.info("Algorithm retrieved was: ", self.oidc.algorithms); | ||
log.info("Issuer we are using is: ", self.oidc.issuer); | ||
log.info("Key Endpoint we will use is: ", jwksUri); | ||
log.info("Authentication endpoint we will use is: ", self.oidc.authorization_endpoint); | ||
log.info("Token endpoint we will use is: ", self.oidc.token_endpoint); | ||
log.info("User info endpoint we will use is: ", self.oidc.userinfo_endpoint); | ||
log.info("The logout endpoint we will use is: ", self.oidc.end_session_endpoint); | ||
} | ||
@@ -131,7 +113,7 @@ | ||
if (!kid) { | ||
throw new Error('kid is missing'); | ||
throw new Error("kid is missing"); | ||
} | ||
if (!keys) { | ||
throw new Error('keys is missing'); | ||
throw new Error("keys is missing"); | ||
} | ||
@@ -141,5 +123,5 @@ | ||
if (self.loggingNoPII) | ||
log.info('working on key'); | ||
log.info("working on key"); | ||
else | ||
log.info('working on key:', key); | ||
log.info("working on key:", key); | ||
@@ -154,5 +136,5 @@ // are we working on the right key? | ||
if (self.loggingNoPII) | ||
log.warn('modulus is empty; corrupt key'); | ||
log.warn("modulus is empty; corrupt key"); | ||
else | ||
log.warn('modulus is empty; corrupt key', key); | ||
log.warn("modulus is empty; corrupt key", key); | ||
return false; | ||
@@ -164,5 +146,5 @@ } | ||
if (self.loggingNoPII) | ||
log.warn('exponent is empty; corrupt key'); | ||
log.warn("exponent is empty; corrupt key"); | ||
else | ||
log.warn('exponent is empty; corrupt key', key); | ||
log.warn("exponent is empty; corrupt key", key); | ||
return false; | ||
@@ -180,5 +162,5 @@ } | ||
if (self.loggingNoPII) | ||
throw new Error('a key with the specific kid cannot be found'); | ||
throw new Error("a key with the specific kid cannot be found"); | ||
else | ||
throw new Error(`a key with kid %s cannot be found`, kid); | ||
throw new Error("a key with kid %s cannot be found", kid); | ||
} | ||
@@ -188,5 +170,5 @@ | ||
if (self.loggingNoPII) | ||
throw new Error('generating public key pem failed'); | ||
throw new Error("generating public key pem failed"); | ||
else | ||
throw new Error(`generating public key pem failed for kid: %s`, kid); | ||
throw new Error("generating public key pem failed for kid: %s", kid); | ||
} | ||
@@ -209,6 +191,6 @@ | ||
if (self.loggingNoPII) { | ||
log.error('cannot get AAD Federation metadata from endpoint you specified'); | ||
return next(new Error('Cannot get AAD Federation metadata')); | ||
log.error("cannot get AAD Federation metadata from endpoint you specified"); | ||
return next(new Error("Cannot get AAD Federation metadata")); | ||
} else { | ||
log.error('Cannot get AAD Federation metadata from endpoint you specified', self.url); | ||
log.error("Cannot get AAD Federation metadata from endpoint you specified", self.url); | ||
return next(new Error(`Error: ${response.statusCode} Cannot get AAD Federation metadata | ||
@@ -224,3 +206,3 @@ from ${self.url}`)); | ||
// use json parser for oidc authType | ||
log.info('Parsing JSON retreived from the endpoint'); | ||
log.info("Parsing JSON retreived from the endpoint"); | ||
self.metadata = JSON.parse(body); | ||
@@ -227,0 +209,0 @@ return next(null); |
@@ -1,25 +0,7 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the "Software"), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
@@ -31,3 +13,2 @@ /** | ||
/* | ||
@@ -47,17 +28,19 @@ * certificate string and private key string have the following format: | ||
/* eslint-disable max-len */ | ||
/* eslint-disable max-len, no-useless-escape*/ | ||
const CERT_REGEX = /-----BEGIN\sCERTIFICATE-----\s+([a-zA-Z0-9+\/=\r\n]+)\s+-----END\sCERTIFICATE-----/; | ||
const PRIVATE_KEY_REGEX = /-----BEGIN\sPRIVATE\sKEY-----\s+([a-zA-Z0-9+\/=\r\n]+)\s+-----END\sPRIVATE\sKEY-----/; | ||
/* eslint-enable max-len */ | ||
/* eslint-enable max-len, no-useless-escape*/ | ||
function getFirstCapturingGroup(str, regex) { | ||
if (typeof str !== 'string') { | ||
if (typeof str !== "string") { | ||
throw new Error(`'str' must be of type "String", actually is ${typeof str}`); | ||
} | ||
const matches = str.match(regex); | ||
// if str matches the regex (either CERT_REGEX or PRIVATE_KEY_REGEX), then | ||
// matches will always have two groups: | ||
// matches[0]: this is always the entire match, in this case, the certificate/key str | ||
// matches[1]: this is the first (and the only) group we want to capture in the regex, | ||
// which is the certifcate/key content | ||
/* | ||
* if str matches the regex (either CERT_REGEX or PRIVATE_KEY_REGEX), then | ||
* matches will always have two groups: | ||
* matches[0]: this is always the entire match, in this case, the certificate/key str | ||
* matches[1]: this is the first (and the only) group we want to capture in the regex, | ||
* which is the certifcate/key content | ||
*/ | ||
if (!Array.isArray(matches) || matches.length !== 2) { | ||
@@ -70,6 +53,6 @@ return null; | ||
function removeRN(str) { | ||
if (typeof str !== 'string') { | ||
if (typeof str !== "string") { | ||
throw new Error(`'str' must be of type "String", actually is ${typeof str}`); | ||
} | ||
return str.replace(/[\r\n]/g, ''); | ||
return str.replace(/[\r\n]/g, ""); | ||
} | ||
@@ -86,8 +69,8 @@ | ||
exports.certToPEM = function certToPEM(cert) { | ||
const BEGIN_CERT = '-----BEGIN CERTIFICATE-----'; | ||
const END_CERT = '-----END CERTIFICATE-----'; | ||
const BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; | ||
const END_CERT = "-----END CERTIFICATE-----"; | ||
return [BEGIN_CERT] | ||
.concat(cert.match(/.{1,64}/g)) | ||
.concat([END_CERT]) | ||
.join('\n'); | ||
.join("\n"); | ||
}; |
@@ -1,27 +0,9 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the "Software"), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use restrict'; | ||
"use restrict"; | ||
const aadutils = require('./aadutils'); | ||
const aadutils = require("./aadutils"); | ||
@@ -34,6 +16,6 @@ /* | ||
function SessionContentHandler(maxAmount, maxAge) { | ||
if (!maxAmount || (typeof maxAmount !== 'number' || maxAmount <= 0 || maxAmount % 1 !== 0)) | ||
throw new Error('SessionContentHandler: maxAmount must be a positive integer'); | ||
if (!maxAge || (typeof maxAge !== 'number' || maxAge <= 0)) | ||
throw new Error('SessionContentHandler: maxAge must be a positive number'); | ||
if (!maxAmount || (typeof maxAmount !== "number" || maxAmount <= 0 || maxAmount % 1 !== 0)) | ||
throw new Error("SessionContentHandler: maxAmount must be a positive integer"); | ||
if (!maxAge || (typeof maxAge !== "number" || maxAge <= 0)) | ||
throw new Error("SessionContentHandler: maxAge must be a positive number"); | ||
this.maxAge = maxAge; // seconds | ||
@@ -45,6 +27,6 @@ this.maxAmount = maxAmount; | ||
if (!req.session) | ||
throw new Error('OIDC strategy requires session support. Did you forget to use session middleware such as express-session?'); | ||
throw new Error("OIDC strategy requires session support. Did you forget to use session middleware such as express-session?"); | ||
// the array in session | ||
var array = req.session[sessionKey] && req.session[sessionKey]['content']; | ||
let array = req.session[sessionKey] && req.session[sessionKey]["content"]; | ||
if (!array) | ||
@@ -57,7 +39,7 @@ array = []; | ||
// find the tuple by state value | ||
var tuple = aadutils.findAndDeleteTupleByState(array, stateToFind); | ||
const tuple = aadutils.findAndDeleteTupleByState(array, stateToFind); | ||
// clear empty array, and clear the session if there is nothing inside | ||
if (req.session[sessionKey] && array.length === 0) | ||
delete req.session[sessionKey]['content']; | ||
delete req.session[sessionKey]["content"]; | ||
if (req.session[sessionKey] && Object.keys(req.session[sessionKey]).length === 0) | ||
@@ -74,6 +56,6 @@ delete req.session[sessionKey]; | ||
req.session[sessionKey] = {}; | ||
if (!req.session[sessionKey]['content']) | ||
req.session[sessionKey]['content'] = []; | ||
if (!req.session[sessionKey]["content"]) | ||
req.session[sessionKey]["content"] = []; | ||
var array = req.session[sessionKey]['content']; | ||
const array = req.session[sessionKey]["content"]; | ||
aadutils.processArray(array, this.maxAmount-1, this.maxAge); | ||
@@ -80,0 +62,0 @@ |
@@ -1,27 +0,9 @@ | ||
/** | ||
* Copyright (c) Microsoft Corporation | ||
* All Rights Reserved | ||
* MIT License | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
* software and associated documentation files (the "Software"), to deal in the Software | ||
* without restriction, including without limitation the rights to use, copy, modify, | ||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to the following | ||
* conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
const UrlValidator = require('valid-url'); | ||
const UrlValidator = require("valid-url"); | ||
@@ -38,3 +20,3 @@ const types = {}; | ||
Object.keys(this.config).forEach((item) => { | ||
if (!this.config.hasOwnProperty(item)) { | ||
if (!Object.prototype.hasOwnProperty.call(this.config, item)) { | ||
throw new TypeError(`Missing value for ${item}`); | ||
@@ -57,27 +39,27 @@ } | ||
Validator.isNonEmpty = 'isNonEmpty'; | ||
Validator.isNonEmpty = "isNonEmpty"; | ||
types.isNonEmpty = { | ||
validate: (value) => { | ||
return value !== '' && value !== undefined && value !== null; | ||
return value !== "" && value !== undefined && value !== null; | ||
}, | ||
error: 'The value cannot be empty', | ||
error: "The value cannot be empty", | ||
}; | ||
Validator.isTypeLegal = 'isTypeLegal'; | ||
Validator.isTypeLegal = "isTypeLegal"; | ||
types.isTypeLegal = { | ||
validate: (value) => { | ||
return value === 'id_token' || value === 'id_token code' || value === 'code id_token' || value === 'code'; | ||
return value === "id_token" || value === "id_token code" || value === "code id_token" || value === "code"; | ||
}, | ||
error: 'The responseType: must be either id_token, id_token code, code id_token or code.', | ||
error: "The responseType: must be either id_token, id_token code, code id_token or code.", | ||
}; | ||
Validator.isModeLegal = 'isModeLegal'; | ||
Validator.isModeLegal = "isModeLegal"; | ||
types.isModeLegal = { | ||
validate: (value) => { | ||
return value === 'query' || value === 'form_post'; | ||
return value === "query" || value === "form_post"; | ||
}, | ||
error: 'The responseMode: must be either query or form_post.', | ||
error: "The responseMode: must be either query or form_post.", | ||
}; | ||
Validator.isURL = 'isURL'; | ||
Validator.isURL = "isURL"; | ||
types.isURL = { | ||
@@ -87,6 +69,6 @@ validate: (value) => { | ||
}, | ||
error: 'The URL must be valid and be https:// or http://', | ||
error: "The URL must be valid and be https:// or http://", | ||
}; | ||
Validator.isHttpURL = 'isHttpURL'; | ||
Validator.isHttpURL = "isHttpURL"; | ||
types.isHttpURL = { | ||
@@ -96,6 +78,6 @@ validate: (value) => { | ||
}, | ||
error: 'The URL must be valid and be http://', | ||
error: "The URL must be valid and be http://", | ||
}; | ||
Validator.isHttpsURL = 'isHttpsURL'; | ||
Validator.isHttpsURL = "isHttpsURL"; | ||
types.isHttpsURL = { | ||
@@ -105,6 +87,6 @@ validate: (value) => { | ||
}, | ||
error: 'The URL must be valid and be https://', | ||
error: "The URL must be valid and be https://", | ||
}; | ||
Validator.isHttpsURLIfExists = 'isHttpsURLIfExists'; | ||
Validator.isHttpsURLIfExists = "isHttpsURLIfExists"; | ||
types.isHttpsURLIfExists = { | ||
@@ -117,5 +99,5 @@ validate: (value) => { | ||
}, | ||
error: 'The URL must be valid and be https://', | ||
error: "The URL must be valid and be https://", | ||
}; | ||
exports.Validator = Validator; |
{ | ||
"name": "passport-azure-ad", | ||
"version": "4.3.1-beta.0", | ||
"version": "4.3.1", | ||
"license": "MIT", | ||
@@ -28,4 +28,7 @@ "keywords": [ | ||
"chai-passport-strategy": "1.x.x", | ||
"eslint": "^7.32.0", | ||
"eslint-plugin-header": "^3.1.1", | ||
"eslint-plugin-security": "^1.4.0", | ||
"grunt": "^1.0.1", | ||
"grunt-contrib-nodeunit": "^2.1.0", | ||
"grunt-contrib-nodeunit": "^3.0.0", | ||
"grunt-mocha-test": "^0.12.7", | ||
@@ -40,3 +43,3 @@ "mocha": "^5.2.0", | ||
"cache-manager": "2.10.2", | ||
"https-proxy-agent": "^2.2.2", | ||
"https-proxy-agent": "^5.0.0", | ||
"jws": "^3.1.3", | ||
@@ -46,3 +49,3 @@ "lodash": "^4.11.2", | ||
"oauth": "0.9.15", | ||
"passport": "^0.3.2", | ||
"passport": "^0.4.1", | ||
"request": "^2.72.0", | ||
@@ -52,3 +55,4 @@ "valid-url": "^1.0.6" | ||
"scripts": { | ||
"test": "grunt run_tests" | ||
"test": "grunt run_tests", | ||
"lint": "./node_modules/.bin/eslint lib/**" | ||
}, | ||
@@ -55,0 +59,0 @@ "engines": { |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
516209
34
10021
1
10
3
+ Addedagent-base@6.0.2(transitive)
+ Addeddebug@4.3.7(transitive)
+ Addedhttps-proxy-agent@5.0.1(transitive)
+ Addedpassport@0.4.1(transitive)
- Removedagent-base@4.3.0(transitive)
- Removeddebug@3.2.7(transitive)
- Removedes6-promisify@5.0.0(transitive)
- Removedhttps-proxy-agent@2.2.4(transitive)
- Removedpassport@0.3.2(transitive)
Updatedhttps-proxy-agent@^5.0.0
Updatedpassport@^0.4.1