@azure/msal-browser
Version: 
Microsoft Authentication Library for js
346 lines (343 loc) • 22.4 kB
JavaScript
/*! @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