msal-iframe-ok
Version:
Fork to allow silent renewal in iFrame of Microsoft Authentication Library for js
637 lines • 23.9 kB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as tslib_1 from "tslib";
import { Constants, SSOTypes } from "./Constants";
import { ClientAuthError } from "./error/ClientAuthError";
import { Library } from "./Constants";
import { Base64 } from "js-base64";
/**
* @hidden
*/
var Utils = /** @class */ (function () {
function Utils() {
}
//#region General Util
/**
* Utils function to compare two Account objects - used to check if the same user account is logged in
*
* @param a1: Account object
* @param a2: Account object
*/
Utils.compareAccounts = function (a1, a2) {
if (!a1 || !a2) {
return false;
}
if (a1.homeAccountIdentifier && a2.homeAccountIdentifier) {
if (a1.homeAccountIdentifier === a2.homeAccountIdentifier) {
return true;
}
}
return false;
};
/**
* Decimal to Hex
*
* @param num
*/
Utils.decimalToHex = function (num) {
var hex = num.toString(16);
while (hex.length < 2) {
hex = "0" + hex;
}
return hex;
};
/**
* MSAL JS Library Version
*/
Utils.getLibraryVersion = function () {
return Library.version;
};
/**
* Creates a new random GUID - used to populate state?
* @returns string (GUID)
*/
Utils.createNewGuid = function () {
// RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
// pseudo-random numbers.
// The algorithm is as follows:
// Set the two most significant bits (bits 6 and 7) of the
// clock_seq_hi_and_reserved to zero and one, respectively.
// Set the four most significant bits (bits 12 through 15) of the
// time_hi_and_version field to the 4-bit version number from
// Section 4.1.3. Version4
// Set all the other bits to randomly (or pseudo-randomly) chosen
// values.
// UUID = time-low "-" time-mid "-"time-high-and-version "-"clock-seq-reserved and low(2hexOctet)"-" node
// time-low = 4hexOctet
// time-mid = 2hexOctet
// time-high-and-version = 2hexOctet
// clock-seq-and-reserved = hexOctet:
// clock-seq-low = hexOctet
// node = 6hexOctet
// Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
// y could be 1000, 1001, 1010, 1011 since most significant two bits needs to be 10
// y values are 8, 9, A, B
var cryptoObj = window.crypto; // for IE 11
if (cryptoObj && cryptoObj.getRandomValues) {
var buffer = new Uint8Array(16);
cryptoObj.getRandomValues(buffer);
//buffer[6] and buffer[7] represents the time_hi_and_version field. We will set the four most significant bits (4 through 7) of buffer[6] to represent decimal number 4 (UUID version number).
buffer[6] |= 0x40; //buffer[6] | 01000000 will set the 6 bit to 1.
buffer[6] &= 0x4f; //buffer[6] & 01001111 will set the 4, 5, and 7 bit to 0 such that bits 4-7 == 0100 = "4".
//buffer[8] represents the clock_seq_hi_and_reserved field. We will set the two most significant bits (6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively.
buffer[8] |= 0x80; //buffer[8] | 10000000 will set the 7 bit to 1.
buffer[8] &= 0xbf; //buffer[8] & 10111111 will set the 6 bit to 0.
return Utils.decimalToHex(buffer[0]) + Utils.decimalToHex(buffer[1])
+ Utils.decimalToHex(buffer[2]) + Utils.decimalToHex(buffer[3])
+ "-" + Utils.decimalToHex(buffer[4]) + Utils.decimalToHex(buffer[5])
+ "-" + Utils.decimalToHex(buffer[6]) + Utils.decimalToHex(buffer[7])
+ "-" + Utils.decimalToHex(buffer[8]) + Utils.decimalToHex(buffer[9])
+ "-" + Utils.decimalToHex(buffer[10]) + Utils.decimalToHex(buffer[11])
+ Utils.decimalToHex(buffer[12]) + Utils.decimalToHex(buffer[13])
+ Utils.decimalToHex(buffer[14]) + Utils.decimalToHex(buffer[15]);
}
else {
var guidHolder = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
var hex = "0123456789abcdef";
var r = 0;
var guidResponse = "";
for (var i = 0; i < 36; i++) {
if (guidHolder[i] !== "-" && guidHolder[i] !== "4") {
// each x and y needs to be random
r = Math.random() * 16 | 0;
}
if (guidHolder[i] === "x") {
guidResponse += hex[r];
}
else if (guidHolder[i] === "y") {
// clock-seq-and-reserved first hex is filtered and remaining hex values are random
r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0??
r |= 0x8; // set pos 3 to 1 as 1???
guidResponse += hex[r];
}
else {
guidResponse += guidHolder[i];
}
}
return guidResponse;
}
};
//#endregion
//#region Time
/**
* Returns time in seconds for expiration based on string value passed in.
*
* @param expires
*/
Utils.expiresIn = function (expires) {
// if AAD did not send "expires_in" property, use default expiration of 3599 seconds, for some reason AAD sends 3599 as "expires_in" value instead of 3600
if (!expires) {
expires = "3599";
}
return this.now() + parseInt(expires, 10);
};
/**
* return the current time in Unix time. Date.getTime() returns in milliseconds.
*/
Utils.now = function () {
return Math.round(new Date().getTime() / 1000.0);
};
//#endregion
//#region String Ops
/**
* Check if a string is empty
*
* @param str
*/
Utils.isEmpty = function (str) {
return (typeof str === "undefined" || !str || 0 === str.length);
};
//#endregion
//#region Token Processing (Extract to TokenProcessing.ts)
/**
* decode a JWT
*
* @param jwtToken
*/
Utils.decodeJwt = function (jwtToken) {
if (this.isEmpty(jwtToken)) {
return null;
}
var idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/;
var matches = idTokenPartsRegex.exec(jwtToken);
if (!matches || matches.length < 4) {
//this._requestContext.logger.warn("The returned id_token is not parseable.");
return null;
}
var crackedToken = {
header: matches[1],
JWSPayload: matches[2],
JWSSig: matches[3]
};
return crackedToken;
};
/**
* Extract IdToken by decoding the RAWIdToken
*
* @param encodedIdToken
*/
Utils.extractIdToken = function (encodedIdToken) {
// id token will be decoded to get the username
var decodedToken = this.decodeJwt(encodedIdToken);
if (!decodedToken) {
return null;
}
try {
var base64IdToken = decodedToken.JWSPayload;
var base64Decoded = this.base64DecodeStringUrlSafe(base64IdToken);
if (!base64Decoded) {
//this._requestContext.logger.info("The returned id_token could not be base64 url safe decoded.");
return null;
}
// ECMA script has JSON built-in support
return JSON.parse(base64Decoded);
}
catch (err) {
//this._requestContext.logger.error("The returned id_token could not be decoded" + err);
}
return null;
};
//#endregion
//#region Encode and Decode
/**
* encoding string to base64 - platform specific check
*
* @param input
*/
Utils.base64EncodeStringUrlSafe = function (input) {
// html5 should support atob function for decoding
return Base64.encode(input);
};
/**
* decoding base64 token - platform specific check
*
* @param base64IdToken
*/
Utils.base64DecodeStringUrlSafe = function (base64IdToken) {
// html5 should support atob function for decoding
base64IdToken = base64IdToken.replace(/-/g, "+").replace(/_/g, "/");
return decodeURIComponent(encodeURIComponent(Base64.decode(base64IdToken))); // jshint ignore:line
};
/**
* base64 encode a string
*
* @param input
*/
// TODO: Rename to specify type of encoding
Utils.encode = function (input) {
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = this.utf8Encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
}
else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
}
return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
};
/**
* utf8 encode a string
*
* @param input
*/
Utils.utf8Encode = function (input) {
input = input.replace(/\r\n/g, "\n");
var utftext = "";
for (var n = 0; n < input.length; n++) {
var c = input.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
/**
* decode a base64 token string
*
* @param base64IdToken
*/
// TODO: Rename to specify type of encoding
Utils.decode = function (base64IdToken) {
var codes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
base64IdToken = String(base64IdToken).replace(/=+$/, "");
var length = base64IdToken.length;
if (length % 4 === 1) {
throw ClientAuthError.createTokenEncodingError(base64IdToken);
}
var h1, h2, h3, h4, bits, c1, c2, c3, decoded = "";
for (var i = 0; i < length; i += 4) {
//Every 4 base64 encoded character will be converted to 3 byte string, which is 24 bits
// then 6 bits per base64 encoded character
h1 = codes.indexOf(base64IdToken.charAt(i));
h2 = codes.indexOf(base64IdToken.charAt(i + 1));
h3 = codes.indexOf(base64IdToken.charAt(i + 2));
h4 = codes.indexOf(base64IdToken.charAt(i + 3));
// For padding, if last two are "="
if (i + 2 === length - 1) {
bits = h1 << 18 | h2 << 12 | h3 << 6;
c1 = bits >> 16 & 255;
c2 = bits >> 8 & 255;
decoded += String.fromCharCode(c1, c2);
break;
}
// if last one is "="
else if (i + 1 === length - 1) {
bits = h1 << 18 | h2 << 12;
c1 = bits >> 16 & 255;
decoded += String.fromCharCode(c1);
break;
}
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
// then convert to 3 byte chars
c1 = bits >> 16 & 255;
c2 = bits >> 8 & 255;
c3 = bits & 255;
decoded += String.fromCharCode(c1, c2, c3);
}
return decoded;
};
/**
* deserialize a string
*
* @param query
*/
Utils.deserialize = function (query) {
var match; // Regex for replacing addition symbol with a space
var pl = /\+/g;
var search = /([^&=]+)=([^&]*)/g;
var decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); };
var obj = {};
match = search.exec(query);
while (match) {
obj[decode(match[1])] = decode(match[2]);
match = search.exec(query);
}
return obj;
};
//#endregion
//#region Scopes (extract to Scopes.ts)
/**
* Check if there are dup scopes in a given request
*
* @param cachedScopes
* @param scopes
*/
// TODO: Rename this, intersecting scopes isn't a great name for duplicate checker
Utils.isIntersectingScopes = function (cachedScopes, scopes) {
cachedScopes = this.convertToLowerCase(cachedScopes);
for (var i = 0; i < scopes.length; i++) {
if (cachedScopes.indexOf(scopes[i].toLowerCase()) > -1) {
return true;
}
}
return false;
};
/**
* Check if a given scope is present in the request
*
* @param cachedScopes
* @param scopes
*/
Utils.containsScope = function (cachedScopes, scopes) {
cachedScopes = this.convertToLowerCase(cachedScopes);
return scopes.every(function (value) { return cachedScopes.indexOf(value.toString().toLowerCase()) >= 0; });
};
/**
* toLower
*
* @param scopes
*/
// TODO: Rename this, too generic name for a function that only deals with scopes
Utils.convertToLowerCase = function (scopes) {
return scopes.map(function (scope) { return scope.toLowerCase(); });
};
/**
* remove one element from a scope array
*
* @param scopes
* @param scope
*/
// TODO: Rename this, too generic name for a function that only deals with scopes
Utils.removeElement = function (scopes, scope) {
return scopes.filter(function (value) { return value !== scope; });
};
//#endregion
//#region URL Processing (Extract to UrlProcessing.ts?)
Utils.getDefaultRedirectUri = function () {
return window.location.href.split("?")[0].split("#")[0];
};
/**
* Given a url like https://a:b/common/d?e=f#g, and a tenantId, returns https://a:b/tenantId/d
* @param href The url
* @param tenantId The tenant id to replace
*/
Utils.replaceTenantPath = function (url, tenantId) {
url = url.toLowerCase();
var urlObject = this.GetUrlComponents(url);
var pathArray = urlObject.PathSegments;
if (tenantId && (pathArray.length !== 0 && (pathArray[0] === Constants.common || pathArray[0] === SSOTypes.ORGANIZATIONS))) {
pathArray[0] = tenantId;
}
return this.constructAuthorityUriFromObject(urlObject, pathArray);
};
Utils.constructAuthorityUriFromObject = function (urlObject, pathArray) {
return this.CanonicalizeUri(urlObject.Protocol + "//" + urlObject.HostNameAndPort + "/" + pathArray.join("/"));
};
/**
* Parses out the components from a url string.
* @returns An object with the various components. Please cache this value insted of calling this multiple times on the same url.
*/
Utils.GetUrlComponents = function (url) {
if (!url) {
throw "Url required";
}
// https://gist.github.com/curtisz/11139b2cfcaef4a261e0
var regEx = RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");
var match = url.match(regEx);
if (!match || match.length < 6) {
throw "Valid url required";
}
var urlComponents = {
Protocol: match[1],
HostNameAndPort: match[4],
AbsolutePath: match[5]
};
var pathSegments = urlComponents.AbsolutePath.split("/");
pathSegments = pathSegments.filter(function (val) { return val && val.length > 0; }); // remove empty elements
urlComponents.PathSegments = pathSegments;
return urlComponents;
};
/**
* Given a url or path, append a trailing slash if one doesnt exist
*
* @param url
*/
Utils.CanonicalizeUri = function (url) {
if (url) {
url = url.toLowerCase();
}
if (url && !Utils.endsWith(url, "/")) {
url += "/";
}
return url;
};
/**
* Checks to see if the url ends with the suffix
* Required because we are compiling for es5 instead of es6
* @param url
* @param str
*/
// TODO: Rename this, not clear what it is supposed to do
Utils.endsWith = function (url, suffix) {
if (!url || !suffix) {
return false;
}
return url.indexOf(suffix, url.length - suffix.length) !== -1;
};
/**
* Utils function to remove the login_hint and domain_hint from the i/p extraQueryParameters
* @param url
* @param name
*/
Utils.urlRemoveQueryStringParameter = function (url, name) {
if (this.isEmpty(url)) {
return url;
}
var regex = new RegExp("(\\&" + name + "=)[^\&]+");
url = url.replace(regex, "");
// name=value&
regex = new RegExp("(" + name + "=)[^\&]+&");
url = url.replace(regex, "");
// name=value
regex = new RegExp("(" + name + "=)[^\&]+");
url = url.replace(regex, "");
return url;
};
//#endregion
//#region ExtraQueryParameters Processing (Extract?)
/**
* Constructs extraQueryParameters to be sent to the server for the AuthenticationParameters set by the developer
* in any login() or acquireToken() calls
* @param idTokenObject
* @param extraQueryParameters
* @param sid
* @param loginHint
*/
//TODO: check how this behaves when domain_hint only is sent in extraparameters and idToken has no upn.
Utils.constructUnifiedCacheQueryParameter = function (request, idTokenObject) {
// preference order: account > sid > login_hint
var ssoType;
var ssoData;
var serverReqParam = {};
// if account info is passed, account.sid > account.login_hint
if (request) {
if (request.account) {
var account = request.account;
if (account.sid) {
ssoType = SSOTypes.SID;
ssoData = account.sid;
}
else if (account.userName) {
ssoType = SSOTypes.LOGIN_HINT;
ssoData = account.userName;
}
}
// sid from request
else if (request.sid) {
ssoType = SSOTypes.SID;
ssoData = request.sid;
}
// loginHint from request
else if (request.loginHint) {
ssoType = SSOTypes.LOGIN_HINT;
ssoData = request.loginHint;
}
}
// adalIdToken retrieved from cache
else if (idTokenObject) {
if (idTokenObject.hasOwnProperty(Constants.upn)) {
ssoType = SSOTypes.ID_TOKEN;
ssoData = idTokenObject.upn;
}
else {
ssoType = SSOTypes.ORGANIZATIONS;
ssoData = null;
}
}
serverReqParam = this.addSSOParameter(ssoType, ssoData);
// add the HomeAccountIdentifier info/ domain_hint
if (request && request.account && request.account.homeAccountIdentifier) {
serverReqParam = this.addSSOParameter(SSOTypes.HOMEACCOUNT_ID, request.account.homeAccountIdentifier, serverReqParam);
}
return serverReqParam;
};
/**
* Add SID to extraQueryParameters
* @param sid
*/
Utils.addSSOParameter = function (ssoType, ssoData, ssoParam) {
if (!ssoParam) {
ssoParam = {};
}
if (!ssoData) {
return ssoParam;
}
switch (ssoType) {
case SSOTypes.SID: {
ssoParam[SSOTypes.SID] = ssoData;
break;
}
case SSOTypes.ID_TOKEN: {
ssoParam[SSOTypes.LOGIN_HINT] = ssoData;
ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.ORGANIZATIONS;
break;
}
case SSOTypes.LOGIN_HINT: {
ssoParam[SSOTypes.LOGIN_HINT] = ssoData;
break;
}
case SSOTypes.ORGANIZATIONS: {
ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.ORGANIZATIONS;
break;
}
case SSOTypes.CONSUMERS: {
ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.CONSUMERS;
break;
}
case SSOTypes.HOMEACCOUNT_ID: {
var homeAccountId = ssoData.split(".");
var uid = Utils.base64DecodeStringUrlSafe(homeAccountId[0]);
var utid = Utils.base64DecodeStringUrlSafe(homeAccountId[1]);
// TODO: domain_req and login_req are not needed according to eSTS team
ssoParam[SSOTypes.LOGIN_REQ] = uid;
ssoParam[SSOTypes.DOMAIN_REQ] = utid;
if (utid === Constants.consumersUtid) {
ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.CONSUMERS;
}
else {
ssoParam[SSOTypes.DOMAIN_HINT] = SSOTypes.ORGANIZATIONS;
}
break;
}
case SSOTypes.LOGIN_REQ: {
ssoParam[SSOTypes.LOGIN_REQ] = ssoData;
break;
}
case SSOTypes.DOMAIN_REQ: {
ssoParam[SSOTypes.DOMAIN_REQ] = ssoData;
break;
}
}
return ssoParam;
};
/**
* Utility to generate a QueryParameterString from a Key-Value mapping of extraQueryParameters passed
* @param extraQueryParameters
*/
Utils.generateQueryParametersString = function (queryParameters) {
var paramsString = null;
if (queryParameters) {
Object.keys(queryParameters).forEach(function (key) {
if (paramsString == null) {
paramsString = key + "=" + encodeURIComponent(queryParameters[key]);
}
else {
paramsString += "&" + key + "=" + encodeURIComponent(queryParameters[key]);
}
});
}
return paramsString;
};
/**
* Check to see if there are SSO params set in the Request
* @param request
*/
Utils.isSSOParam = function (request) {
return request && (request.account || request.sid || request.loginHint);
};
//#endregion
//#region Response Helpers
Utils.setResponseIdToken = function (originalResponse, idToken) {
var response = tslib_1.__assign({}, originalResponse);
response.idToken = idToken;
if (response.idToken.objectId) {
response.uniqueId = response.idToken.objectId;
}
else {
response.uniqueId = response.idToken.subject;
}
response.tenantId = response.idToken.tenantId;
return response;
};
return Utils;
}());
export { Utils };
//# sourceMappingURL=Utils.js.map