UNPKG

@azure/msal-browser

Version:
962 lines (959 loc) 44.9 kB
/*! @azure/msal-browser v2.28.1 2022-08-01 */ 'use strict'; import { __extends, __spread, __awaiter, __generator } from '../_virtual/_tslib.js'; import { AccountEntity, CacheManager, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity, ServerTelemetryEntity, AuthorityMetadataEntity, Constants, PersistentCacheKeys, ClientAuthError, ThrottlingEntity, StringUtils, ProtocolUtils, CcsCredentialType, IdToken, DEFAULT_CRYPTO_IMPLEMENTATION } from '@azure/msal-common'; import { BrowserAuthError } from '../error/BrowserAuthError.js'; import { BrowserCacheLocation, InMemoryCacheKeys, TemporaryCacheKeys } from '../utils/BrowserConstants.js'; import { BrowserStorage } from './BrowserStorage.js'; import { MemoryStorage } from './MemoryStorage.js'; import { BrowserProtocolUtils } from '../utils/BrowserProtocolUtils.js'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ /** * This class implements the cache storage interface for MSAL through browser local or session storage. * Cookies are only used if storeAuthStateInCookie is true, and are only used for * parameters such as state and nonce, generally. */ var BrowserCacheManager = /** @class */ (function (_super) { __extends(BrowserCacheManager, _super); function BrowserCacheManager(clientId, cacheConfig, cryptoImpl, logger) { var _this = _super.call(this, clientId, cryptoImpl) || this; // Cookie life calculation (hours * minutes * seconds * ms) _this.COOKIE_LIFE_MULTIPLIER = 24 * 60 * 60 * 1000; _this.cacheConfig = cacheConfig; _this.logger = logger; _this.internalStorage = new MemoryStorage(); _this.browserStorage = _this.setupBrowserStorage(_this.cacheConfig.cacheLocation); _this.temporaryCacheStorage = _this.setupTemporaryCacheStorage(_this.cacheConfig.cacheLocation); // Migrate any cache entries from older versions of MSAL. _this.migrateCacheEntries(); return _this; } /** * Returns a window storage class implementing the IWindowStorage interface that corresponds to the configured cacheLocation. * @param cacheLocation */ BrowserCacheManager.prototype.setupBrowserStorage = function (cacheLocation) { switch (cacheLocation) { case BrowserCacheLocation.LocalStorage: case BrowserCacheLocation.SessionStorage: try { // Temporary cache items will always be stored in session storage to mitigate problems caused by multiple tabs return new BrowserStorage(cacheLocation); } catch (e) { this.logger.verbose(e); break; } } this.cacheConfig.cacheLocation = BrowserCacheLocation.MemoryStorage; return new MemoryStorage(); }; /** * * @param cacheLocation */ BrowserCacheManager.prototype.setupTemporaryCacheStorage = function (cacheLocation) { switch (cacheLocation) { case BrowserCacheLocation.LocalStorage: case BrowserCacheLocation.SessionStorage: try { // Temporary cache items will always be stored in session storage to mitigate problems caused by multiple tabs return new BrowserStorage(BrowserCacheLocation.SessionStorage); } catch (e) { this.logger.verbose(e); return this.internalStorage; } case BrowserCacheLocation.MemoryStorage: default: return this.internalStorage; } }; /** * Migrate all old cache entries to new schema. No rollback supported. * @param storeAuthStateInCookie */ BrowserCacheManager.prototype.migrateCacheEntries = function () { var _this = this; var idTokenKey = Constants.CACHE_PREFIX + "." + PersistentCacheKeys.ID_TOKEN; var clientInfoKey = Constants.CACHE_PREFIX + "." + PersistentCacheKeys.CLIENT_INFO; var errorKey = Constants.CACHE_PREFIX + "." + PersistentCacheKeys.ERROR; var errorDescKey = Constants.CACHE_PREFIX + "." + PersistentCacheKeys.ERROR_DESC; var idTokenValue = this.browserStorage.getItem(idTokenKey); var clientInfoValue = this.browserStorage.getItem(clientInfoKey); var errorValue = this.browserStorage.getItem(errorKey); var errorDescValue = this.browserStorage.getItem(errorDescKey); var values = [idTokenValue, clientInfoValue, errorValue, errorDescValue]; var keysToMigrate = [PersistentCacheKeys.ID_TOKEN, PersistentCacheKeys.CLIENT_INFO, PersistentCacheKeys.ERROR, PersistentCacheKeys.ERROR_DESC]; keysToMigrate.forEach(function (cacheKey, index) { return _this.migrateCacheEntry(cacheKey, values[index]); }); }; /** * Utility function to help with migration. * @param newKey * @param value * @param storeAuthStateInCookie */ BrowserCacheManager.prototype.migrateCacheEntry = function (newKey, value) { if (value) { this.setTemporaryCache(newKey, value, true); } }; /** * Parses passed value as JSON object, JSON.parse() will throw an error. * @param input */ BrowserCacheManager.prototype.validateAndParseJson = function (jsonValue) { try { var parsedJson = JSON.parse(jsonValue); /** * There are edge cases in which JSON.parse will successfully parse a non-valid JSON object * (e.g. JSON.parse will parse an escaped string into an unescaped string), so adding a type check * of the parsed value is necessary in order to be certain that the string represents a valid JSON object. * */ return (parsedJson && typeof parsedJson === "object") ? parsedJson : null; } catch (error) { return null; } }; /** * fetches the entry from the browser storage based off the key * @param key */ BrowserCacheManager.prototype.getItem = function (key) { return this.browserStorage.getItem(key); }; /** * sets the entry in the browser storage * @param key * @param value */ BrowserCacheManager.prototype.setItem = function (key, value) { this.browserStorage.setItem(key, value); }; /** * fetch the account entity from the platform cache * @param accountKey */ BrowserCacheManager.prototype.getAccount = function (accountKey) { var account = this.getItem(accountKey); if (!account) { return null; } var parsedAccount = this.validateAndParseJson(account); if (!parsedAccount || !AccountEntity.isAccountEntity(parsedAccount)) { return null; } return CacheManager.toObject(new AccountEntity(), parsedAccount); }; /** * set account entity in the platform cache * @param key * @param value */ BrowserCacheManager.prototype.setAccount = function (account) { this.logger.trace("BrowserCacheManager.setAccount called"); var key = account.generateAccountKey(); this.setItem(key, JSON.stringify(account)); }; /** * generates idToken entity from a string * @param idTokenKey */ BrowserCacheManager.prototype.getIdTokenCredential = function (idTokenKey) { var value = this.getItem(idTokenKey); if (!value) { this.logger.trace("BrowserCacheManager.getIdTokenCredential: called, no cache hit"); return null; } var parsedIdToken = this.validateAndParseJson(value); if (!parsedIdToken || !IdTokenEntity.isIdTokenEntity(parsedIdToken)) { this.logger.trace("BrowserCacheManager.getIdTokenCredential: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getIdTokenCredential: cache hit"); return CacheManager.toObject(new IdTokenEntity(), parsedIdToken); }; /** * set IdToken credential to the platform cache * @param idToken */ BrowserCacheManager.prototype.setIdTokenCredential = function (idToken) { this.logger.trace("BrowserCacheManager.setIdTokenCredential called"); var idTokenKey = idToken.generateCredentialKey(); this.setItem(idTokenKey, JSON.stringify(idToken)); }; /** * generates accessToken entity from a string * @param key */ BrowserCacheManager.prototype.getAccessTokenCredential = function (accessTokenKey) { var value = this.getItem(accessTokenKey); if (!value) { this.logger.trace("BrowserCacheManager.getAccessTokenCredential: called, no cache hit"); return null; } var parsedAccessToken = this.validateAndParseJson(value); if (!parsedAccessToken || !AccessTokenEntity.isAccessTokenEntity(parsedAccessToken)) { this.logger.trace("BrowserCacheManager.getAccessTokenCredential: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getAccessTokenCredential: cache hit"); return CacheManager.toObject(new AccessTokenEntity(), parsedAccessToken); }; /** * set accessToken credential to the platform cache * @param accessToken */ BrowserCacheManager.prototype.setAccessTokenCredential = function (accessToken) { this.logger.trace("BrowserCacheManager.setAccessTokenCredential called"); var accessTokenKey = accessToken.generateCredentialKey(); this.setItem(accessTokenKey, JSON.stringify(accessToken)); }; /** * generates refreshToken entity from a string * @param refreshTokenKey */ BrowserCacheManager.prototype.getRefreshTokenCredential = function (refreshTokenKey) { var value = this.getItem(refreshTokenKey); if (!value) { this.logger.trace("BrowserCacheManager.getRefreshTokenCredential: called, no cache hit"); return null; } var parsedRefreshToken = this.validateAndParseJson(value); if (!parsedRefreshToken || !RefreshTokenEntity.isRefreshTokenEntity(parsedRefreshToken)) { this.logger.trace("BrowserCacheManager.getRefreshTokenCredential: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getRefreshTokenCredential: cache hit"); return CacheManager.toObject(new RefreshTokenEntity(), parsedRefreshToken); }; /** * set refreshToken credential to the platform cache * @param refreshToken */ BrowserCacheManager.prototype.setRefreshTokenCredential = function (refreshToken) { this.logger.trace("BrowserCacheManager.setRefreshTokenCredential called"); var refreshTokenKey = refreshToken.generateCredentialKey(); this.setItem(refreshTokenKey, JSON.stringify(refreshToken)); }; /** * fetch appMetadata entity from the platform cache * @param appMetadataKey */ BrowserCacheManager.prototype.getAppMetadata = function (appMetadataKey) { var value = this.getItem(appMetadataKey); if (!value) { this.logger.trace("BrowserCacheManager.getAppMetadata: called, no cache hit"); return null; } var parsedMetadata = this.validateAndParseJson(value); if (!parsedMetadata || !AppMetadataEntity.isAppMetadataEntity(appMetadataKey, parsedMetadata)) { this.logger.trace("BrowserCacheManager.getAppMetadata: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getAppMetadata: cache hit"); return CacheManager.toObject(new AppMetadataEntity(), parsedMetadata); }; /** * set appMetadata entity to the platform cache * @param appMetadata */ BrowserCacheManager.prototype.setAppMetadata = function (appMetadata) { this.logger.trace("BrowserCacheManager.setAppMetadata called"); var appMetadataKey = appMetadata.generateAppMetadataKey(); this.setItem(appMetadataKey, JSON.stringify(appMetadata)); }; /** * fetch server telemetry entity from the platform cache * @param serverTelemetryKey */ BrowserCacheManager.prototype.getServerTelemetry = function (serverTelemetryKey) { var value = this.getItem(serverTelemetryKey); if (!value) { this.logger.trace("BrowserCacheManager.getServerTelemetry: called, no cache hit"); return null; } var parsedMetadata = this.validateAndParseJson(value); if (!parsedMetadata || !ServerTelemetryEntity.isServerTelemetryEntity(serverTelemetryKey, parsedMetadata)) { this.logger.trace("BrowserCacheManager.getServerTelemetry: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getServerTelemetry: cache hit"); return CacheManager.toObject(new ServerTelemetryEntity(), parsedMetadata); }; /** * set server telemetry entity to the platform cache * @param serverTelemetryKey * @param serverTelemetry */ BrowserCacheManager.prototype.setServerTelemetry = function (serverTelemetryKey, serverTelemetry) { this.logger.trace("BrowserCacheManager.setServerTelemetry called"); this.setItem(serverTelemetryKey, JSON.stringify(serverTelemetry)); }; /** * */ BrowserCacheManager.prototype.getAuthorityMetadata = function (key) { var value = this.internalStorage.getItem(key); if (!value) { this.logger.trace("BrowserCacheManager.getAuthorityMetadata: called, no cache hit"); return null; } var parsedMetadata = this.validateAndParseJson(value); if (parsedMetadata && AuthorityMetadataEntity.isAuthorityMetadataEntity(key, parsedMetadata)) { this.logger.trace("BrowserCacheManager.getAuthorityMetadata: cache hit"); return CacheManager.toObject(new AuthorityMetadataEntity(), parsedMetadata); } return null; }; /** * */ BrowserCacheManager.prototype.getAuthorityMetadataKeys = function () { var _this = this; var allKeys = this.internalStorage.getKeys(); return allKeys.filter(function (key) { return _this.isAuthorityMetadata(key); }); }; /** * Sets wrapper metadata in memory * @param wrapperSKU * @param wrapperVersion */ BrowserCacheManager.prototype.setWrapperMetadata = function (wrapperSKU, wrapperVersion) { this.internalStorage.setItem(InMemoryCacheKeys.WRAPPER_SKU, wrapperSKU); this.internalStorage.setItem(InMemoryCacheKeys.WRAPPER_VER, wrapperVersion); }; /** * Returns wrapper metadata from in-memory storage */ BrowserCacheManager.prototype.getWrapperMetadata = function () { var sku = this.internalStorage.getItem(InMemoryCacheKeys.WRAPPER_SKU) || Constants.EMPTY_STRING; var version = this.internalStorage.getItem(InMemoryCacheKeys.WRAPPER_VER) || Constants.EMPTY_STRING; return [sku, version]; }; /** * * @param entity */ BrowserCacheManager.prototype.setAuthorityMetadata = function (key, entity) { this.logger.trace("BrowserCacheManager.setAuthorityMetadata called"); this.internalStorage.setItem(key, JSON.stringify(entity)); }; /** * Gets the active account */ BrowserCacheManager.prototype.getActiveAccount = function () { var activeAccountKeyFilters = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT_FILTERS); var activeAccountValueFilters = this.getItem(activeAccountKeyFilters); if (!activeAccountValueFilters) { // if new active account cache type isn't found, it's an old version, so look for that instead this.logger.trace("No active account filters cache schema found, looking for legacy schema"); var activeAccountKeyLocal = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT); var activeAccountValueLocal = this.getItem(activeAccountKeyLocal); if (!activeAccountValueLocal) { this.logger.trace("No active account found"); return null; } var activeAccount = this.getAccountInfoByFilter({ localAccountId: activeAccountValueLocal })[0] || null; if (activeAccount) { this.logger.trace("Legacy active account cache schema found"); this.logger.trace("Adding active account filters cache schema"); this.setActiveAccount(activeAccount); return activeAccount; } return null; } var activeAccountValueObj = this.validateAndParseJson(activeAccountValueFilters); if (activeAccountValueObj) { this.logger.trace("Active account filters schema found"); return this.getAccountInfoByFilter({ homeAccountId: activeAccountValueObj.homeAccountId, localAccountId: activeAccountValueObj.localAccountId })[0] || null; } this.logger.trace("No active account found"); return null; }; /** * Sets the active account's localAccountId in cache * @param account */ BrowserCacheManager.prototype.setActiveAccount = function (account) { var activeAccountKey = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT_FILTERS); var activeAccountKeyLocal = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT); if (account) { this.logger.verbose("setActiveAccount: Active account set"); var activeAccountValue = { homeAccountId: account.homeAccountId, localAccountId: account.localAccountId }; this.browserStorage.setItem(activeAccountKey, JSON.stringify(activeAccountValue)); this.browserStorage.setItem(activeAccountKeyLocal, account.localAccountId); } else { this.logger.verbose("setActiveAccount: No account passed, active account not set"); this.browserStorage.removeItem(activeAccountKey); this.browserStorage.removeItem(activeAccountKeyLocal); } }; /** * Gets a list of accounts that match all of the filters provided * @param account */ BrowserCacheManager.prototype.getAccountInfoByFilter = function (accountFilter) { var allAccounts = this.getAllAccounts(); return allAccounts.filter(function (accountObj) { if (accountFilter.username && accountFilter.username.toLowerCase() !== accountObj.username.toLowerCase()) { return false; } if (accountFilter.homeAccountId && accountFilter.homeAccountId !== accountObj.homeAccountId) { return false; } if (accountFilter.localAccountId && accountFilter.localAccountId !== accountObj.localAccountId) { return false; } if (accountFilter.tenantId && accountFilter.tenantId !== accountObj.tenantId) { return false; } if (accountFilter.environment && accountFilter.environment !== accountObj.environment) { return false; } return true; }); }; /** * Checks the cache for accounts matching loginHint or SID * @param loginHint * @param sid */ BrowserCacheManager.prototype.getAccountInfoByHints = function (loginHint, sid) { var matchingAccounts = this.getAllAccounts().filter(function (accountInfo) { if (sid) { var accountSid = accountInfo.idTokenClaims && accountInfo.idTokenClaims["sid"]; return sid === accountSid; } if (loginHint) { return loginHint === accountInfo.username; } return false; }); if (matchingAccounts.length === 1) { return matchingAccounts[0]; } else if (matchingAccounts.length > 1) { throw ClientAuthError.createMultipleMatchingAccountsInCacheError(); } return null; }; /** * fetch throttling entity from the platform cache * @param throttlingCacheKey */ BrowserCacheManager.prototype.getThrottlingCache = function (throttlingCacheKey) { var value = this.getItem(throttlingCacheKey); if (!value) { this.logger.trace("BrowserCacheManager.getThrottlingCache: called, no cache hit"); return null; } var parsedThrottlingCache = this.validateAndParseJson(value); if (!parsedThrottlingCache || !ThrottlingEntity.isThrottlingEntity(throttlingCacheKey, parsedThrottlingCache)) { this.logger.trace("BrowserCacheManager.getThrottlingCache: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getThrottlingCache: cache hit"); return CacheManager.toObject(new ThrottlingEntity(), parsedThrottlingCache); }; /** * set throttling entity to the platform cache * @param throttlingCacheKey * @param throttlingCache */ BrowserCacheManager.prototype.setThrottlingCache = function (throttlingCacheKey, throttlingCache) { this.logger.trace("BrowserCacheManager.setThrottlingCache called"); this.setItem(throttlingCacheKey, JSON.stringify(throttlingCache)); }; /** * Gets cache item with given key. * Will retrieve from cookies if storeAuthStateInCookie is set to true. * @param key */ BrowserCacheManager.prototype.getTemporaryCache = function (cacheKey, generateKey) { var key = generateKey ? this.generateCacheKey(cacheKey) : cacheKey; if (this.cacheConfig.storeAuthStateInCookie) { var itemCookie = this.getItemCookie(key); if (itemCookie) { this.logger.trace("BrowserCacheManager.getTemporaryCache: storeAuthStateInCookies set to true, retrieving from cookies"); return itemCookie; } } var value = this.temporaryCacheStorage.getItem(key); if (!value) { // If temp cache item not found in session/memory, check local storage for items set by old versions if (this.cacheConfig.cacheLocation === BrowserCacheLocation.LocalStorage) { var item = this.browserStorage.getItem(key); if (item) { this.logger.trace("BrowserCacheManager.getTemporaryCache: Temporary cache item found in local storage"); return item; } } this.logger.trace("BrowserCacheManager.getTemporaryCache: No cache item found in local storage"); return null; } this.logger.trace("BrowserCacheManager.getTemporaryCache: Temporary cache item returned"); return value; }; /** * Sets the cache item with the key and value given. * Stores in cookie if storeAuthStateInCookie is set to true. * This can cause cookie overflow if used incorrectly. * @param key * @param value */ BrowserCacheManager.prototype.setTemporaryCache = function (cacheKey, value, generateKey) { var key = generateKey ? this.generateCacheKey(cacheKey) : cacheKey; this.temporaryCacheStorage.setItem(key, value); if (this.cacheConfig.storeAuthStateInCookie) { this.logger.trace("BrowserCacheManager.setTemporaryCache: storeAuthStateInCookie set to true, setting item cookie"); this.setItemCookie(key, value); } }; /** * Removes the cache item with the given key. * Will also clear the cookie item if storeAuthStateInCookie is set to true. * @param key */ BrowserCacheManager.prototype.removeItem = function (key) { this.browserStorage.removeItem(key); this.temporaryCacheStorage.removeItem(key); if (this.cacheConfig.storeAuthStateInCookie) { this.logger.trace("BrowserCacheManager.removeItem: storeAuthStateInCookie is true, clearing item cookie"); this.clearItemCookie(key); } return true; }; /** * Checks whether key is in cache. * @param key */ BrowserCacheManager.prototype.containsKey = function (key) { return this.browserStorage.containsKey(key) || this.temporaryCacheStorage.containsKey(key); }; /** * Gets all keys in window. */ BrowserCacheManager.prototype.getKeys = function () { return __spread(this.browserStorage.getKeys(), this.temporaryCacheStorage.getKeys()); }; /** * Clears all cache entries created by MSAL. */ BrowserCacheManager.prototype.clear = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: // Removes all accounts and their credentials return [4 /*yield*/, this.removeAllAccounts()]; case 1: // Removes all accounts and their credentials _a.sent(); this.removeAppMetadata(); // Removes all remaining MSAL cache items this.getKeys().forEach(function (cacheKey) { // Check if key contains msal prefix; For now, we are clearing all the cache items created by MSAL.js if ((_this.browserStorage.containsKey(cacheKey) || _this.temporaryCacheStorage.containsKey(cacheKey)) && ((cacheKey.indexOf(Constants.CACHE_PREFIX) !== -1) || (cacheKey.indexOf(_this.clientId) !== -1))) { _this.removeItem(cacheKey); } }); this.internalStorage.clear(); return [2 /*return*/]; } }); }); }; /** * Add value to cookies * @param cookieName * @param cookieValue * @param expires */ BrowserCacheManager.prototype.setItemCookie = function (cookieName, cookieValue, expires) { var cookieStr = encodeURIComponent(cookieName) + "=" + encodeURIComponent(cookieValue) + ";path=/;SameSite=Lax;"; if (expires) { var expireTime = this.getCookieExpirationTime(expires); cookieStr += "expires=" + expireTime + ";"; } if (this.cacheConfig.secureCookies) { cookieStr += "Secure;"; } document.cookie = cookieStr; }; /** * Get one item by key from cookies * @param cookieName */ BrowserCacheManager.prototype.getItemCookie = function (cookieName) { var name = encodeURIComponent(cookieName) + "="; var cookieList = document.cookie.split(";"); for (var i = 0; i < cookieList.length; i++) { var cookie = cookieList[i]; while (cookie.charAt(0) === " ") { cookie = cookie.substring(1); } if (cookie.indexOf(name) === 0) { return decodeURIComponent(cookie.substring(name.length, cookie.length)); } } return Constants.EMPTY_STRING; }; /** * Clear all msal-related cookies currently set in the browser. Should only be used to clear temporary cache items. */ BrowserCacheManager.prototype.clearMsalCookies = function () { var _this = this; var cookiePrefix = Constants.CACHE_PREFIX + "." + this.clientId; var cookieList = document.cookie.split(";"); cookieList.forEach(function (cookie) { while (cookie.charAt(0) === " ") { // eslint-disable-next-line no-param-reassign cookie = cookie.substring(1); } if (cookie.indexOf(cookiePrefix) === 0) { var cookieKey = cookie.split("=")[0]; _this.clearItemCookie(cookieKey); } }); }; /** * Clear an item in the cookies by key * @param cookieName */ BrowserCacheManager.prototype.clearItemCookie = function (cookieName) { this.setItemCookie(cookieName, Constants.EMPTY_STRING, -1); }; /** * Get cookie expiration time * @param cookieLifeDays */ BrowserCacheManager.prototype.getCookieExpirationTime = function (cookieLifeDays) { var today = new Date(); var expr = new Date(today.getTime() + cookieLifeDays * this.COOKIE_LIFE_MULTIPLIER); return expr.toUTCString(); }; /** * Gets the cache object referenced by the browser */ BrowserCacheManager.prototype.getCache = function () { return this.browserStorage; }; /** * interface compat, we cannot overwrite browser cache; Functionality is supported by individual entities in browser */ BrowserCacheManager.prototype.setCache = function () { // sets nothing }; /** * Prepend msal.<client-id> to each key; Skip for any JSON object as Key (defined schemas do not need the key appended: AccessToken Keys or the upcoming schema) * @param key * @param addInstanceId */ BrowserCacheManager.prototype.generateCacheKey = function (key) { var generatedKey = this.validateAndParseJson(key); if (!generatedKey) { if (StringUtils.startsWith(key, Constants.CACHE_PREFIX) || StringUtils.startsWith(key, PersistentCacheKeys.ADAL_ID_TOKEN)) { return key; } return Constants.CACHE_PREFIX + "." + this.clientId + "." + key; } return JSON.stringify(key); }; /** * Create authorityKey to cache authority * @param state */ BrowserCacheManager.prototype.generateAuthorityKey = function (stateString) { var stateId = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString).libraryState.id; return this.generateCacheKey(TemporaryCacheKeys.AUTHORITY + "." + stateId); }; /** * Create Nonce key to cache nonce * @param state */ BrowserCacheManager.prototype.generateNonceKey = function (stateString) { var stateId = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString).libraryState.id; return this.generateCacheKey(TemporaryCacheKeys.NONCE_IDTOKEN + "." + stateId); }; /** * Creates full cache key for the request state * @param stateString State string for the request */ BrowserCacheManager.prototype.generateStateKey = function (stateString) { // Use the library state id to key temp storage for uniqueness for multiple concurrent requests var stateId = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString).libraryState.id; return this.generateCacheKey(TemporaryCacheKeys.REQUEST_STATE + "." + stateId); }; /** * Gets the cached authority based on the cached state. Returns empty if no cached state found. */ BrowserCacheManager.prototype.getCachedAuthority = function (cachedState) { var stateCacheKey = this.generateStateKey(cachedState); var state = this.getTemporaryCache(stateCacheKey); if (!state) { return null; } var authorityCacheKey = this.generateAuthorityKey(state); return this.getTemporaryCache(authorityCacheKey); }; /** * Updates account, authority, and state in cache * @param serverAuthenticationRequest * @param account */ BrowserCacheManager.prototype.updateCacheEntries = function (state, nonce, authorityInstance, loginHint, account) { this.logger.trace("BrowserCacheManager.updateCacheEntries called"); // Cache the request state var stateCacheKey = this.generateStateKey(state); this.setTemporaryCache(stateCacheKey, state, false); // Cache the nonce var nonceCacheKey = this.generateNonceKey(state); this.setTemporaryCache(nonceCacheKey, nonce, false); // Cache authorityKey var authorityCacheKey = this.generateAuthorityKey(state); this.setTemporaryCache(authorityCacheKey, authorityInstance, false); if (account) { var ccsCredential = { credential: account.homeAccountId, type: CcsCredentialType.HOME_ACCOUNT_ID }; this.setTemporaryCache(TemporaryCacheKeys.CCS_CREDENTIAL, JSON.stringify(ccsCredential), true); } else if (!StringUtils.isEmpty(loginHint)) { var ccsCredential = { credential: loginHint, type: CcsCredentialType.UPN }; this.setTemporaryCache(TemporaryCacheKeys.CCS_CREDENTIAL, JSON.stringify(ccsCredential), true); } }; /** * Reset all temporary cache items * @param state */ BrowserCacheManager.prototype.resetRequestCache = function (state) { var _this = this; this.logger.trace("BrowserCacheManager.resetRequestCache called"); // check state and remove associated cache items if (!StringUtils.isEmpty(state)) { this.getKeys().forEach(function (key) { if (key.indexOf(state) !== -1) { _this.removeItem(key); } }); } // delete generic interactive request parameters if (state) { this.removeItem(this.generateStateKey(state)); this.removeItem(this.generateNonceKey(state)); this.removeItem(this.generateAuthorityKey(state)); } this.removeItem(this.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS)); this.removeItem(this.generateCacheKey(TemporaryCacheKeys.ORIGIN_URI)); this.removeItem(this.generateCacheKey(TemporaryCacheKeys.URL_HASH)); this.removeItem(this.generateCacheKey(TemporaryCacheKeys.CORRELATION_ID)); this.removeItem(this.generateCacheKey(TemporaryCacheKeys.CCS_CREDENTIAL)); this.removeItem(this.generateCacheKey(TemporaryCacheKeys.NATIVE_REQUEST)); this.setInteractionInProgress(false); }; /** * Removes temporary cache for the provided state * @param stateString */ BrowserCacheManager.prototype.cleanRequestByState = function (stateString) { this.logger.trace("BrowserCacheManager.cleanRequestByState called"); // Interaction is completed - remove interaction status. if (stateString) { var stateKey = this.generateStateKey(stateString); var cachedState = this.temporaryCacheStorage.getItem(stateKey); this.logger.infoPii("BrowserCacheManager.cleanRequestByState: Removing temporary cache items for state: " + cachedState); this.resetRequestCache(cachedState || Constants.EMPTY_STRING); } this.clearMsalCookies(); }; /** * Looks in temporary cache for any state values with the provided interactionType and removes all temporary cache items for that state * Used in scenarios where temp cache needs to be cleaned but state is not known, such as clicking browser back button. * @param interactionType */ BrowserCacheManager.prototype.cleanRequestByInteractionType = function (interactionType) { var _this = this; this.logger.trace("BrowserCacheManager.cleanRequestByInteractionType called"); // Loop through all keys to find state key this.getKeys().forEach(function (key) { // If this key is not the state key, move on if (key.indexOf(TemporaryCacheKeys.REQUEST_STATE) === -1) { return; } // Retrieve state value, return if not a valid value var stateValue = _this.temporaryCacheStorage.getItem(key); if (!stateValue) { return; } // Extract state and ensure it matches given InteractionType, then clean request cache var parsedState = BrowserProtocolUtils.extractBrowserRequestState(_this.cryptoImpl, stateValue); if (parsedState && parsedState.interactionType === interactionType) { _this.logger.infoPii("BrowserCacheManager.cleanRequestByInteractionType: Removing temporary cache items for state: " + stateValue); _this.resetRequestCache(stateValue); } }); this.clearMsalCookies(); this.setInteractionInProgress(false); }; BrowserCacheManager.prototype.cacheCodeRequest = function (authCodeRequest, browserCrypto) { this.logger.trace("BrowserCacheManager.cacheCodeRequest called"); var encodedValue = browserCrypto.base64Encode(JSON.stringify(authCodeRequest)); this.setTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, encodedValue, true); }; /** * Gets the token exchange parameters from the cache. Throws an error if nothing is found. */ BrowserCacheManager.prototype.getCachedRequest = function (state, browserCrypto) { this.logger.trace("BrowserCacheManager.getCachedRequest called"); // Get token request from cache and parse as TokenExchangeParameters. var encodedTokenRequest = this.getTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, true); if (!encodedTokenRequest) { throw BrowserAuthError.createNoTokenRequestCacheError(); } var parsedRequest = this.validateAndParseJson(browserCrypto.base64Decode(encodedTokenRequest)); if (!parsedRequest) { throw BrowserAuthError.createUnableToParseTokenRequestCacheError(); } this.removeItem(this.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS)); // Get cached authority and use if no authority is cached with request. if (StringUtils.isEmpty(parsedRequest.authority)) { var authorityCacheKey = this.generateAuthorityKey(state); var cachedAuthority = this.getTemporaryCache(authorityCacheKey); if (!cachedAuthority) { throw BrowserAuthError.createNoCachedAuthorityError(); } parsedRequest.authority = cachedAuthority; } return parsedRequest; }; /** * Gets cached native request for redirect flows */ BrowserCacheManager.prototype.getCachedNativeRequest = function () { this.logger.trace("BrowserCacheManager.getCachedNativeRequest called"); var cachedRequest = this.getTemporaryCache(TemporaryCacheKeys.NATIVE_REQUEST, true); if (!cachedRequest) { this.logger.trace("BrowserCacheManager.getCachedNativeRequest: No cached native request found"); return null; } var parsedRequest = this.validateAndParseJson(cachedRequest); if (!parsedRequest) { this.logger.error("BrowserCacheManager.getCachedNativeRequest: Unable to parse native request"); return null; } return parsedRequest; }; BrowserCacheManager.prototype.isInteractionInProgress = function (matchClientId) { var clientId = this.getInteractionInProgress(); if (matchClientId) { return clientId === this.clientId; } else { return !!clientId; } }; BrowserCacheManager.prototype.getInteractionInProgress = function () { var key = Constants.CACHE_PREFIX + "." + TemporaryCacheKeys.INTERACTION_STATUS_KEY; return this.getTemporaryCache(key, false); }; BrowserCacheManager.prototype.setInteractionInProgress = function (inProgress) { // Ensure we don't overwrite interaction in progress for a different clientId var key = Constants.CACHE_PREFIX + "." + TemporaryCacheKeys.INTERACTION_STATUS_KEY; if (inProgress) { if (this.getInteractionInProgress()) { throw BrowserAuthError.createInteractionInProgressError(); } else { // No interaction is in progress this.setTemporaryCache(key, this.clientId, false); } } else if (!inProgress && this.getInteractionInProgress() === this.clientId) { this.removeItem(key); } }; /** * Returns username retrieved from ADAL or MSAL v1 idToken */ BrowserCacheManager.prototype.getLegacyLoginHint = function () { // Only check for adal/msal token if no SSO params are being used var adalIdTokenString = this.getTemporaryCache(PersistentCacheKeys.ADAL_ID_TOKEN); if (adalIdTokenString) { this.browserStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN); this.logger.verbose("Cached ADAL id token retrieved."); } // Check for cached MSAL v1 id token var msalIdTokenString = this.getTemporaryCache(PersistentCacheKeys.ID_TOKEN, true); if (msalIdTokenString) { this.removeItem(this.generateCacheKey(PersistentCacheKeys.ID_TOKEN)); this.logger.verbose("Cached MSAL.js v1 id token retrieved"); } var cachedIdTokenString = msalIdTokenString || adalIdTokenString; if (cachedIdTokenString) { var cachedIdToken = new IdToken(cachedIdTokenString, this.cryptoImpl); if (cachedIdToken.claims && cachedIdToken.claims.preferred_username) { this.logger.verbose("No SSO params used and ADAL/MSAL v1 token retrieved, setting ADAL/MSAL v1 preferred_username as loginHint"); return cachedIdToken.claims.preferred_username; } else if (cachedIdToken.claims && cachedIdToken.claims.upn) { this.logger.verbose("No SSO params used and ADAL/MSAL v1 token retrieved, setting ADAL/MSAL v1 upn as loginHint"); return cachedIdToken.claims.upn; } else { this.logger.verbose("No SSO params used and ADAL/MSAL v1 token retrieved, however, no account hint claim found. Enable preferred_username or upn id token claim to get SSO."); } } return null; }; /** * Updates a credential's cache key if the current cache key is outdated */ BrowserCacheManager.prototype.updateCredentialCacheKey = function (currentCacheKey, credential) { var updatedCacheKey = credential.generateCredentialKey(); if (currentCacheKey !== updatedCacheKey) { var cacheItem = this.getItem(currentCacheKey); if (cacheItem) { this.removeItem(currentCacheKey); this.setItem(updatedCacheKey, cacheItem); this.logger.verbose("Updated an outdated " + credential.credentialType + " cache key"); return updatedCacheKey; } else { this.logger.error("Attempted to update an outdated " + credential.credentialType + " cache key but no item matching the outdated key was found in storage"); } } return currentCacheKey; }; return BrowserCacheManager; }(CacheManager)); var DEFAULT_BROWSER_CACHE_MANAGER = function (clientId, logger) { var cacheOptions = { cacheLocation: BrowserCacheLocation.MemoryStorage, storeAuthStateInCookie: false, secureCookies: false }; return new BrowserCacheManager(clientId, cacheOptions, DEFAULT_CRYPTO_IMPLEMENTATION, logger); }; export { BrowserCacheManager, DEFAULT_BROWSER_CACHE_MANAGER }; //# sourceMappingURL=BrowserCacheManager.js.map