@azure/msal-browser
Version:
Microsoft Authentication Library for js
507 lines (504 loc) • 28.3 kB
JavaScript
/*! @azure/msal-browser v2.28.1 2022-08-01 */
'use strict';
import { __extends, __awaiter, __generator, __assign } from '../_virtual/_tslib.js';
import { OIDC_DEFAULT_SCOPES, StringUtils, UrlString, Constants, AuthError, ThrottlingUtils, ProtocolUtils, PerformanceEvents } from '@azure/msal-common';
import { StandardInteractionClient } from './StandardInteractionClient.js';
import { EventType } from '../event/EventType.js';
import { BrowserConstants, InteractionType, ApiId } from '../utils/BrowserConstants.js';
import { BrowserUtils } from '../utils/BrowserUtils.js';
import { NativeInteractionClient } from './NativeInteractionClient.js';
import { NativeMessageHandler } from '../broker/nativeBroker/NativeMessageHandler.js';
import { BrowserAuthError } from '../error/BrowserAuthError.js';
import { InteractionHandler } from '../interaction_handler/InteractionHandler.js';
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var PopupClient = /** @class */ (function (_super) {
__extends(PopupClient, _super);
function PopupClient(config, storageImpl, browserCrypto, logger, eventHandler, navigationClient, performanceClient, nativeStorageImpl, nativeMessageHandler, correlationId) {
var _this = _super.call(this, config, storageImpl, browserCrypto, logger, eventHandler, navigationClient, performanceClient, nativeMessageHandler, correlationId) || this;
// Properly sets this reference for the unload event.
_this.unloadWindow = _this.unloadWindow.bind(_this);
_this.nativeStorage = nativeStorageImpl;
return _this;
}
/**
* Acquires tokens by opening a popup window to the /authorize endpoint of the authority
* @param request
*/
PopupClient.prototype.acquireToken = function (request) {
try {
var popupName = this.generatePopupName(request.scopes || OIDC_DEFAULT_SCOPES, request.authority || this.config.auth.authority);
var popupWindowAttributes = request.popupWindowAttributes || {};
// asyncPopups flag is true. Acquires token without first opening popup. Popup will be opened later asynchronously.
if (this.config.system.asyncPopups) {
this.logger.verbose("asyncPopups set to true, acquiring token");
// Passes on popup position and dimensions if in request
return this.acquireTokenPopupAsync(request, popupName, popupWindowAttributes);
}
else {
// asyncPopups flag is set to false. Opens popup before acquiring token.
this.logger.verbose("asyncPopup set to false, opening popup before acquiring token");
var popup = this.openSizedPopup("about:blank", popupName, popupWindowAttributes);
return this.acquireTokenPopupAsync(request, popupName, popupWindowAttributes, popup);
}
}
catch (e) {
return Promise.reject(e);
}
};
/**
* Clears local cache for the current user then opens a popup window prompting the user to sign-out of the server
* @param logoutRequest
*/
PopupClient.prototype.logout = function (logoutRequest) {
try {
this.logger.verbose("logoutPopup called");
var validLogoutRequest = this.initializeLogoutRequest(logoutRequest);
var popupName = this.generateLogoutPopupName(validLogoutRequest);
var authority = logoutRequest && logoutRequest.authority;
var mainWindowRedirectUri = logoutRequest && logoutRequest.mainWindowRedirectUri;
var popupWindowAttributes = (logoutRequest === null || logoutRequest === void 0 ? void 0 : logoutRequest.popupWindowAttributes) || {};
// asyncPopups flag is true. Acquires token without first opening popup. Popup will be opened later asynchronously.
if (this.config.system.asyncPopups) {
this.logger.verbose("asyncPopups set to true");
// Passes on popup position and dimensions if in request
return this.logoutPopupAsync(validLogoutRequest, popupName, popupWindowAttributes, authority, undefined, mainWindowRedirectUri);
}
else {
// asyncPopups flag is set to false. Opens popup before logging out.
this.logger.verbose("asyncPopup set to false, opening popup");
var popup = this.openSizedPopup("about:blank", popupName, popupWindowAttributes);
return this.logoutPopupAsync(validLogoutRequest, popupName, popupWindowAttributes, authority, popup, mainWindowRedirectUri);
}
}
catch (e) {
// Since this function is synchronous we need to reject
return Promise.reject(e);
}
};
/**
* Helper which obtains an access_token for your API via opening a popup window in the user's browser
* @param validRequest
* @param popupName
* @param popup
* @param popupWindowAttributes
*
* @returns A promise that is fulfilled when this function has completed, or rejected if an error was raised.
*/
PopupClient.prototype.acquireTokenPopupAsync = function (request, popupName, popupWindowAttributes, popup) {
return __awaiter(this, void 0, void 0, function () {
var serverTelemetryManager, validRequest, authCodeRequest, authClient, isNativeBroker, fetchNativeAccountIdMeasurement, navigateUrl, interactionHandler, popupParameters, popupWindow, hash, serverParams, state_1, nativeInteractionClient, userRequestState, result, e_1;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.logger.verbose("acquireTokenPopupAsync called");
serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.acquireTokenPopup);
return [4 /*yield*/, this.initializeAuthorizationRequest(request, InteractionType.Popup)];
case 1:
validRequest = _a.sent();
this.browserStorage.updateCacheEntries(validRequest.state, validRequest.nonce, validRequest.authority, validRequest.loginHint || Constants.EMPTY_STRING, validRequest.account || null);
_a.label = 2;
case 2:
_a.trys.push([2, 8, , 9]);
return [4 /*yield*/, this.initializeAuthorizationCodeRequest(validRequest)];
case 3:
authCodeRequest = _a.sent();
return [4 /*yield*/, this.createAuthCodeClient(serverTelemetryManager, validRequest.authority, validRequest.azureCloudOptions)];
case 4:
authClient = _a.sent();
this.logger.verbose("Auth code client created");
isNativeBroker = NativeMessageHandler.isNativeAvailable(this.config, this.logger, this.nativeMessageHandler, request.authenticationScheme);
fetchNativeAccountIdMeasurement = void 0;
if (isNativeBroker) {
fetchNativeAccountIdMeasurement = this.performanceClient.startMeasurement(PerformanceEvents.FetchAccountIdWithNativeBroker, request.correlationId);
}
return [4 /*yield*/, authClient.getAuthCodeUrl(__assign(__assign({}, validRequest), { nativeBroker: isNativeBroker }))];
case 5:
navigateUrl = _a.sent();
interactionHandler = new InteractionHandler(authClient, this.browserStorage, authCodeRequest, this.logger);
popupParameters = {
popup: popup,
popupName: popupName,
popupWindowAttributes: popupWindowAttributes
};
popupWindow = this.initiateAuthRequest(navigateUrl, popupParameters);
this.eventHandler.emitEvent(EventType.POPUP_OPENED, InteractionType.Popup, { popupWindow: popupWindow }, null);
return [4 /*yield*/, this.monitorPopupForHash(popupWindow)];
case 6:
hash = _a.sent();
serverParams = UrlString.getDeserializedHash(hash);
state_1 = this.validateAndExtractStateFromHash(serverParams, InteractionType.Popup, validRequest.correlationId);
// Remove throttle if it exists
ThrottlingUtils.removeThrottle(this.browserStorage, this.config.auth.clientId, authCodeRequest);
if (serverParams.accountId) {
this.logger.verbose("Account id found in hash, calling WAM for token");
// end measurement for server call with native brokering enabled
if (fetchNativeAccountIdMeasurement) {
fetchNativeAccountIdMeasurement.endMeasurement({
success: true,
isNativeBroker: true
});
}
if (!this.nativeMessageHandler) {
throw BrowserAuthError.createNativeConnectionNotEstablishedError();
}
nativeInteractionClient = new NativeInteractionClient(this.config, this.browserStorage, this.browserCrypto, this.logger, this.eventHandler, this.navigationClient, ApiId.acquireTokenPopup, this.performanceClient, this.nativeMessageHandler, serverParams.accountId, this.nativeStorage, validRequest.correlationId);
userRequestState = ProtocolUtils.parseRequestState(this.browserCrypto, state_1).userRequestState;
return [2 /*return*/, nativeInteractionClient.acquireToken(__assign(__assign({}, validRequest), { state: userRequestState, prompt: undefined // Server should handle the prompt, ideally native broker can do this part silently
})).finally(function () {
_this.browserStorage.cleanRequestByState(state_1);
})];
}
return [4 /*yield*/, interactionHandler.handleCodeResponseFromHash(hash, state_1, authClient.authority, this.networkClient)];
case 7:
result = _a.sent();
return [2 /*return*/, result];
case 8:
e_1 = _a.sent();
if (popup) {
// Close the synchronous popup if an error is thrown before the window unload event is registered
popup.close();
}
if (e_1 instanceof AuthError) {
e_1.setCorrelationId(this.correlationId);
}
serverTelemetryManager.cacheFailedRequest(e_1);
this.browserStorage.cleanRequestByState(validRequest.state);
throw e_1;
case 9: return [2 /*return*/];
}
});
});
};
/**
*
* @param validRequest
* @param popupName
* @param requestAuthority
* @param popup
* @param mainWindowRedirectUri
* @param popupWindowAttributes
*/
PopupClient.prototype.logoutPopupAsync = function (validRequest, popupName, popupWindowAttributes, requestAuthority, popup, mainWindowRedirectUri) {
return __awaiter(this, void 0, void 0, function () {
var serverTelemetryManager, authClient, logoutUri, popupWindow, navigationOptions, absoluteUrl, e_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.logger.verbose("logoutPopupAsync called");
this.eventHandler.emitEvent(EventType.LOGOUT_START, InteractionType.Popup, validRequest);
serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.logoutPopup);
_a.label = 1;
case 1:
_a.trys.push([1, 5, , 6]);
// Clear cache on logout
return [4 /*yield*/, this.clearCacheOnLogout(validRequest.account)];
case 2:
// Clear cache on logout
_a.sent();
return [4 /*yield*/, this.createAuthCodeClient(serverTelemetryManager, requestAuthority)];
case 3:
authClient = _a.sent();
this.logger.verbose("Auth code client created");
logoutUri = authClient.getLogoutUri(validRequest);
this.eventHandler.emitEvent(EventType.LOGOUT_SUCCESS, InteractionType.Popup, validRequest);
popupWindow = this.openPopup(logoutUri, { popupName: popupName, popupWindowAttributes: popupWindowAttributes, popup: popup });
this.eventHandler.emitEvent(EventType.POPUP_OPENED, InteractionType.Popup, { popupWindow: popupWindow }, null);
return [4 /*yield*/, this.waitForLogoutPopup(popupWindow)];
case 4:
_a.sent();
if (mainWindowRedirectUri) {
navigationOptions = {
apiId: ApiId.logoutPopup,
timeout: this.config.system.redirectNavigationTimeout,
noHistory: false
};
absoluteUrl = UrlString.getAbsoluteUrl(mainWindowRedirectUri, BrowserUtils.getCurrentUri());
this.logger.verbose("Redirecting main window to url specified in the request");
this.logger.verbosePii("Redirecting main window to: " + absoluteUrl);
this.navigationClient.navigateInternal(absoluteUrl, navigationOptions);
}
else {
this.logger.verbose("No main window navigation requested");
}
return [3 /*break*/, 6];
case 5:
e_2 = _a.sent();
if (popup) {
// Close the synchronous popup if an error is thrown before the window unload event is registered
popup.close();
}
if (e_2 instanceof AuthError) {
e_2.setCorrelationId(this.correlationId);
}
this.browserStorage.setInteractionInProgress(false);
this.eventHandler.emitEvent(EventType.LOGOUT_FAILURE, InteractionType.Popup, null, e_2);
this.eventHandler.emitEvent(EventType.LOGOUT_END, InteractionType.Popup);
serverTelemetryManager.cacheFailedRequest(e_2);
throw e_2;
case 6:
this.eventHandler.emitEvent(EventType.LOGOUT_END, InteractionType.Popup);
return [2 /*return*/];
}
});
});
};
/**
* Opens a popup window with given request Url.
* @param requestUrl
*/
PopupClient.prototype.initiateAuthRequest = function (requestUrl, params) {
// Check that request url is not empty.
if (!StringUtils.isEmpty(requestUrl)) {
this.logger.infoPii("Navigate to: " + requestUrl);
// Open the popup window to requestUrl.
return this.openPopup(requestUrl, params);
}
else {
// Throw error if request URL is empty.
this.logger.error("Navigate url is empty");
throw BrowserAuthError.createEmptyNavigationUriError();
}
};
/**
* Monitors a window until it loads a url with the same origin.
* @param popupWindow - window that is being monitored
* @param timeout - timeout for processing hash once popup is redirected back to application
*/
PopupClient.prototype.monitorPopupForHash = function (popupWindow) {
var _this = this;
return new Promise(function (resolve, reject) {
/*
* Polling for popups needs to be tick-based,
* since a non-trivial amount of time can be spent on interaction (which should not count against the timeout).
*/
var maxTicks = _this.config.system.windowHashTimeout / BrowserConstants.POLL_INTERVAL_MS;
var ticks = 0;
_this.logger.verbose("PopupHandler.monitorPopupForHash - polling started");
var intervalId = setInterval(function () {
// Window is closed
if (popupWindow.closed) {
_this.logger.error("PopupHandler.monitorPopupForHash - window closed");
_this.cleanPopup();
clearInterval(intervalId);
reject(BrowserAuthError.createUserCancelledError());
return;
}
var href = Constants.EMPTY_STRING;
var hash = Constants.EMPTY_STRING;
try {
/*
* Will throw if cross origin,
* which should be caught and ignored
* since we need the interval to keep running while on STS UI.
*/
href = popupWindow.location.href;
hash = popupWindow.location.hash;
}
catch (e) { }
// Don't process blank pages or cross domain
if (StringUtils.isEmpty(href) || href === "about:blank") {
return;
}
_this.logger.verbose("PopupHandler.monitorPopupForHash - popup window is on same origin as caller");
/*
* Only run clock when we are on same domain for popups
* as popup operations can take a long time.
*/
ticks++;
if (hash) {
_this.logger.verbose("PopupHandler.monitorPopupForHash - found hash in url");
clearInterval(intervalId);
_this.cleanPopup(popupWindow);
if (UrlString.hashContainsKnownProperties(hash)) {
_this.logger.verbose("PopupHandler.monitorPopupForHash - hash contains known properties, returning.");
resolve(hash);
}
else {
_this.logger.error("PopupHandler.monitorPopupForHash - found hash in url but it does not contain known properties. Check that your router is not changing the hash prematurely.");
_this.logger.errorPii("PopupHandler.monitorPopupForHash - hash found: " + hash);
reject(BrowserAuthError.createHashDoesNotContainKnownPropertiesError());
}
}
else if (ticks > maxTicks) {
_this.logger.error("PopupHandler.monitorPopupForHash - unable to find hash in url, timing out");
clearInterval(intervalId);
reject(BrowserAuthError.createMonitorPopupTimeoutError());
}
}, BrowserConstants.POLL_INTERVAL_MS);
});
};
/**
* Waits for user interaction in logout popup window
* @param popupWindow
* @returns
*/
PopupClient.prototype.waitForLogoutPopup = function (popupWindow) {
var _this = this;
return new Promise(function (resolve) {
_this.logger.verbose("PopupHandler.waitForLogoutPopup - polling started");
var intervalId = setInterval(function () {
// Window is closed
if (popupWindow.closed) {
_this.logger.error("PopupHandler.waitForLogoutPopup - window closed");
_this.cleanPopup();
clearInterval(intervalId);
resolve();
}
var href = Constants.EMPTY_STRING;
try {
/*
* Will throw if cross origin,
* which should be caught and ignored
* since we need the interval to keep running while on STS UI.
*/
href = popupWindow.location.href;
}
catch (e) { }
// Don't process blank pages or cross domain
if (StringUtils.isEmpty(href) || href === "about:blank") {
return;
}
_this.logger.verbose("PopupHandler.waitForLogoutPopup - popup window is on same origin as caller, closing.");
clearInterval(intervalId);
_this.cleanPopup(popupWindow);
resolve();
}, BrowserConstants.POLL_INTERVAL_MS);
});
};
/**
* @hidden
*
* Configures popup window for login.
*
* @param urlNavigate
* @param title
* @param popUpWidth
* @param popUpHeight
* @param popupWindowAttributes
* @ignore
* @hidden
*/
PopupClient.prototype.openPopup = function (urlNavigate, popupParams) {
try {
var popupWindow = void 0;
// Popup window passed in, setting url to navigate to
if (popupParams.popup) {
popupWindow = popupParams.popup;
this.logger.verbosePii("Navigating popup window to: " + urlNavigate);
popupWindow.location.assign(urlNavigate);
}
else if (typeof popupParams.popup === "undefined") {
// Popup will be undefined if it was not passed in
this.logger.verbosePii("Opening popup window to: " + urlNavigate);
popupWindow = this.openSizedPopup(urlNavigate, popupParams.popupName, popupParams.popupWindowAttributes);
}
// Popup will be null if popups are blocked
if (!popupWindow) {
throw BrowserAuthError.createEmptyWindowCreatedError();
}
if (popupWindow.focus) {
popupWindow.focus();
}
this.currentWindow = popupWindow;
window.addEventListener("beforeunload", this.unloadWindow);
return popupWindow;
}
catch (e) {
this.logger.error("error opening popup " + e.message);
this.browserStorage.setInteractionInProgress(false);
throw BrowserAuthError.createPopupWindowError(e.toString());
}
};
/**
* Helper function to set popup window dimensions and position
* @param urlNavigate
* @param popupName
* @param popupWindowAttributes
* @returns
*/
PopupClient.prototype.openSizedPopup = function (urlNavigate, popupName, popupWindowAttributes) {
var _a, _b, _c, _d;
/**
* adding winLeft and winTop to account for dual monitor
* using screenLeft and screenTop for IE8 and earlier
*/
var winLeft = window.screenLeft ? window.screenLeft : window.screenX;
var winTop = window.screenTop ? window.screenTop : window.screenY;
/**
* window.innerWidth displays browser window"s height and width excluding toolbars
* using document.documentElement.clientWidth for IE8 and earlier
*/
var winWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
var winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
var width = (_a = popupWindowAttributes.popupSize) === null || _a === void 0 ? void 0 : _a.width;
var height = (_b = popupWindowAttributes.popupSize) === null || _b === void 0 ? void 0 : _b.height;
var top = (_c = popupWindowAttributes.popupPosition) === null || _c === void 0 ? void 0 : _c.top;
var left = (_d = popupWindowAttributes.popupPosition) === null || _d === void 0 ? void 0 : _d.left;
if (!width || width < 0 || width > winWidth) {
this.logger.verbose("Default popup window width used. Window width not configured or invalid.");
width = BrowserConstants.POPUP_WIDTH;
}
if (!height || height < 0 || height > winHeight) {
this.logger.verbose("Default popup window height used. Window height not configured or invalid.");
height = BrowserConstants.POPUP_HEIGHT;
}
if (!top || top < 0 || top > winHeight) {
this.logger.verbose("Default popup window top position used. Window top not configured or invalid.");
top = Math.max(0, ((winHeight / 2) - (BrowserConstants.POPUP_HEIGHT / 2)) + winTop);
}
if (!left || left < 0 || left > winWidth) {
this.logger.verbose("Default popup window left position used. Window left not configured or invalid.");
left = Math.max(0, ((winWidth / 2) - (BrowserConstants.POPUP_WIDTH / 2)) + winLeft);
}
return window.open(urlNavigate, popupName, "width=" + width + ", height=" + height + ", top=" + top + ", left=" + left + ", scrollbars=yes");
};
/**
* Event callback to unload main window.
*/
PopupClient.prototype.unloadWindow = function (e) {
this.browserStorage.cleanRequestByInteractionType(InteractionType.Popup);
if (this.currentWindow) {
this.currentWindow.close();
}
// Guarantees browser unload will happen, so no other errors will be thrown.
e.preventDefault();
};
/**
* Closes popup, removes any state vars created during popup calls.
* @param popupWindow
*/
PopupClient.prototype.cleanPopup = function (popupWindow) {
if (popupWindow) {
// Close window.
popupWindow.close();
}
// Remove window unload function
window.removeEventListener("beforeunload", this.unloadWindow);
// Interaction is completed - remove interaction status.
this.browserStorage.setInteractionInProgress(false);
};
/**
* Generates the name for the popup based on the client id and request
* @param clientId
* @param request
*/
PopupClient.prototype.generatePopupName = function (scopes, authority) {
return BrowserConstants.POPUP_NAME_PREFIX + "." + this.config.auth.clientId + "." + scopes.join("-") + "." + authority + "." + this.correlationId;
};
/**
* Generates the name for the popup based on the client id and request for logouts
* @param clientId
* @param request
*/
PopupClient.prototype.generateLogoutPopupName = function (request) {
var homeAccountId = request.account && request.account.homeAccountId;
return BrowserConstants.POPUP_NAME_PREFIX + "." + this.config.auth.clientId + "." + homeAccountId + "." + this.correlationId;
};
return PopupClient;
}(StandardInteractionClient));
export { PopupClient };
//# sourceMappingURL=PopupClient.js.map