UNPKG

@azure/msal-browser

Version:
346 lines (343 loc) 22.4 kB
/*! @azure/msal-browser v2.28.1 2022-08-01 */ 'use strict'; import { __extends, __awaiter, __generator, __assign } from '../_virtual/_tslib.js'; import { UrlString, AuthError, Constants, ThrottlingUtils, ProtocolUtils } from '@azure/msal-common'; import { StandardInteractionClient } from './StandardInteractionClient.js'; import { TemporaryCacheKeys, ApiId, InteractionType } from '../utils/BrowserConstants.js'; import { RedirectHandler } from '../interaction_handler/RedirectHandler.js'; import { BrowserUtils } from '../utils/BrowserUtils.js'; import { EventType } from '../event/EventType.js'; import { BrowserAuthError } from '../error/BrowserAuthError.js'; import { NativeInteractionClient } from './NativeInteractionClient.js'; import { NativeMessageHandler } from '../broker/nativeBroker/NativeMessageHandler.js'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ var RedirectClient = /** @class */ (function (_super) { __extends(RedirectClient, _super); function RedirectClient(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; _this.nativeStorage = nativeStorageImpl; return _this; } /** * Redirects the page to the /authorize endpoint of the IDP * @param request */ RedirectClient.prototype.acquireToken = function (request) { return __awaiter(this, void 0, void 0, function () { var validRequest, serverTelemetryManager, handleBackButton, authCodeRequest, authClient, interactionHandler, navigateUrl, redirectStartPage, e_1; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.initializeAuthorizationRequest(request, InteractionType.Redirect)]; case 1: validRequest = _a.sent(); this.browserStorage.updateCacheEntries(validRequest.state, validRequest.nonce, validRequest.authority, validRequest.loginHint || Constants.EMPTY_STRING, validRequest.account || null); serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.acquireTokenRedirect); handleBackButton = function (event) { // Clear temporary cache if the back button is clicked during the redirect flow. if (event.persisted) { _this.logger.verbose("Page was restored from back/forward cache. Clearing temporary cache."); _this.browserStorage.cleanRequestByState(validRequest.state); } }; _a.label = 2; case 2: _a.trys.push([2, 7, , 8]); 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"); interactionHandler = new RedirectHandler(authClient, this.browserStorage, authCodeRequest, this.logger, this.browserCrypto); return [4 /*yield*/, authClient.getAuthCodeUrl(__assign(__assign({}, validRequest), { nativeBroker: NativeMessageHandler.isNativeAvailable(this.config, this.logger, this.nativeMessageHandler, request.authenticationScheme) }))]; case 5: navigateUrl = _a.sent(); redirectStartPage = this.getRedirectStartPage(request.redirectStartPage); this.logger.verbosePii("Redirect start page: " + redirectStartPage); // Clear temporary cache if the back button is clicked during the redirect flow. window.addEventListener("pageshow", handleBackButton); return [4 /*yield*/, interactionHandler.initiateAuthRequest(navigateUrl, { navigationClient: this.navigationClient, redirectTimeout: this.config.system.redirectNavigationTimeout, redirectStartPage: redirectStartPage, onRedirectNavigate: request.onRedirectNavigate })]; case 6: // Show the UI once the url has been created. Response will come back in the hash, which will be handled in the handleRedirectCallback function. return [2 /*return*/, _a.sent()]; case 7: e_1 = _a.sent(); if (e_1 instanceof AuthError) { e_1.setCorrelationId(this.correlationId); } window.removeEventListener("pageshow", handleBackButton); serverTelemetryManager.cacheFailedRequest(e_1); this.browserStorage.cleanRequestByState(validRequest.state); throw e_1; case 8: return [2 /*return*/]; } }); }); }; /** * Checks if navigateToLoginRequestUrl is set, and: * - if true, performs logic to cache and navigate * - if false, handles hash string and parses response * @param hash */ RedirectClient.prototype.handleRedirectPromise = function (hash) { return __awaiter(this, void 0, void 0, function () { var serverTelemetryManager, responseHash, state, serverParams, loginRequestUrl, loginRequestUrlNormalized, currentUrlNormalized, handleHashResult, navigationOptions, processHashOnRedirect, homepage, e_2; return __generator(this, function (_a) { switch (_a.label) { case 0: serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.handleRedirectPromise); _a.label = 1; case 1: _a.trys.push([1, 10, , 11]); if (!this.browserStorage.isInteractionInProgress(true)) { this.logger.info("handleRedirectPromise called but there is no interaction in progress, returning null."); return [2 /*return*/, null]; } responseHash = this.getRedirectResponseHash(hash || window.location.hash); if (!responseHash) { // Not a recognized server response hash or hash not associated with a redirect request this.logger.info("handleRedirectPromise did not detect a response hash as a result of a redirect. Cleaning temporary cache."); this.browserStorage.cleanRequestByInteractionType(InteractionType.Redirect); return [2 /*return*/, null]; } state = void 0; try { serverParams = UrlString.getDeserializedHash(responseHash); state = this.validateAndExtractStateFromHash(serverParams, InteractionType.Redirect); this.logger.verbose("State extracted from hash"); } catch (e) { this.logger.info("handleRedirectPromise was unable to extract state due to: " + e); this.browserStorage.cleanRequestByInteractionType(InteractionType.Redirect); return [2 /*return*/, null]; } loginRequestUrl = this.browserStorage.getTemporaryCache(TemporaryCacheKeys.ORIGIN_URI, true) || Constants.EMPTY_STRING; loginRequestUrlNormalized = UrlString.removeHashFromUrl(loginRequestUrl); currentUrlNormalized = UrlString.removeHashFromUrl(window.location.href); if (!(loginRequestUrlNormalized === currentUrlNormalized && this.config.auth.navigateToLoginRequestUrl)) return [3 /*break*/, 3]; // We are on the page we need to navigate to - handle hash this.logger.verbose("Current page is loginRequestUrl, handling hash"); return [4 /*yield*/, this.handleHash(responseHash, state, serverTelemetryManager)]; case 2: handleHashResult = _a.sent(); if (loginRequestUrl.indexOf("#") > -1) { // Replace current hash with non-msal hash, if present BrowserUtils.replaceHash(loginRequestUrl); } return [2 /*return*/, handleHashResult]; case 3: if (!!this.config.auth.navigateToLoginRequestUrl) return [3 /*break*/, 4]; this.logger.verbose("NavigateToLoginRequestUrl set to false, handling hash"); return [2 /*return*/, this.handleHash(responseHash, state, serverTelemetryManager)]; case 4: if (!(!BrowserUtils.isInIframe() || this.config.system.allowRedirectInIframe)) return [3 /*break*/, 9]; /* * Returned from authority using redirect - need to perform navigation before processing response * Cache the hash to be retrieved after the next redirect */ this.browserStorage.setTemporaryCache(TemporaryCacheKeys.URL_HASH, responseHash, true); navigationOptions = { apiId: ApiId.handleRedirectPromise, timeout: this.config.system.redirectNavigationTimeout, noHistory: true }; processHashOnRedirect = true; if (!(!loginRequestUrl || loginRequestUrl === "null")) return [3 /*break*/, 6]; homepage = BrowserUtils.getHomepage(); // Cache the homepage under ORIGIN_URI to ensure cached hash is processed on homepage this.browserStorage.setTemporaryCache(TemporaryCacheKeys.ORIGIN_URI, homepage, true); this.logger.warning("Unable to get valid login request url from cache, redirecting to home page"); return [4 /*yield*/, this.navigationClient.navigateInternal(homepage, navigationOptions)]; case 5: processHashOnRedirect = _a.sent(); return [3 /*break*/, 8]; case 6: // Navigate to page that initiated the redirect request this.logger.verbose("Navigating to loginRequestUrl: " + loginRequestUrl); return [4 /*yield*/, this.navigationClient.navigateInternal(loginRequestUrl, navigationOptions)]; case 7: processHashOnRedirect = _a.sent(); _a.label = 8; case 8: // If navigateInternal implementation returns false, handle the hash now if (!processHashOnRedirect) { return [2 /*return*/, this.handleHash(responseHash, state, serverTelemetryManager)]; } _a.label = 9; case 9: return [2 /*return*/, null]; case 10: e_2 = _a.sent(); if (e_2 instanceof AuthError) { e_2.setCorrelationId(this.correlationId); } serverTelemetryManager.cacheFailedRequest(e_2); this.browserStorage.cleanRequestByInteractionType(InteractionType.Redirect); throw e_2; case 11: return [2 /*return*/]; } }); }); }; /** * Gets the response hash for a redirect request * Returns null if interactionType in the state value is not "redirect" or the hash does not contain known properties * @param hash */ RedirectClient.prototype.getRedirectResponseHash = function (hash) { this.logger.verbose("getRedirectResponseHash called"); // Get current location hash from window or cache. var isResponseHash = UrlString.hashContainsKnownProperties(hash); if (isResponseHash) { BrowserUtils.clearHash(window); this.logger.verbose("Hash contains known properties, returning response hash"); return hash; } var cachedHash = this.browserStorage.getTemporaryCache(TemporaryCacheKeys.URL_HASH, true); this.browserStorage.removeItem(this.browserStorage.generateCacheKey(TemporaryCacheKeys.URL_HASH)); this.logger.verbose("Hash does not contain known properties, returning cached hash"); return cachedHash; }; /** * Checks if hash exists and handles in window. * @param hash * @param state */ RedirectClient.prototype.handleHash = function (hash, state, serverTelemetryManager) { return __awaiter(this, void 0, void 0, function () { var cachedRequest, serverParams, nativeInteractionClient, userRequestState, currentAuthority, authClient, interactionHandler; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: cachedRequest = this.browserStorage.getCachedRequest(state, this.browserCrypto); this.logger.verbose("handleHash called, retrieved cached request"); serverParams = UrlString.getDeserializedHash(hash); if (serverParams.accountId) { this.logger.verbose("Account id found in hash, calling WAM for token"); 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.browserStorage, cachedRequest.correlationId); userRequestState = ProtocolUtils.parseRequestState(this.browserCrypto, state).userRequestState; return [2 /*return*/, nativeInteractionClient.acquireToken(__assign(__assign({}, cachedRequest), { state: userRequestState, prompt: undefined // Server should handle the prompt, ideally native broker can do this part silently })).finally(function () { _this.browserStorage.cleanRequestByState(state); })]; } currentAuthority = this.browserStorage.getCachedAuthority(state); if (!currentAuthority) { throw BrowserAuthError.createNoCachedAuthorityError(); } return [4 /*yield*/, this.createAuthCodeClient(serverTelemetryManager, currentAuthority)]; case 1: authClient = _a.sent(); this.logger.verbose("Auth code client created"); ThrottlingUtils.removeThrottle(this.browserStorage, this.config.auth.clientId, cachedRequest); interactionHandler = new RedirectHandler(authClient, this.browserStorage, cachedRequest, this.logger, this.browserCrypto); return [4 /*yield*/, interactionHandler.handleCodeResponseFromHash(hash, state, authClient.authority, this.networkClient)]; case 2: return [2 /*return*/, _a.sent()]; } }); }); }; /** * Use to log out the current user, and redirect the user to the postLogoutRedirectUri. * Default behaviour is to redirect the user to `window.location.href`. * @param logoutRequest */ RedirectClient.prototype.logout = function (logoutRequest) { return __awaiter(this, void 0, void 0, function () { var validLogoutRequest, serverTelemetryManager, navigationOptions, authClient, logoutUri, navigate, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: this.logger.verbose("logoutRedirect called"); validLogoutRequest = this.initializeLogoutRequest(logoutRequest); serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.logout); _a.label = 1; case 1: _a.trys.push([1, 10, , 11]); this.eventHandler.emitEvent(EventType.LOGOUT_START, InteractionType.Redirect, logoutRequest); // Clear cache on logout return [4 /*yield*/, this.clearCacheOnLogout(validLogoutRequest.account)]; case 2: // Clear cache on logout _a.sent(); navigationOptions = { apiId: ApiId.logout, timeout: this.config.system.redirectNavigationTimeout, noHistory: false }; return [4 /*yield*/, this.createAuthCodeClient(serverTelemetryManager, logoutRequest && logoutRequest.authority)]; case 3: authClient = _a.sent(); this.logger.verbose("Auth code client created"); logoutUri = authClient.getLogoutUri(validLogoutRequest); this.eventHandler.emitEvent(EventType.LOGOUT_SUCCESS, InteractionType.Redirect, validLogoutRequest); if (!(logoutRequest && typeof logoutRequest.onRedirectNavigate === "function")) return [3 /*break*/, 7]; navigate = logoutRequest.onRedirectNavigate(logoutUri); if (!(navigate !== false)) return [3 /*break*/, 5]; this.logger.verbose("Logout onRedirectNavigate did not return false, navigating"); // Ensure interaction is in progress if (!this.browserStorage.getInteractionInProgress()) { this.browserStorage.setInteractionInProgress(true); } return [4 /*yield*/, this.navigationClient.navigateExternal(logoutUri, navigationOptions)]; case 4: _a.sent(); return [2 /*return*/]; case 5: // Ensure interaction is not in progress this.browserStorage.setInteractionInProgress(false); this.logger.verbose("Logout onRedirectNavigate returned false, stopping navigation"); _a.label = 6; case 6: return [3 /*break*/, 9]; case 7: // Ensure interaction is in progress if (!this.browserStorage.getInteractionInProgress()) { this.browserStorage.setInteractionInProgress(true); } return [4 /*yield*/, this.navigationClient.navigateExternal(logoutUri, navigationOptions)]; case 8: _a.sent(); return [2 /*return*/]; case 9: return [3 /*break*/, 11]; case 10: e_3 = _a.sent(); if (e_3 instanceof AuthError) { e_3.setCorrelationId(this.correlationId); } serverTelemetryManager.cacheFailedRequest(e_3); this.eventHandler.emitEvent(EventType.LOGOUT_FAILURE, InteractionType.Redirect, null, e_3); this.eventHandler.emitEvent(EventType.LOGOUT_END, InteractionType.Redirect); throw e_3; case 11: this.eventHandler.emitEvent(EventType.LOGOUT_END, InteractionType.Redirect); return [2 /*return*/]; } }); }); }; /** * Use to get the redirectStartPage either from request or use current window * @param requestStartPage */ RedirectClient.prototype.getRedirectStartPage = function (requestStartPage) { var redirectStartPage = requestStartPage || window.location.href; return UrlString.getAbsoluteUrl(redirectStartPage, BrowserUtils.getCurrentUri()); }; return RedirectClient; }(StandardInteractionClient)); export { RedirectClient }; //# sourceMappingURL=RedirectClient.js.map