UNPKG

@azure/msal-browser

Version:
507 lines (504 loc) 28.3 kB
/*! @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