diff --git a/change/@azure-msal-browser-0e1341a0-d3ce-4987-b651-7a52610f774e.json b/change/@azure-msal-browser-0e1341a0-d3ce-4987-b651-7a52610f774e.json new file mode 100644 index 0000000000..7c31b5a24b --- /dev/null +++ b/change/@azure-msal-browser-0e1341a0-d3ce-4987-b651-7a52610f774e.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Enable strict TypeScript option (#2792)", + "packageName": "@azure/msal-browser", + "email": "thomas.norling@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-common-1c7724af-c058-4193-97e4-6c59abe17625.json b/change/@azure-msal-common-1c7724af-c058-4193-97e4-6c59abe17625.json new file mode 100644 index 0000000000..4cb9973e3b --- /dev/null +++ b/change/@azure-msal-common-1c7724af-c058-4193-97e4-6c59abe17625.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Add interface stubs (#2792)", + "packageName": "@azure/msal-common", + "email": "thomas.norling@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-browser/src/app/ClientApplication.ts b/lib/msal-browser/src/app/ClientApplication.ts index c6de298ed4..3126ef12dd 100644 --- a/lib/msal-browser/src/app/ClientApplication.ts +++ b/lib/msal-browser/src/app/ClientApplication.ts @@ -4,9 +4,9 @@ */ import { CryptoOps } from "../crypto/CryptoOps"; -import { Authority, TrustedAuthority, StringUtils, UrlString, ServerAuthorizationCodeResponse, AuthorizationCodeRequest, AuthorizationUrlRequest, AuthorizationCodeClient, PromptValue, ServerError, InteractionRequiredAuthError, AccountInfo, AuthorityFactory, ServerTelemetryManager, SilentFlowClient, ClientConfiguration, BaseAuthRequest, ServerTelemetryRequest, PersistentCacheKeys, IdToken, ProtocolUtils, ResponseMode, Constants, INetworkModule, AuthenticationResult, Logger, ThrottlingUtils, RefreshTokenClient, AuthenticationScheme, SilentFlowRequest, EndSessionRequest as CommonEndSessionRequest, AccountEntity } from "@azure/msal-common"; -import { BrowserCacheManager } from "../cache/BrowserCacheManager"; -import { buildConfiguration, Configuration } from "../config/Configuration"; +import { Authority, TrustedAuthority, StringUtils, UrlString, ServerAuthorizationCodeResponse, AuthorizationCodeRequest, AuthorizationCodeClient, PromptValue, ServerError, InteractionRequiredAuthError, AccountInfo, AuthorityFactory, ServerTelemetryManager, SilentFlowClient, ClientConfiguration, BaseAuthRequest, ServerTelemetryRequest, PersistentCacheKeys, IdToken, ProtocolUtils, ResponseMode, Constants, INetworkModule, AuthenticationResult, Logger, ThrottlingUtils, RefreshTokenClient, AuthenticationScheme, SilentFlowRequest, EndSessionRequest as CommonEndSessionRequest, AccountEntity, ICrypto, DEFAULT_CRYPTO_IMPLEMENTATION } from "@azure/msal-common"; +import { BrowserCacheManager, DEFAULT_BROWSER_CACHE_MANAGER } from "../cache/BrowserCacheManager"; +import { BrowserConfiguration, buildConfiguration, Configuration } from "../config/Configuration"; import { TemporaryCacheKeys, InteractionType, ApiId, BrowserConstants, BrowserCacheLocation } from "../utils/BrowserConstants"; import { BrowserUtils } from "../utils/BrowserUtils"; import { BrowserStateObject, BrowserProtocolUtils } from "../utils/BrowserProtocolUtils"; @@ -15,6 +15,7 @@ import { PopupHandler } from "../interaction_handler/PopupHandler"; import { SilentHandler } from "../interaction_handler/SilentHandler"; import { RedirectRequest } from "../request/RedirectRequest"; import { PopupRequest } from "../request/PopupRequest"; +import { AuthorizationUrlRequest } from "../request/AuthorizationUrlRequest"; import { BrowserAuthError } from "../error/BrowserAuthError"; import { SsoSilentRequest } from "../request/SsoSilentRequest"; import { version, name } from "../../package.json"; @@ -26,7 +27,7 @@ import { BrowserConfigurationAuthError } from "../error/BrowserConfigurationAuth export abstract class ClientApplication { // Crypto interface implementation - protected readonly browserCrypto: CryptoOps; + protected readonly browserCrypto: ICrypto; // Storage interface implementation protected readonly browserStorage: BrowserCacheManager; @@ -34,14 +35,11 @@ export abstract class ClientApplication { // Network interface implementation protected readonly networkClient: INetworkModule; - // Response promise - protected readonly tokenExchangePromise: Promise; - // Input configuration by developer/user - protected config: Configuration; + protected config: BrowserConfiguration; // Default authority - protected defaultAuthority: Authority; + protected defaultAuthority: Authority | null; // Logger protected logger: Logger; @@ -83,33 +81,35 @@ export abstract class ClientApplication { * This is to support server-side rendering environments. */ this.isBrowserEnvironment = typeof window !== "undefined"; - if (!this.isBrowserEnvironment) { - return; - } - // Set the configuration. - this.config = buildConfiguration(configuration); + this.config = buildConfiguration(configuration, this.isBrowserEnvironment); - // Initialize the crypto class. - this.browserCrypto = new CryptoOps(); + this.defaultAuthority = null; + this.activeLocalAccountId = null; - // Initialize the network module class. - this.networkClient = this.config.system.networkClient; + // Array of events + this.eventCallbacks = new Map(); // Initialize logger this.logger = new Logger(this.config.system.loggerOptions, name, version); + // Initialize the network module class. + this.networkClient = this.config.system.networkClient; + + if (!this.isBrowserEnvironment) { + this.browserStorage = DEFAULT_BROWSER_CACHE_MANAGER(this.config.auth.clientId, this.logger); + this.browserCrypto = DEFAULT_CRYPTO_IMPLEMENTATION; + return; + } + + // Initialize the crypto class. + this.browserCrypto = new CryptoOps(); + // Initialize the browser storage class. this.browserStorage = new BrowserCacheManager(this.config.auth.clientId, this.config.cache, this.browserCrypto, this.logger); - // Array of events - this.eventCallbacks = new Map(); - // Initialize default authority instance TrustedAuthority.setTrustedAuthoritiesFromConfig(this.config.auth.knownAuthorities, this.config.auth.cloudDiscoveryMetadata); - - this.defaultAuthority = null; - this.activeLocalAccountId = null; } // #region Redirect Flow @@ -126,7 +126,7 @@ export abstract class ClientApplication { const loggedInAccounts = this.getAllAccounts(); if (this.isBrowserEnvironment) { return this.handleRedirectResponse(hash) - .then((result: AuthenticationResult) => { + .then((result: AuthenticationResult | null) => { if (result) { // Emit login event if number of accounts change const isLoggingIn = loggedInAccounts.length < this.getAllAccounts().length; @@ -167,19 +167,30 @@ export abstract class ClientApplication { } const responseHash = this.getRedirectResponseHash(hash || window.location.hash); - if (StringUtils.isEmpty(responseHash)) { + if (!responseHash) { // Not a recognized server response hash or hash not associated with a redirect request + this.browserStorage.cleanRequestByInteractionType(InteractionType.Redirect); + return null; + } + + let state: string; + try { + state = this.validateAndExtractStateFromHash(responseHash, InteractionType.Redirect); + BrowserUtils.clearHash(); + } catch (e) { + this.logger.info(`handleRedirectPromise was unable to extract state due to: ${e}`); + this.browserStorage.cleanRequestByInteractionType(InteractionType.Redirect); return null; } // If navigateToLoginRequestUrl is true, get the url where the redirect request was initiated - const loginRequestUrl = this.browserStorage.getTemporaryCache(TemporaryCacheKeys.ORIGIN_URI, true); - const loginRequestUrlNormalized = UrlString.removeHashFromUrl(loginRequestUrl || ""); + const loginRequestUrl = this.browserStorage.getTemporaryCache(TemporaryCacheKeys.ORIGIN_URI, true) || ""; + const loginRequestUrlNormalized = UrlString.removeHashFromUrl(loginRequestUrl); const currentUrlNormalized = UrlString.removeHashFromUrl(window.location.href); if (loginRequestUrlNormalized === currentUrlNormalized && this.config.auth.navigateToLoginRequestUrl) { // We are on the page we need to navigate to - handle hash - const handleHashResult = await this.handleHash(responseHash); + const handleHashResult = await this.handleHash(responseHash, state); if (loginRequestUrl.indexOf("#") > -1) { // Replace current hash with non-msal hash, if present @@ -188,7 +199,7 @@ export abstract class ClientApplication { return handleHashResult; } else if (!this.config.auth.navigateToLoginRequestUrl) { - return this.handleHash(responseHash); + return this.handleHash(responseHash, state); } else if (!BrowserUtils.isInIframe()) { /* * Returned from authority using redirect - need to perform navigation before processing response @@ -219,24 +230,34 @@ export abstract class ClientApplication { private getRedirectResponseHash(hash: string): string | null { // Get current location hash from window or cache. const isResponseHash: boolean = UrlString.hashContainsKnownProperties(hash); - const cachedHash: string = this.browserStorage.getTemporaryCache(TemporaryCacheKeys.URL_HASH, true); + const cachedHash = this.browserStorage.getTemporaryCache(TemporaryCacheKeys.URL_HASH, true); this.browserStorage.removeItem(this.browserStorage.generateCacheKey(TemporaryCacheKeys.URL_HASH)); - const responseHash: string = isResponseHash ? hash : cachedHash; - if (responseHash) { - // Deserialize hash fragment response parameters. - const serverParams: ServerAuthorizationCodeResponse = UrlString.getDeserializedHash(responseHash); - const platformStateObj: BrowserStateObject = BrowserProtocolUtils.extractBrowserRequestState(this.browserCrypto, serverParams.state); - if (platformStateObj.interactionType !== InteractionType.Redirect) { - return null; - } else { - BrowserUtils.clearHash(); - return responseHash; - } + return isResponseHash ? hash : cachedHash; + } + + /** + * + * @param hash + * @param interactionType + */ + private validateAndExtractStateFromHash(hash: string, interactionType: InteractionType): string { + // Deserialize hash fragment response parameters. + const serverParams: ServerAuthorizationCodeResponse = UrlString.getDeserializedHash(hash); + if (!serverParams.state) { + throw BrowserAuthError.createHashDoesNotContainStateError(); } - this.browserStorage.cleanRequestByInteractionType(InteractionType.Redirect); - return null; + const platformStateObj = BrowserProtocolUtils.extractBrowserRequestState(this.browserCrypto, serverParams.state); + if (!platformStateObj) { + throw BrowserAuthError.createUnableToParseStateError(); + } + + if (platformStateObj.interactionType !== interactionType) { + throw BrowserAuthError.createStateInteractionTypeMismatchError(); + } + + return serverParams.state; } /** @@ -244,20 +265,20 @@ export abstract class ClientApplication { * @param responseHash * @param interactionHandler */ - private async handleHash(responseHash: string): Promise { - const encodedTokenRequest = this.browserStorage.getTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, true); - const cachedRequest = JSON.parse(this.browserCrypto.base64Decode(encodedTokenRequest)) as AuthorizationCodeRequest; + private async handleHash(hash: string, state: string): Promise { + const cachedRequest = this.browserStorage.getCachedRequest(state, this.browserCrypto); const serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.handleRedirectPromise, cachedRequest.correlationId); - // Deserialize hash fragment response parameters. - const serverParams = BrowserProtocolUtils.parseServerResponseFromHash(responseHash); - try { // Hash contains known properties - handle and return in callback - const currentAuthority = this.browserStorage.getCachedAuthority(serverParams.state); + const currentAuthority = this.browserStorage.getCachedAuthority(state); + if (!currentAuthority) { + throw BrowserAuthError.createNoCachedAuthorityError(); + } + const authClient = await this.createAuthCodeClient(serverTelemetryManager, currentAuthority); - const interactionHandler = new RedirectHandler(authClient, this.browserStorage, this.browserCrypto); - return await interactionHandler.handleCodeResponse(responseHash, authClient.authority, this.networkClient, this.config.auth.clientId); + const interactionHandler = new RedirectHandler(authClient, this.browserStorage, cachedRequest, this.browserCrypto); + return await interactionHandler.handleCodeResponse(hash, state, authClient.authority, this.networkClient, this.config.auth.clientId); } catch (e) { serverTelemetryManager.cacheFailedRequest(e); this.browserStorage.cleanRequestByInteractionType(InteractionType.Redirect); @@ -297,7 +318,7 @@ export abstract class ClientApplication { const authClient: AuthorizationCodeClient = await this.createAuthCodeClient(serverTelemetryManager, validRequest.authority); // Create redirect interaction handler. - const interactionHandler = new RedirectHandler(authClient, this.browserStorage, this.browserCrypto); + const interactionHandler = new RedirectHandler(authClient, this.browserStorage, authCodeRequest, this.browserCrypto); // Create acquire token url. const navigateUrl = await authClient.getAuthCodeUrl(validRequest); @@ -305,7 +326,7 @@ export abstract class ClientApplication { const redirectStartPage = (request && request.redirectStartPage) || window.location.href; // 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 interactionHandler.initiateAuthRequest(navigateUrl, authCodeRequest, { + return interactionHandler.initiateAuthRequest(navigateUrl, { redirectTimeout: this.config.system.redirectNavigationTimeout, redirectStartPage: redirectStartPage, onRedirectNavigate: request.onRedirectNavigate @@ -382,22 +403,23 @@ export abstract class ClientApplication { const navigateUrl = await authClient.getAuthCodeUrl(validRequest); // Create popup interaction handler. - const interactionHandler = new PopupHandler(authClient, this.browserStorage); + const interactionHandler = new PopupHandler(authClient, this.browserStorage, authCodeRequest); // Show the UI once the url has been created. Get the window handle for the popup. const popupParameters = { popup: popup }; - const popupWindow: Window = interactionHandler.initiateAuthRequest(navigateUrl, authCodeRequest, popupParameters); + const popupWindow: Window = interactionHandler.initiateAuthRequest(navigateUrl, popupParameters); // Monitor the window for the hash. Return the string value and close the popup when the hash is received. Default timeout is 60 seconds. const hash = await interactionHandler.monitorPopupForHash(popupWindow, this.config.system.windowHashTimeout); + const state = this.validateAndExtractStateFromHash(hash, InteractionType.Popup); // Remove throttle if it exists ThrottlingUtils.removeThrottle(this.browserStorage, this.config.auth.clientId, authCodeRequest.authority, authCodeRequest.scopes); // Handle response from hash string. - const result = await interactionHandler.handleCodeResponse(hash, authClient.authority, this.networkClient); + const result = await interactionHandler.handleCodeResponse(hash, state, authClient.authority, this.networkClient); // If logged in, emit acquire token events const isLoggingIn = loggedInAccounts.length < this.getAllAccounts().length; @@ -541,13 +563,15 @@ export abstract class ClientApplication { */ private async silentTokenHelper(navigateUrl: string, authCodeRequest: AuthorizationCodeRequest, authClient: AuthorizationCodeClient): Promise { // Create silent handler - const silentHandler = new SilentHandler(authClient, this.browserStorage, this.config.system.navigateFrameWait); + const silentHandler = new SilentHandler(authClient, this.browserStorage, authCodeRequest, this.config.system.navigateFrameWait); // Get the frame handle for the silent request - const msalFrame = await silentHandler.initiateAuthRequest(navigateUrl, authCodeRequest); + const msalFrame = await silentHandler.initiateAuthRequest(navigateUrl); // Monitor the window for the hash. Return the string value and close the popup when the hash is received. Default timeout is 60 seconds. const hash = await silentHandler.monitorIframeForHash(msalFrame, this.config.system.iframeHashTimeout); + const state = this.validateAndExtractStateFromHash(hash, InteractionType.Silent); + // Handle response from hash string - return silentHandler.handleCodeResponse(hash, authClient.authority, this.networkClient); + return silentHandler.handleCodeResponse(hash, state, authClient.authority, this.networkClient); } // #endregion @@ -560,11 +584,13 @@ export abstract class ClientApplication { * @param {@link (EndSessionRequest:type)} */ async logout(logoutRequest?: EndSessionRequest): Promise { + this.preflightBrowserEnvironmentCheck(InteractionType.Redirect); + const validLogoutRequest = this.initializeLogoutRequest(logoutRequest); + const serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.logout, validLogoutRequest.correlationId); + try { - this.preflightBrowserEnvironmentCheck(InteractionType.Redirect); this.emitEvent(EventType.LOGOUT_START, InteractionType.Redirect, logoutRequest); - const validLogoutRequest = this.initializeLogoutRequest(logoutRequest); - const authClient = await this.createAuthCodeClient(null, logoutRequest && logoutRequest.authority); + const authClient = await this.createAuthCodeClient(serverTelemetryManager, logoutRequest && logoutRequest.authority); // create logout string and navigate user window to logout. Auth module will clear cache. const logoutUri: string = authClient.getLogoutUri(validLogoutRequest); this.emitEvent(EventType.LOGOUT_SUCCESS, InteractionType.Redirect, validLogoutRequest); @@ -587,6 +613,7 @@ export abstract class ClientApplication { return BrowserUtils.navigateWindow(logoutUri, this.config.system.redirectNavigationTimeout, this.logger); } } catch(e) { + serverTelemetryManager.cacheFailedRequest(e); this.emitEvent(EventType.LOGOUT_FAILURE, InteractionType.Redirect, null, e); throw e; } @@ -750,7 +777,7 @@ export abstract class ClientApplication { */ protected async getClientConfiguration(serverTelemetryManager: ServerTelemetryManager, requestAuthority?: string): Promise { // If the requestAuthority is passed and is not equivalent to the default configured authority, create new authority and discover endpoints. Return default authority otherwise. - const discoveredAuthority = (!StringUtils.isEmpty(requestAuthority) && requestAuthority !== this.config.auth.authority) ? await AuthorityFactory.createDiscoveredInstance(requestAuthority, this.config.system.networkClient, this.config.auth.protocolMode) + const discoveredAuthority = (requestAuthority && requestAuthority !== this.config.auth.authority) ? await AuthorityFactory.createDiscoveredInstance(requestAuthority, this.config.system.networkClient, this.config.auth.protocolMode) : await this.getDiscoveredDefaultAuthority(); return { authOptions: { @@ -823,10 +850,7 @@ export abstract class ClientApplication { * @param request */ protected initializeBaseRequest(request: Partial): BaseAuthRequest { - let authority = request.authority; - if (StringUtils.isEmpty(authority)) { - authority = this.config.auth.authority; - } + const authority = request.authority || this.config.auth.authority; const scopes = [...((request && request.scopes) || [])]; const correlationId = (request && request.correlationId) || this.browserCrypto.createNewGuid(); @@ -868,28 +892,27 @@ export abstract class ClientApplication { browserState ); - let nonce = request.nonce; - if (StringUtils.isEmpty(nonce)) { - nonce = this.browserCrypto.createNewGuid(); - } - const authenticationScheme = request.authenticationScheme || AuthenticationScheme.BEARER; const validatedRequest: AuthorizationUrlRequest = { ...this.initializeBaseRequest(request), redirectUri: redirectUri, state: state, - nonce: nonce, + nonce: request.nonce || this.browserCrypto.createNewGuid(), responseMode: ResponseMode.FRAGMENT, - authenticationScheme: authenticationScheme, - account: request.account || this.getActiveAccount() + authenticationScheme: authenticationScheme }; + const account = request.account || this.getActiveAccount(); + if (account) { + validatedRequest.account = account; + } + // Check for ADAL SSO if (StringUtils.isEmpty(validatedRequest.loginHint)) { // Only check for adal token if no SSO params are being used const adalIdTokenString = this.browserStorage.getTemporaryCache(PersistentCacheKeys.ADAL_ID_TOKEN); - if (!StringUtils.isEmpty(adalIdTokenString)) { + if (adalIdTokenString) { const adalIdToken = new IdToken(adalIdTokenString, this.browserCrypto); this.browserStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN); if (adalIdToken.claims && adalIdToken.claims.upn) { @@ -928,7 +951,7 @@ export abstract class ClientApplication { * @param logoutRequest */ protected initializeLogoutRequest(logoutRequest?: EndSessionRequest): CommonEndSessionRequest { - const validLogoutRequest = { + const validLogoutRequest: CommonEndSessionRequest = { correlationId: this.browserCrypto.createNewGuid(), ...logoutRequest }; diff --git a/lib/msal-browser/src/app/PublicClientApplication.ts b/lib/msal-browser/src/app/PublicClientApplication.ts index de5f1118a7..0a5816aa65 100644 --- a/lib/msal-browser/src/app/PublicClientApplication.ts +++ b/lib/msal-browser/src/app/PublicClientApplication.ts @@ -12,6 +12,7 @@ import { PopupRequest } from "../request/PopupRequest"; import { ClientApplication } from "./ClientApplication"; import { SilentRequest } from "../request/SilentRequest"; import { EventType } from "../event/EventType"; +import { BrowserAuthError } from "../error/BrowserAuthError"; /** * The PublicClientApplication class is the object exposed by the library to perform authentication and authorization functions in Single Page Applications @@ -76,10 +77,14 @@ export class PublicClientApplication extends ClientApplication implements IPubli */ async acquireTokenSilent(request: SilentRequest): Promise { this.preflightBrowserEnvironmentCheck(InteractionType.Silent); + const account = request.account || this.getActiveAccount(); + if (!account) { + throw BrowserAuthError.createNoAccountError(); + } const silentRequest: SilentFlowRequest = { ...request, ...this.initializeBaseRequest(request), - account: request.account || this.getActiveAccount(), + account: account, forceRefresh: request.forceRefresh || false }; this.emitEvent(EventType.ACQUIRE_TOKEN_START, InteractionType.Silent, request); diff --git a/lib/msal-browser/src/cache/BrowserCacheManager.ts b/lib/msal-browser/src/cache/BrowserCacheManager.ts index a7fab50a1c..4e669d74a4 100644 --- a/lib/msal-browser/src/cache/BrowserCacheManager.ts +++ b/lib/msal-browser/src/cache/BrowserCacheManager.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. */ -import { Constants, PersistentCacheKeys, StringUtils, AuthorizationCodeRequest, ICrypto, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity, CacheManager, ServerTelemetryEntity, ThrottlingEntity, ProtocolUtils, Logger } from "@azure/msal-common"; +import { Constants, PersistentCacheKeys, StringUtils, AuthorizationCodeRequest, ICrypto, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, AppMetadataEntity, CacheManager, ServerTelemetryEntity, ThrottlingEntity, ProtocolUtils, Logger, DEFAULT_CRYPTO_IMPLEMENTATION } from "@azure/msal-common"; import { CacheOptions } from "../config/Configuration"; -import { CryptoOps } from "../crypto/CryptoOps"; import { BrowserAuthError } from "../error/BrowserAuthError"; import { BrowserCacheLocation, InteractionType, TemporaryCacheKeys } from "../utils/BrowserConstants"; import { BrowserStorage } from "./BrowserStorage"; @@ -32,7 +31,7 @@ export class BrowserCacheManager extends CacheManager { // Cookie life calculation (hours * minutes * seconds * ms) private readonly COOKIE_LIFE_MULTIPLIER = 24 * 60 * 60 * 1000; - constructor(clientId: string, cacheConfig: CacheOptions, cryptoImpl: CryptoOps, logger: Logger) { + constructor(clientId: string, cacheConfig: Required, cryptoImpl: ICrypto, logger: Logger) { super(clientId, cryptoImpl); this.cacheConfig = cacheConfig; @@ -93,7 +92,7 @@ export class BrowserCacheManager extends CacheManager { * @param value * @param storeAuthStateInCookie */ - private migrateCacheEntry(newKey: string, value: string): void { + private migrateCacheEntry(newKey: string, value: string|null): void { if (value) { this.setTemporaryCache(newKey, value, true); } @@ -103,7 +102,7 @@ export class BrowserCacheManager extends CacheManager { * Parses passed value as JSON object, JSON.parse() will throw an error. * @param input */ - private validateAndParseJson(jsonValue: string): object { + private validateAndParseJson(jsonValue: string): object | null { try { const parsedJson = JSON.parse(jsonValue); /** @@ -122,7 +121,7 @@ export class BrowserCacheManager extends CacheManager { * fetches the entry from the browser storage based off the key * @param key */ - getItem(key: string): string { + getItem(key: string): string | null { return this.browserStorage.getItem(key); } @@ -141,11 +140,15 @@ export class BrowserCacheManager extends CacheManager { */ getAccount(accountKey: string): AccountEntity | null { const account = this.getItem(accountKey); - if (StringUtils.isEmpty(account)) { + if (!account) { return null; } const parsedAccount = this.validateAndParseJson(account); + if (!parsedAccount) { + return null; + } + const accountEntity = CacheManager.toObject(new AccountEntity(), parsedAccount); if (AccountEntity.isAccountEntity(accountEntity)) { return accountEntity; @@ -167,13 +170,17 @@ export class BrowserCacheManager extends CacheManager { * generates idToken entity from a string * @param idTokenKey */ - getIdTokenCredential(idTokenKey: string): IdTokenEntity { + getIdTokenCredential(idTokenKey: string): IdTokenEntity | null { const value = this.getItem(idTokenKey); - if (StringUtils.isEmpty(value)) { + if (!value) { return null; } const parsedIdToken = this.validateAndParseJson(value); + if (!parsedIdToken) { + return null; + } + const idToken: IdTokenEntity = CacheManager.toObject(new IdTokenEntity(), parsedIdToken); if (IdTokenEntity.isIdTokenEntity(idToken)) { return idToken; @@ -194,12 +201,16 @@ export class BrowserCacheManager extends CacheManager { * generates accessToken entity from a string * @param key */ - getAccessTokenCredential(accessTokenKey: string): AccessTokenEntity { + getAccessTokenCredential(accessTokenKey: string): AccessTokenEntity | null { const value = this.getItem(accessTokenKey); - if (StringUtils.isEmpty(value)) { + if (!value) { return null; } const parsedAccessToken = this.validateAndParseJson(value); + if (!parsedAccessToken) { + return null; + } + const accessToken: AccessTokenEntity = CacheManager.toObject(new AccessTokenEntity(), parsedAccessToken); if (AccessTokenEntity.isAccessTokenEntity(accessToken)) { return accessToken; @@ -220,12 +231,16 @@ export class BrowserCacheManager extends CacheManager { * generates refreshToken entity from a string * @param refreshTokenKey */ - getRefreshTokenCredential(refreshTokenKey: string): RefreshTokenEntity { + getRefreshTokenCredential(refreshTokenKey: string): RefreshTokenEntity | null { const value = this.getItem(refreshTokenKey); - if (StringUtils.isEmpty(value)) { + if (!value) { return null; } const parsedRefreshToken = this.validateAndParseJson(value); + if (!parsedRefreshToken) { + return null; + } + const refreshToken: RefreshTokenEntity = CacheManager.toObject(new RefreshTokenEntity(), parsedRefreshToken); if (RefreshTokenEntity.isRefreshTokenEntity(refreshToken)) { return refreshToken; @@ -246,13 +261,17 @@ export class BrowserCacheManager extends CacheManager { * fetch appMetadata entity from the platform cache * @param appMetadataKey */ - getAppMetadata(appMetadataKey: string): AppMetadataEntity { + getAppMetadata(appMetadataKey: string): AppMetadataEntity | null { const value = this.getItem(appMetadataKey); - if (StringUtils.isEmpty(value)) { + if (!value) { return null; } const parsedMetadata = this.validateAndParseJson(value); + if (!parsedMetadata) { + return null; + } + const appMetadata: AppMetadataEntity = CacheManager.toObject(new AppMetadataEntity(), parsedMetadata); if (AppMetadataEntity.isAppMetadataEntity(appMetadataKey, appMetadata)) { return appMetadata; @@ -275,10 +294,14 @@ export class BrowserCacheManager extends CacheManager { */ getServerTelemetry(serverTelemetryKey: string): ServerTelemetryEntity | null { const value = this.getItem(serverTelemetryKey); - if (StringUtils.isEmpty(value)) { + if (!value) { return null; } const parsedMetadata = this.validateAndParseJson(value); + if (!parsedMetadata) { + return null; + } + const serverTelemetryEntity = CacheManager.toObject(new ServerTelemetryEntity(), parsedMetadata); if (ServerTelemetryEntity.isServerTelemetryEntity(serverTelemetryKey, serverTelemetryEntity)) { return serverTelemetryEntity; @@ -301,11 +324,15 @@ export class BrowserCacheManager extends CacheManager { */ getThrottlingCache(throttlingCacheKey: string): ThrottlingEntity | null { const value = this.getItem(throttlingCacheKey); - if (StringUtils.isEmpty(value)) { + if (!value) { return null; } const parsedThrottlingCache = this.validateAndParseJson(value); + if (!parsedThrottlingCache) { + return null; + } + const throttlingCache = CacheManager.toObject(new ThrottlingEntity(), parsedThrottlingCache); if (ThrottlingEntity.isThrottlingEntity(throttlingCacheKey, throttlingCache)) { return throttlingCache; @@ -327,7 +354,7 @@ export class BrowserCacheManager extends CacheManager { * Will retrieve frm cookies if storeAuthStateInCookie is set to true. * @param key */ - getTemporaryCache(cacheKey: string, generateKey?: boolean): string { + getTemporaryCache(cacheKey: string, generateKey?: boolean): string | null { const key = generateKey ? this.generateCacheKey(cacheKey) : cacheKey; if (this.cacheConfig.storeAuthStateInCookie) { const itemCookie = this.getItemCookie(key); @@ -337,7 +364,7 @@ export class BrowserCacheManager extends CacheManager { } const value = this.getItem(key); - if (StringUtils.isEmpty(value)) { + if (!value) { return null; } return value; @@ -444,16 +471,6 @@ export class BrowserCacheManager extends CacheManager { this.setItemCookie(cookieName, "", -1); } - /** - * Clear all msal cookies - */ - clearMsalCookie(stateString?: string): void { - const nonceKey = stateString ? this.generateNonceKey(stateString) : this.generateStateKey(TemporaryCacheKeys.NONCE_IDTOKEN); - this.clearItemCookie(this.generateStateKey(stateString)); - this.clearItemCookie(nonceKey); - this.clearItemCookie(this.generateCacheKey(TemporaryCacheKeys.ORIGIN_URI)); - } - /** * Get cookie expiration time * @param cookieLifeDays @@ -538,21 +555,10 @@ export class BrowserCacheManager extends CacheManager { return this.generateCacheKey(`${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`); } - /** - * Sets the cacheKey for and stores the authority information in cache - * @param state - * @param authority - */ - setAuthorityCache(authority: string, state: string): void { - // Cache authorityKey - const authorityCacheKey = this.generateAuthorityKey(state); - this.setItem(authorityCacheKey, authority); - } - /** * Gets the cached authority based on the cached state. Returns empty if no cached state found. */ - getCachedAuthority(cachedState: string): string { + getCachedAuthority(cachedState: string): string | null { const stateCacheKey = this.generateStateKey(cachedState); const state = this.getTemporaryCache(stateCacheKey); if (!state) { @@ -578,7 +584,8 @@ export class BrowserCacheManager extends CacheManager { this.setTemporaryCache(nonceCacheKey, nonce, false); // Cache authorityKey - this.setAuthorityCache(authorityInstance, state); + const authorityCacheKey = this.generateAuthorityKey(state); + this.setTemporaryCache(authorityCacheKey, authorityInstance, false); } /** @@ -621,8 +628,11 @@ export class BrowserCacheManager extends CacheManager { } const value = this.browserStorage.getItem(key); + if (!value) { + return; + } const parsedState = BrowserProtocolUtils.extractBrowserRequestState(this.cryptoImpl, value); - if (parsedState.interactionType === interactionType) { + if (parsedState && parsedState.interactionType === interactionType) { this.resetRequestCache(value); } }); @@ -637,22 +647,36 @@ export class BrowserCacheManager extends CacheManager { * Gets the token exchange parameters from the cache. Throws an error if nothing is found. */ getCachedRequest(state: string, browserCrypto: ICrypto): AuthorizationCodeRequest { - try { - // Get token request from cache and parse as TokenExchangeParameters. - const encodedTokenRequest = this.getTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, true); + // Get token request from cache and parse as TokenExchangeParameters. + const encodedTokenRequest = this.getTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, true); + if (!encodedTokenRequest) { + throw BrowserAuthError.createNoTokenRequestCacheError(); + } - const parsedRequest = JSON.parse(browserCrypto.base64Decode(encodedTokenRequest)) as AuthorizationCodeRequest; - this.removeItem(this.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS)); + const parsedRequest = this.validateAndParseJson(browserCrypto.base64Decode(encodedTokenRequest)) as AuthorizationCodeRequest; + 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)) { - const authorityCacheKey: string = this.generateAuthorityKey(state); - const cachedAuthority: string = this.getTemporaryCache(authorityCacheKey); - parsedRequest.authority = cachedAuthority; + // Get cached authority and use if no authority is cached with request. + if (StringUtils.isEmpty(parsedRequest.authority)) { + const authorityCacheKey: string = this.generateAuthorityKey(state); + const cachedAuthority = this.getTemporaryCache(authorityCacheKey); + if (!cachedAuthority) { + throw BrowserAuthError.createNoCachedAuthorityError(); } - return parsedRequest; - } catch (err) { - throw BrowserAuthError.createTokenRequestCacheError(err); + parsedRequest.authority = cachedAuthority; } + + return parsedRequest; } } + +export const DEFAULT_BROWSER_CACHE_MANAGER = (clientId: string, logger: Logger) => { + const cacheOptions = { + cacheLocation: BrowserCacheLocation.MemoryStorage, + storeAuthStateInCookie: false + }; + return new BrowserCacheManager(clientId, cacheOptions, DEFAULT_CRYPTO_IMPLEMENTATION, logger); +}; diff --git a/lib/msal-browser/src/cache/BrowserStorage.ts b/lib/msal-browser/src/cache/BrowserStorage.ts index 91cc354580..3d6fba3283 100644 --- a/lib/msal-browser/src/cache/BrowserStorage.ts +++ b/lib/msal-browser/src/cache/BrowserStorage.ts @@ -9,20 +9,11 @@ import { IWindowStorage } from "./IWindowStorage"; export class BrowserStorage implements IWindowStorage { - private _windowStorage: Storage; - private cacheLocation: string; - - public get windowStorage(): Storage { - if (!this._windowStorage) { - this._windowStorage = window[this.cacheLocation]; - } - - return this._windowStorage; - } + private windowStorage: Storage; constructor(cacheLocation: string) { this.validateWindowStorage(cacheLocation); - this.cacheLocation = cacheLocation; + this.windowStorage = window[cacheLocation]; } private validateWindowStorage(cacheLocation: string) { @@ -35,7 +26,7 @@ export class BrowserStorage implements IWindowStorage { } } - getItem(key: string): string { + getItem(key: string): string | null { return this.windowStorage.getItem(key); } diff --git a/lib/msal-browser/src/cache/DatabaseStorage.ts b/lib/msal-browser/src/cache/DatabaseStorage.ts index 6c2f10e638..8c7703af94 100644 --- a/lib/msal-browser/src/cache/DatabaseStorage.ts +++ b/lib/msal-browser/src/cache/DatabaseStorage.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. */ +import { BrowserAuthError } from "../error/BrowserAuthError"; + interface IDBOpenDBRequestEvent extends Event { target: IDBOpenDBRequest & EventTarget; } @@ -19,7 +21,7 @@ interface IDBRequestEvent extends Event { * Storage wrapper for IndexedDB storage in browsers: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API */ export class DatabaseStorage{ - private db : IDBDatabase; + private db: IDBDatabase|undefined; private dbName: string; private tableName: string; private version: number; @@ -39,11 +41,13 @@ export class DatabaseStorage{ return new Promise((resolve, reject) => { // TODO: Add timeouts? const openDB = window.indexedDB.open(this.dbName, this.version); - openDB.addEventListener("upgradeneeded", (e: IDBOpenOnUpgradeNeededEvent) => { - e.target.result.createObjectStore(this.tableName); + openDB.addEventListener("upgradeneeded", (e: any) => { + const event = e as IDBOpenOnUpgradeNeededEvent; + event.target.result.createObjectStore(this.tableName); }); - openDB.addEventListener("success", (e: IDBOpenDBRequestEvent) => { - this.db = e.target.result; + openDB.addEventListener("success", (e: any) => { + const event = e as IDBOpenDBRequestEvent; + this.db = event.target.result; this.dbOpen = true; resolve(); }); @@ -63,11 +67,18 @@ export class DatabaseStorage{ return new Promise((resolve, reject) => { // TODO: Add timeouts? + if (!this.db) { + return reject(BrowserAuthError.createDatabaseNotOpenError()); + } + const transaction = this.db.transaction([this.tableName], "readonly"); const objectStore = transaction.objectStore(this.tableName); const dbGet = objectStore.get(key); - dbGet.addEventListener("success", (e: IDBRequestEvent) => resolve(e.target.result)); + dbGet.addEventListener("success", (e: any) => { + const event = e as IDBRequestEvent; + resolve(event.target.result); + }); dbGet.addEventListener("error", e => reject(e)); }); } @@ -84,11 +95,18 @@ export class DatabaseStorage{ return new Promise((resolve: any, reject: any) => { // TODO: Add timeouts? + if (!this.db) { + return reject(BrowserAuthError.createDatabaseNotOpenError()); + } + const transaction = this.db.transaction([this.tableName], "readwrite"); const objectStore = transaction.objectStore(this.tableName); const dbPut = objectStore.put(payload, key); - dbPut.addEventListener("success", (e: IDBRequestEvent) => resolve(e.target.result)); + dbPut.addEventListener("success", (e: any) => { + const event = e as IDBRequestEvent; + resolve(event.target.result); + }); dbPut.addEventListener("error", e => reject(e)); }); } diff --git a/lib/msal-browser/src/cache/IWindowStorage.ts b/lib/msal-browser/src/cache/IWindowStorage.ts index 56bc767872..d4bdbc662a 100644 --- a/lib/msal-browser/src/cache/IWindowStorage.ts +++ b/lib/msal-browser/src/cache/IWindowStorage.ts @@ -8,7 +8,7 @@ export interface IWindowStorage { * Get the item from the window storage object matching the given key. * @param key */ - getItem(key: string): string; + getItem(key: string): string | null; /** * Sets the item in the window storage object with the given key. diff --git a/lib/msal-browser/src/cache/MemoryStorage.ts b/lib/msal-browser/src/cache/MemoryStorage.ts index 6ffdbc58dd..86d0cd90dc 100644 --- a/lib/msal-browser/src/cache/MemoryStorage.ts +++ b/lib/msal-browser/src/cache/MemoryStorage.ts @@ -13,7 +13,7 @@ export class MemoryStorage implements IWindowStorage { this.cache = new Map(); } - getItem(key: string): string { + getItem(key: string): string | null { return this.cache.get(key) || null; } diff --git a/lib/msal-browser/src/config/Configuration.ts b/lib/msal-browser/src/config/Configuration.ts index f666fdf252..8d36522133 100644 --- a/lib/msal-browser/src/config/Configuration.ts +++ b/lib/msal-browser/src/config/Configuration.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { SystemOptions, LoggerOptions, INetworkModule, DEFAULT_SYSTEM_OPTIONS, Constants, ProtocolMode, LogLevel } from "@azure/msal-common"; +import { SystemOptions, LoggerOptions, INetworkModule, DEFAULT_SYSTEM_OPTIONS, Constants, ProtocolMode, LogLevel, StubbedNetworkModule } from "@azure/msal-common"; import { BrowserUtils } from "../utils/BrowserUtils"; import { BrowserCacheLocation } from "../utils/BrowserConstants"; @@ -83,7 +83,7 @@ export type BrowserSystemOptions = SystemOptions & { * - system: this is where you can configure the network client, logger, token renewal offset */ export type Configuration = { - auth?: BrowserAuthOptions, + auth: BrowserAuthOptions, cache?: CacheOptions, system?: BrowserSystemOptions }; @@ -103,7 +103,7 @@ export type BrowserConfiguration = { * * @returns Configuration object */ -export function buildConfiguration({ auth: userInputAuth, cache: userInputCache, system: userInputSystem }: Configuration): BrowserConfiguration { +export function buildConfiguration({ auth: userInputAuth, cache: userInputCache, system: userInputSystem }: Configuration, isBrowserEnvironment: boolean): BrowserConfiguration { // Default auth options for browser const DEFAULT_AUTH_OPTIONS: Required = { @@ -135,12 +135,12 @@ export function buildConfiguration({ auth: userInputAuth, cache: userInputCache, const DEFAULT_BROWSER_SYSTEM_OPTIONS: Required = { ...DEFAULT_SYSTEM_OPTIONS, loggerOptions: DEFAULT_LOGGER_OPTIONS, - networkClient: BrowserUtils.getBrowserNetworkClient(), + networkClient: isBrowserEnvironment ? BrowserUtils.getBrowserNetworkClient() : StubbedNetworkModule, loadFrameTimeout: 0, // If loadFrameTimeout is provided, use that as default. windowHashTimeout: (userInputSystem && userInputSystem.loadFrameTimeout) || DEFAULT_POPUP_TIMEOUT_MS, iframeHashTimeout: (userInputSystem && userInputSystem.loadFrameTimeout) || DEFAULT_IFRAME_TIMEOUT_MS, - navigateFrameWait: BrowserUtils.detectIEOrEdge() ? 500 : 0, + navigateFrameWait: isBrowserEnvironment && BrowserUtils.detectIEOrEdge() ? 500 : 0, redirectNavigationTimeout: DEFAULT_REDIRECT_TIMEOUT_MS, asyncPopups: false, allowRedirectInIframe: false diff --git a/lib/msal-browser/src/error/BrowserAuthError.ts b/lib/msal-browser/src/error/BrowserAuthError.ts index 5f481bbc2d..089f410250 100644 --- a/lib/msal-browser/src/error/BrowserAuthError.ts +++ b/lib/msal-browser/src/error/BrowserAuthError.ts @@ -29,6 +29,18 @@ export const BrowserAuthErrorMessage = { code: "hash_empty_error", desc: "Hash value cannot be processed because it is empty." }, + hashDoesNotContainStateError: { + code: "no_state_in_hash", + desc: "Hash does not contain state. Please verify that the request originated from msal." + }, + unableToParseStateError: { + code: "unable_to_parse_state", + desc: "Unable to parse state. Please verify that the request originated from msal." + }, + stateInteractionTypeMismatchError: { + code: "state_interaction_type_mismatch", + desc: "Hash contains state but the interaction type does not match the caller." + }, interactionInProgress: { code: "interaction_in_progress", desc: "Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API." @@ -69,13 +81,29 @@ export const BrowserAuthErrorMessage = { code: "silent_sso_error", desc: "Silent SSO could not be completed - insufficient information was provided. Please provide either a loginHint or sid." }, + noAccountError: { + code: "no_account_error", + desc: "No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request." + }, silentPromptValueError: { code: "silent_prompt_value_error", desc: "The value given for the prompt value is not valid for silent requests - must be set to 'none'." }, - tokenRequestCacheError: { - code: "token_request_cache_error", - desc: "The token request could not be fetched from the cache correctly." + noTokenRequestCacheError: { + code: "no_token_request_cache_error", + desc: "No token request in found in cache." + }, + unableToParseTokenRequestCacheError: { + code: "unable_to_parse_token_request_cache_error", + desc: "The cached token request could not be parsed." + }, + noCachedAuthorityError: { + code: "no_cached_authority_error", + desc: "No cached authority found." + }, + authRequestNotSet: { + code: "auth_request_not_set_error", + desc: "Auth Request not set. Please ensure initiateAuthRequest was called from the InteractionHandler" }, invalidCacheType: { code: "invalid_cache_type", @@ -84,6 +112,10 @@ export const BrowserAuthErrorMessage = { notInBrowserEnvironment: { code: "non_browser_environment", desc: "Login and token requests are not supported in non-browser environments." + }, + databaseNotOpen: { + code: "database_not_open", + desc: "Database is not open!" } }; @@ -141,6 +173,27 @@ export class BrowserAuthError extends AuthError { return new BrowserAuthError(BrowserAuthErrorMessage.hashEmptyError.code, `${BrowserAuthErrorMessage.hashEmptyError.desc} Given Url: ${hashValue}`); } + /** + * Creates an error thrown when the hash string value is unexpectedly empty. + */ + static createHashDoesNotContainStateError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.hashDoesNotContainStateError.code, BrowserAuthErrorMessage.hashDoesNotContainStateError.desc); + } + + /** + * Creates an error thrown when the hash string value is unexpectedly empty. + */ + static createUnableToParseStateError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.unableToParseStateError.code, BrowserAuthErrorMessage.unableToParseStateError.desc); + } + + /** + * Creates an error thrown when the state value in the hash does not match the interaction type of the API attempting to consume it. + */ + static createStateInteractionTypeMismatchError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.stateInteractionTypeMismatchError.code, BrowserAuthErrorMessage.stateInteractionTypeMismatchError.desc); + } + /** * Creates an error thrown when a browser interaction (redirect or popup) is in progress. */ @@ -221,6 +274,13 @@ export class BrowserAuthError extends AuthError { return new BrowserAuthError(BrowserAuthErrorMessage.silentSSOInsufficientInfoError.code, BrowserAuthErrorMessage.silentSSOInsufficientInfoError.desc); } + /** + * Creates an error thrown when the account object is not provided in the acquireTokenSilent API. + */ + static createNoAccountError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.noAccountError.code, BrowserAuthErrorMessage.noAccountError.desc); + } + /** * Creates an error thrown when a given prompt value is invalid for silent requests. */ @@ -228,13 +288,36 @@ export class BrowserAuthError extends AuthError { return new BrowserAuthError(BrowserAuthErrorMessage.silentPromptValueError.code, `${BrowserAuthErrorMessage.silentPromptValueError.desc} Given value: ${givenPrompt}`); } + /** + * Creates an error thrown when the cached token request could not be retrieved from the cache + */ + static createUnableToParseTokenRequestCacheError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.unableToParseTokenRequestCacheError.code, + BrowserAuthErrorMessage.unableToParseTokenRequestCacheError.desc); + } + /** * Creates an error thrown when the token request could not be retrieved from the cache - * @param errDetail */ - static createTokenRequestCacheError(errDetail: string): BrowserAuthError { - return new BrowserAuthError(BrowserAuthErrorMessage.tokenRequestCacheError.code, - `${BrowserAuthErrorMessage.tokenRequestCacheError.desc} Error Detail: ${errDetail}`); + static createNoTokenRequestCacheError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.noTokenRequestCacheError.code, + BrowserAuthErrorMessage.noTokenRequestCacheError.desc); + } + + /** + * Creates an error thrown when handleCodeResponse is called before initiateAuthRequest (InteractionHandler) + */ + static createAuthRequestNotSetError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.authRequestNotSet.code, + BrowserAuthErrorMessage.authRequestNotSet.desc); + } + + /** + * Creates an error thrown when the authority could not be retrieved from the cache + */ + static createNoCachedAuthorityError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.noCachedAuthorityError.code, + BrowserAuthErrorMessage.noCachedAuthorityError.desc); } /** @@ -250,4 +333,11 @@ export class BrowserAuthError extends AuthError { static createNonBrowserEnvironmentError(): BrowserAuthError { return new BrowserAuthError(BrowserAuthErrorMessage.notInBrowserEnvironment.code, BrowserAuthErrorMessage.notInBrowserEnvironment.desc); } + + /** + * Create an error thrown when indexDB database is not open + */ + static createDatabaseNotOpenError(): BrowserAuthError { + return new BrowserAuthError(BrowserAuthErrorMessage.databaseNotOpen.code, BrowserAuthErrorMessage.databaseNotOpen.desc); + } } diff --git a/lib/msal-browser/src/interaction_handler/InteractionHandler.ts b/lib/msal-browser/src/interaction_handler/InteractionHandler.ts index c7c6d17049..c3072ede50 100644 --- a/lib/msal-browser/src/interaction_handler/InteractionHandler.ts +++ b/lib/msal-browser/src/interaction_handler/InteractionHandler.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. */ -import { StringUtils, AuthorizationCodeRequest, AuthenticationResult, AuthorizationCodeClient, AuthorityFactory, Authority, INetworkModule } from "@azure/msal-common"; +import { StringUtils, AuthorizationCodeRequest, AuthenticationResult, AuthorizationCodeClient, AuthorityFactory, Authority, INetworkModule, ClientAuthError } from "@azure/msal-common"; import { BrowserCacheManager } from "../cache/BrowserCacheManager"; import { BrowserAuthError } from "../error/BrowserAuthError"; -import { BrowserProtocolUtils } from "../utils/BrowserProtocolUtils"; export type InteractionParams = {}; @@ -19,33 +18,34 @@ export abstract class InteractionHandler { protected browserStorage: BrowserCacheManager; protected authCodeRequest: AuthorizationCodeRequest; - constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager) { + constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager, authCodeRequest: AuthorizationCodeRequest) { this.authModule = authCodeModule; this.browserStorage = storageImpl; + this.authCodeRequest = authCodeRequest; } /** * Function to enable user interaction. * @param requestUrl */ - abstract initiateAuthRequest(requestUrl: string, authCodeRequest: AuthorizationCodeRequest, params: InteractionParams): Window | Promise | Promise; + abstract initiateAuthRequest(requestUrl: string, params: InteractionParams): Window | Promise | Promise; /** * Function to handle response parameters from hash. * @param locationHash */ - async handleCodeResponse(locationHash: string, authority: Authority, networkModule: INetworkModule): Promise { + async handleCodeResponse(locationHash: string, state: string, authority: Authority, networkModule: INetworkModule): Promise { // Check that location hash isn't empty. if (StringUtils.isEmpty(locationHash)) { throw BrowserAuthError.createEmptyHashError(locationHash); } - // Deserialize hash fragment response parameters. - const serverParams = BrowserProtocolUtils.parseServerResponseFromHash(locationHash); - // Handle code response. - const stateKey = this.browserStorage.generateStateKey(serverParams.state); + const stateKey = this.browserStorage.generateStateKey(state); const requestState = this.browserStorage.getTemporaryCache(stateKey); + if (!requestState) { + throw ClientAuthError.createStateNotFoundError("Cached State"); + } const authCodeResponse = this.authModule.handleFragmentResponse(locationHash, requestState); // Get cached items @@ -60,12 +60,12 @@ export abstract class InteractionHandler { await this.updateTokenEndpointAuthority(authCodeResponse.cloud_instance_host_name, authority, networkModule); } - authCodeResponse.nonce = cachedNonce; + authCodeResponse.nonce = cachedNonce || undefined; authCodeResponse.state = requestState; // Acquire token with retrieved code. const tokenResponse = await this.authModule.acquireToken(this.authCodeRequest, authCodeResponse); - this.browserStorage.cleanRequestByState(serverParams.state); + this.browserStorage.cleanRequestByState(state); return tokenResponse; } diff --git a/lib/msal-browser/src/interaction_handler/PopupHandler.ts b/lib/msal-browser/src/interaction_handler/PopupHandler.ts index 516f57eaef..b0d8b8ff16 100644 --- a/lib/msal-browser/src/interaction_handler/PopupHandler.ts +++ b/lib/msal-browser/src/interaction_handler/PopupHandler.ts @@ -20,10 +20,10 @@ export type PopupParams = InteractionParams & { */ export class PopupHandler extends InteractionHandler { - private currentWindow: Window; + private currentWindow: Window|undefined; - constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager) { - super(authCodeModule, storageImpl); + constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager, authCodeRequest: AuthorizationCodeRequest) { + super(authCodeModule, storageImpl, authCodeRequest); // Properly sets this reference for the unload event. this.unloadWindow = this.unloadWindow.bind(this); @@ -33,11 +33,9 @@ export class PopupHandler extends InteractionHandler { * Opens a popup window with given request Url. * @param requestUrl */ - initiateAuthRequest(requestUrl: string, authCodeRequest: AuthorizationCodeRequest, params: PopupParams): Window { + initiateAuthRequest(requestUrl: string, params: PopupParams): Window { // Check that request url is not empty. if (!StringUtils.isEmpty(requestUrl)) { - // Save auth code request - this.authCodeRequest = authCodeRequest; // Set interaction status in the library. this.browserStorage.setTemporaryCache(TemporaryCacheKeys.INTERACTION_STATUS_KEY, BrowserConstants.INTERACTION_IN_PROGRESS_VALUE, true); this.authModule.logger.infoPii("Navigate to:" + requestUrl); @@ -74,7 +72,7 @@ export class PopupHandler extends InteractionHandler { return; } - let href: string; + let href: string = Constants.EMPTY_STRING; try { /* * Will throw if cross origin, @@ -175,9 +173,11 @@ export class PopupHandler extends InteractionHandler { */ unloadWindow(e: Event): void { this.browserStorage.cleanRequestByInteractionType(InteractionType.Popup); - this.currentWindow.close(); + if (this.currentWindow) { + this.currentWindow.close(); + } // Guarantees browser unload will happen, so no other errors will be thrown. - delete e["returnValue"]; + e.preventDefault(); } /** diff --git a/lib/msal-browser/src/interaction_handler/RedirectHandler.ts b/lib/msal-browser/src/interaction_handler/RedirectHandler.ts index ba814728f3..4fed97ca6c 100644 --- a/lib/msal-browser/src/interaction_handler/RedirectHandler.ts +++ b/lib/msal-browser/src/interaction_handler/RedirectHandler.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. */ -import { AuthorizationCodeClient, StringUtils, AuthorizationCodeRequest, ICrypto, AuthenticationResult, ThrottlingUtils, Authority, INetworkModule } from "@azure/msal-common"; +import { AuthorizationCodeClient, StringUtils, AuthorizationCodeRequest, ICrypto, AuthenticationResult, ThrottlingUtils, Authority, INetworkModule, ClientAuthError } from "@azure/msal-common"; import { BrowserAuthError } from "../error/BrowserAuthError"; import { BrowserConstants, TemporaryCacheKeys } from "../utils/BrowserConstants"; import { BrowserUtils } from "../utils/BrowserUtils"; -import { BrowserProtocolUtils } from "../utils/BrowserProtocolUtils"; import { BrowserCacheManager } from "../cache/BrowserCacheManager"; import { InteractionHandler, InteractionParams } from "./InteractionHandler"; @@ -21,8 +20,8 @@ export class RedirectHandler extends InteractionHandler { private browserCrypto: ICrypto; - constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager, browserCrypto: ICrypto) { - super(authCodeModule, storageImpl); + constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager, authCodeRequest: AuthorizationCodeRequest, browserCrypto: ICrypto) { + super(authCodeModule, storageImpl, authCodeRequest); this.browserCrypto = browserCrypto; } @@ -30,7 +29,7 @@ export class RedirectHandler extends InteractionHandler { * Redirects window to given URL. * @param urlNavigate */ - initiateAuthRequest(requestUrl: string, authCodeRequest: AuthorizationCodeRequest, params: RedirectParams): Promise { + initiateAuthRequest(requestUrl: string, params: RedirectParams): Promise { // Navigate if valid URL if (!StringUtils.isEmpty(requestUrl)) { // Cache start page, returns to this page after redirectUri if navigateToLoginRequestUrl is true @@ -40,7 +39,7 @@ export class RedirectHandler extends InteractionHandler { // Set interaction status in the library. this.browserStorage.setTemporaryCache(TemporaryCacheKeys.INTERACTION_STATUS_KEY, BrowserConstants.INTERACTION_IN_PROGRESS_VALUE, true); - this.browserStorage.cacheCodeRequest(authCodeRequest, this.browserCrypto); + this.browserStorage.cacheCodeRequest(this.authCodeRequest, this.browserCrypto); this.authModule.logger.infoPii("Navigate to:" + requestUrl); // If onRedirectNavigate is implemented, invoke it and provide requestUrl if (typeof params.onRedirectNavigate === "function") { @@ -71,7 +70,7 @@ export class RedirectHandler extends InteractionHandler { * Handle authorization code response in the window. * @param hash */ - async handleCodeResponse(locationHash: string, authority: Authority, networkModule: INetworkModule, clientId?: string): Promise { + async handleCodeResponse(locationHash: string, state: string, authority: Authority, networkModule: INetworkModule, clientId?: string): Promise { // Check that location hash isn't empty. if (StringUtils.isEmpty(locationHash)) { throw BrowserAuthError.createEmptyHashError(locationHash); @@ -80,18 +79,17 @@ export class RedirectHandler extends InteractionHandler { // Interaction is completed - remove interaction status. this.browserStorage.removeItem(this.browserStorage.generateCacheKey(TemporaryCacheKeys.INTERACTION_STATUS_KEY)); - // Deserialize hash fragment response parameters. - const serverParams = BrowserProtocolUtils.parseServerResponseFromHash(locationHash); - // Handle code response. - const stateKey = this.browserStorage.generateStateKey(serverParams.state); + const stateKey = this.browserStorage.generateStateKey(state); const requestState = this.browserStorage.getTemporaryCache(stateKey); + if (!requestState) { + throw ClientAuthError.createStateNotFoundError("Cached State"); + } const authCodeResponse = this.authModule.handleFragmentResponse(locationHash, requestState); // Get cached items const nonceKey = this.browserStorage.generateNonceKey(requestState); const cachedNonce = this.browserStorage.getTemporaryCache(nonceKey); - this.authCodeRequest = this.browserStorage.getCachedRequest(requestState, this.browserCrypto); // Assign code to request this.authCodeRequest.code = authCodeResponse.code; @@ -101,7 +99,7 @@ export class RedirectHandler extends InteractionHandler { await this.updateTokenEndpointAuthority(authCodeResponse.cloud_instance_host_name, authority, networkModule); } - authCodeResponse.nonce = cachedNonce; + authCodeResponse.nonce = cachedNonce || undefined; authCodeResponse.state = requestState; // Remove throttle if it exists @@ -112,7 +110,7 @@ export class RedirectHandler extends InteractionHandler { // Acquire token with retrieved code. const tokenResponse = await this.authModule.acquireToken(this.authCodeRequest, authCodeResponse); - this.browserStorage.cleanRequestByState(serverParams.state); + this.browserStorage.cleanRequestByState(state); return tokenResponse; } } diff --git a/lib/msal-browser/src/interaction_handler/SilentHandler.ts b/lib/msal-browser/src/interaction_handler/SilentHandler.ts index b103e778b0..6e81564a14 100644 --- a/lib/msal-browser/src/interaction_handler/SilentHandler.ts +++ b/lib/msal-browser/src/interaction_handler/SilentHandler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { UrlString, StringUtils, AuthorizationCodeRequest, AuthorizationCodeClient } from "@azure/msal-common"; +import { UrlString, StringUtils, AuthorizationCodeRequest, AuthorizationCodeClient, Constants } from "@azure/msal-common"; import { InteractionHandler } from "./InteractionHandler"; import { BrowserConstants } from "../utils/BrowserConstants"; import { BrowserAuthError } from "../error/BrowserAuthError"; @@ -13,8 +13,8 @@ import { DEFAULT_IFRAME_TIMEOUT_MS } from "../config/Configuration"; export class SilentHandler extends InteractionHandler { private navigateFrameWait: number; - constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager, navigateFrameWait: number) { - super(authCodeModule, storageImpl); + constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager, authCodeRequest: AuthorizationCodeRequest, navigateFrameWait: number) { + super(authCodeModule, storageImpl, authCodeRequest); this.navigateFrameWait = navigateFrameWait; } @@ -23,14 +23,12 @@ export class SilentHandler extends InteractionHandler { * @param urlNavigate * @param userRequestScopes */ - async initiateAuthRequest(requestUrl: string, authCodeRequest: AuthorizationCodeRequest): Promise { + async initiateAuthRequest(requestUrl: string): Promise { if (StringUtils.isEmpty(requestUrl)) { // Throw error if request URL is empty. this.authModule.logger.info("Navigate url is empty"); throw BrowserAuthError.createEmptyNavigationUriError(); } - // Save auth code request - this.authCodeRequest = authCodeRequest; return this.navigateFrameWait ? await this.loadFrame(requestUrl) : this.loadFrameSync(requestUrl); } @@ -61,21 +59,22 @@ export class SilentHandler extends InteractionHandler { return; } - let href: string; + let href: string = Constants.EMPTY_STRING; + const contentWindow = iframe.contentWindow; 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 = iframe.contentWindow.location.href; + href = contentWindow ? contentWindow.location.href : Constants.EMPTY_STRING; } catch (e) {} if (StringUtils.isEmpty(href)) { return; } - const contentHash = iframe.contentWindow.location.hash; + const contentHash = contentWindow ? contentWindow.location.hash: Constants.EMPTY_STRING; if (UrlString.hashContainsKnownProperties(contentHash)) { // Success case this.removeHiddenIframe(iframe); diff --git a/lib/msal-browser/src/network/FetchClient.ts b/lib/msal-browser/src/network/FetchClient.ts index f89ed0c61a..3f6aea2540 100644 --- a/lib/msal-browser/src/network/FetchClient.ts +++ b/lib/msal-browser/src/network/FetchClient.ts @@ -58,8 +58,9 @@ export class FetchClient implements INetworkModule { if (!(options && options.headers)) { return headers; } - Object.keys(options.headers).forEach((key) => { - headers.append(key, options.headers[key]); + const optionsHeaders = options.headers; + Object.keys(optionsHeaders).forEach((key) => { + headers.append(key, optionsHeaders[key]); }); return headers; } diff --git a/lib/msal-browser/src/network/XhrClient.ts b/lib/msal-browser/src/network/XhrClient.ts index 79fd33ce74..93e36ac75e 100644 --- a/lib/msal-browser/src/network/XhrClient.ts +++ b/lib/msal-browser/src/network/XhrClient.ts @@ -64,7 +64,7 @@ export class XhrClient implements INetworkModule { reject(xhr.status); }; - if (method === "POST" && options.body) { + if (method === "POST" && options && options.body) { xhr.send(options.body); } else if (method === "GET") { xhr.send(); @@ -81,8 +81,9 @@ export class XhrClient implements INetworkModule { */ private setXhrHeaders(xhr: XMLHttpRequest, options?: NetworkRequestOptions): void { if (options && options.headers) { - Object.keys(options.headers).forEach((key: string) => { - xhr.setRequestHeader(key, options.headers[key]); + const headers = options.headers; + Object.keys(headers).forEach((key: string) => { + xhr.setRequestHeader(key, headers[key]); }); } } @@ -101,7 +102,9 @@ export class XhrClient implements INetworkModule { const parts = value.split(": "); const headerName = parts.shift(); const headerVal = parts.join(": "); - headerDict[headerName] = headerVal; + if (headerName && headerVal) { + headerDict[headerName] = headerVal; + } }); return headerDict; diff --git a/lib/msal-browser/src/request/AuthorizationUrlRequest.ts b/lib/msal-browser/src/request/AuthorizationUrlRequest.ts index 0b6cb2a2b2..f68fa2971e 100644 --- a/lib/msal-browser/src/request/AuthorizationUrlRequest.ts +++ b/lib/msal-browser/src/request/AuthorizationUrlRequest.ts @@ -6,4 +6,7 @@ import { AuthorizationUrlRequest as CommonAuthorizationUrlRequest } from "@azure/msal-common"; // This type is deprecated and will be removed on the next major version update -export type AuthorizationUrlRequest = Partial; +export type AuthorizationUrlRequest = Omit & { + state: string; + nonce: string; +}; diff --git a/lib/msal-browser/src/utils/BrowserConstants.ts b/lib/msal-browser/src/utils/BrowserConstants.ts index 75fda7441e..03370feff0 100644 --- a/lib/msal-browser/src/utils/BrowserConstants.ts +++ b/lib/msal-browser/src/utils/BrowserConstants.ts @@ -68,7 +68,8 @@ export enum ApiId { ssoSilent = 863, acquireTokenSilent_authCode = 864, handleRedirectPromise = 865, - acquireTokenSilent_silentFlow = 61 + acquireTokenSilent_silentFlow = 61, + logout = 961 } /* diff --git a/lib/msal-browser/src/utils/BrowserProtocolUtils.ts b/lib/msal-browser/src/utils/BrowserProtocolUtils.ts index 9005a95103..c23598d15b 100644 --- a/lib/msal-browser/src/utils/BrowserProtocolUtils.ts +++ b/lib/msal-browser/src/utils/BrowserProtocolUtils.ts @@ -17,7 +17,7 @@ export class BrowserProtocolUtils { * @param browserCrypto * @param state */ - static extractBrowserRequestState(browserCrypto: ICrypto, state: string): BrowserStateObject { + static extractBrowserRequestState(browserCrypto: ICrypto, state: string): BrowserStateObject | null { if (StringUtils.isEmpty(state)) { return null; } diff --git a/lib/msal-browser/src/utils/BrowserUtils.ts b/lib/msal-browser/src/utils/BrowserUtils.ts index e1709656d1..37a0d8b068 100644 --- a/lib/msal-browser/src/utils/BrowserUtils.ts +++ b/lib/msal-browser/src/utils/BrowserUtils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { INetworkModule, Logger, UrlString } from "@azure/msal-common"; +import { Constants, INetworkModule, Logger, UrlString } from "@azure/msal-common"; import { FetchClient } from "../network/FetchClient"; import { XhrClient } from "../network/XhrClient"; import { BrowserAuthError } from "../error/BrowserAuthError"; @@ -44,7 +44,7 @@ export class BrowserUtils { // Office.js sets history.replaceState to null if (typeof history.replaceState === "function") { // Full removes "#" from url - history.replaceState(null, null, `${window.location.pathname}${window.location.search}`); + history.replaceState(null, Constants.EMPTY_STRING, `${window.location.pathname}${window.location.search}`); } else { window.location.hash = ""; } diff --git a/lib/msal-browser/test/app/PublicClientApplication.spec.ts b/lib/msal-browser/test/app/PublicClientApplication.spec.ts index b027e56c3d..e7fad58107 100644 --- a/lib/msal-browser/test/app/PublicClientApplication.spec.ts +++ b/lib/msal-browser/test/app/PublicClientApplication.spec.ts @@ -25,7 +25,6 @@ import { SilentRequest } from "../../src/request/SilentRequest"; import { BrowserCacheManager } from "../../src/cache/BrowserCacheManager"; import { RedirectRequest } from "../../src/request/RedirectRequest"; import pkg from "../../package.json"; -import { ClientApplication } from "../../src/app/ClientApplication"; chai.use(chaiAsPromised); const expect = chai.expect; @@ -84,14 +83,14 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("handleRedirectPromise returns null if interaction is not in progress", async () => { sinon.stub(pca, "interactionInProgress").returns(false); - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, TEST_URIS.TEST_ALTERNATE_REDIR_URI); expect(await pca.handleRedirectPromise()).to.be.null; }); it("navigates and caches hash if navigateToLoginRequestUri is true and interaction type is redirect", (done) => { sinon.stub(pca, "interactionInProgress").returns(true); - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, TEST_URIS.TEST_ALTERNATE_REDIR_URI); sinon.stub(BrowserUtils, "navigateWindow").callsFake((urlNavigate: string, timeout: number, logger: Logger, noHistory?: boolean) => { expect(noHistory).to.be.true; @@ -106,7 +105,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("navigates to root and caches hash if navigateToLoginRequestUri is true", (done) => { sinon.stub(pca, "interactionInProgress").returns(true); - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; sinon.stub(BrowserUtils, "navigateWindow").callsFake((urlNavigate: string, timeout: number, logger: Logger, noHistory?: boolean) => { expect(noHistory).to.be.true; expect(timeout).to.be.greaterThan(0); @@ -121,7 +120,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("navigates to root and caches hash if navigateToLoginRequestUri is true and loginRequestUrl is 'null'", (done) => { sinon.stub(pca, "interactionInProgress").returns(true); - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, "null"); sinon.stub(BrowserUtils, "navigateWindow").callsFake((urlNavigate: string, timeout: number, logger: Logger, noHistory?: boolean) => { expect(noHistory).to.be.true; @@ -138,7 +137,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("navigates and caches hash if navigateToLoginRequestUri is true and loginRequestUrl contains query string", (done) => { sinon.stub(pca, "interactionInProgress").returns(true); const loginRequestUrl = window.location.href + "?testQueryString=1"; - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, loginRequestUrl); sinon.stub(BrowserUtils, "navigateWindow").callsFake((urlNavigate: string, timeout: number, logger: Logger, noHistory?: boolean) => { expect(noHistory).to.be.true; @@ -154,7 +153,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("navigates and caches hash if navigateToLoginRequestUri is true and loginRequestUrl contains query string and hash", (done) => { sinon.stub(pca, "interactionInProgress").returns(true); const loginRequestUrl = window.location.href + "?testQueryString=1#testHash"; - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, loginRequestUrl); sinon.stub(BrowserUtils, "navigateWindow").callsFake((urlNavigate: string, timeout: number, logger: Logger, noHistory?: boolean) => { expect(noHistory).to.be.true; @@ -170,10 +169,10 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("replaces custom hash if navigateToLoginRequestUri is true and loginRequestUrl contains custom hash", () => { sinon.stub(pca, "interactionInProgress").returns(true); const loginRequestUrl = window.location.href + "#testHash"; - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, loginRequestUrl); sinon.stub(PublicClientApplication.prototype, "handleHash").callsFake((responseHash) => { - expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT); }); return pca.handleRedirectPromise() .then(() => { @@ -186,9 +185,9 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { const loginRequestUrl = window.location.href + "#testHash"; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, loginRequestUrl); sinon.stub(PublicClientApplication.prototype, "handleHash").callsFake((responseHash) => { - expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT); }); - return pca.handleRedirectPromise(TEST_HASHES.TEST_SUCCESS_CODE_HASH) + return pca.handleRedirectPromise(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT) .then(() => { expect(window.location.href).to.be.eq(loginRequestUrl); }); @@ -197,10 +196,10 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("processes hash if navigateToLoginRequestUri is true and loginRequestUrl contains trailing slash", (done) => { sinon.stub(pca, "interactionInProgress").returns(true); const loginRequestUrl = window.location.href.endsWith("/") ? window.location.href.slice(0, -1) : window.location.href + "/"; - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, loginRequestUrl); sinon.stub(PublicClientApplication.prototype, "handleHash").callsFake((responseHash) => { - expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT); done(); }); pca.handleRedirectPromise(); @@ -215,11 +214,11 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }); sinon.stub(pca, "interactionInProgress").returns(true); const loginRequestUrl = window.location.href + "#testHash"; - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, loginRequestUrl); sinon.stub(PublicClientApplication.prototype, "handleHash").callsFake((responseHash) => { expect(window.location.href).to.not.contain("#testHash"); - expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + expect(responseHash).to.be.eq(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT); done(); }); pca.handleRedirectPromise(); @@ -410,13 +409,14 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("gets hash from cache and processes response", async () => { const b64Encode = new Base64Encode(); - const stateString = TEST_STATE_VALUES.TEST_STATE; + const stateString = TEST_STATE_VALUES.TEST_STATE_REDIRECT; const browserCrypto = new CryptoOps(); const stateId = ProtocolUtils.parseRequestState(browserCrypto, stateString).libraryState.id; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, TEST_URIS.TEST_REDIR_URI); - window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE); - window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.URL_HASH}`, TEST_HASHES.TEST_SUCCESS_CODE_HASH); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.AUTHORITY}.${stateId}`, TEST_CONFIG.validAuthority); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE_REDIRECT); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.URL_HASH}`, TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`, BrowserConstants.INTERACTION_IN_PROGRESS_VALUE); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.NONCE_IDTOKEN}.${stateId}`, "123523"); const testTokenReq: AuthorizationCodeRequest = { @@ -502,13 +502,14 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { authenticationScheme: TEST_CONFIG.TOKEN_TYPE_BEARER as AuthenticationScheme }; - const stateString = TEST_STATE_VALUES.TEST_STATE; + const stateString = TEST_STATE_VALUES.TEST_STATE_REDIRECT; const browserCrypto = new CryptoOps(); const stateId = ProtocolUtils.parseRequestState(browserCrypto, stateString).libraryState.id; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_PARAMS}`, browserCrypto.base64Encode(JSON.stringify(testAuthCodeRequest))); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, TEST_URIS.TEST_REDIR_URI); - window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.AUTHORITY}.${stateId}`, TEST_CONFIG.validAuthority); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE_REDIRECT); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.URL_HASH}`, TEST_HASHES.TEST_ERROR_HASH); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`, BrowserConstants.INTERACTION_IN_PROGRESS_VALUE); @@ -526,13 +527,14 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("processes hash if navigateToLoginRequestUri is false and request origin is the same", async () => { const b64Encode = new Base64Encode(); - const stateString = TEST_STATE_VALUES.TEST_STATE; + const stateString = TEST_STATE_VALUES.TEST_STATE_REDIRECT; const browserCrypto = new CryptoOps(); const stateId = ProtocolUtils.parseRequestState(browserCrypto, stateString).libraryState.id; - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, TEST_URIS.TEST_REDIR_URI); - window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.AUTHORITY}.${stateId}`, TEST_CONFIG.validAuthority); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE_REDIRECT); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`, BrowserConstants.INTERACTION_IN_PROGRESS_VALUE); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.NONCE_IDTOKEN}.${stateId}`, "123523"); @@ -618,13 +620,14 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("processes hash if navigateToLoginRequestUri is false and request origin is different", async () => { const b64Encode = new Base64Encode(); - const stateString = TEST_STATE_VALUES.TEST_STATE; + const stateString = TEST_STATE_VALUES.TEST_STATE_REDIRECT; const browserCrypto = new CryptoOps(); const stateId = ProtocolUtils.parseRequestState(browserCrypto, stateString).libraryState.id; - window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH; + window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT; window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`, TEST_URIS.TEST_ALTERNATE_REDIR_URI); - window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.AUTHORITY}.${stateId}`, TEST_CONFIG.validAuthority); + window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`, TEST_STATE_VALUES.TEST_STATE_REDIRECT); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`, BrowserConstants.INTERACTION_IN_PROGRESS_VALUE); window.sessionStorage.setItem(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.NONCE_IDTOKEN}.${stateId}`, "123523"); @@ -729,7 +732,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }); it("loginRedirect navigates to created login url", (done) => { - sinon.stub(RedirectHandler.prototype, "initiateAuthRequest").callsFake((navigateUrl, request, timeout): Promise => { + sinon.stub(RedirectHandler.prototype, "initiateAuthRequest").callsFake((navigateUrl): Promise => { expect(navigateUrl).to.be.eq(testNavUrl); return Promise.resolve(done()); }); @@ -754,7 +757,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }); it("loginRedirect navigates to created login url, with empty request", (done) => { - sinon.stub(RedirectHandler.prototype, "initiateAuthRequest").callsFake((navigateUrl, request, timeout): Promise => { + sinon.stub(RedirectHandler.prototype, "initiateAuthRequest").callsFake((navigateUrl): Promise => { expect(navigateUrl.startsWith(testNavUrlNoRequest)).to.be.true; return Promise.resolve(done()); }); @@ -798,9 +801,9 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { const browserCrypto = new CryptoOps(); const browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig, browserCrypto, testLogger); await pca.loginRedirect(emptyRequest); - expect(browserStorage.getTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE))).to.be.deep.eq(TEST_STATE_VALUES.TEST_STATE); - expect(browserStorage.getTemporaryCache(browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE))).to.be.eq(RANDOM_TEST_GUID); - expect(browserStorage.getTemporaryCache(browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE))).to.be.eq(`${Constants.DEFAULT_AUTHORITY}`); + expect(browserStorage.getTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT))).to.be.deep.eq(TEST_STATE_VALUES.TEST_STATE_REDIRECT); + expect(browserStorage.getTemporaryCache(browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT))).to.be.eq(RANDOM_TEST_GUID); + expect(browserStorage.getTemporaryCache(browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT))).to.be.eq(`${Constants.DEFAULT_AUTHORITY}`); }); it("Caches token request correctly", async () => { @@ -924,11 +927,10 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { ...emptyRequest, scopes: [], loginHint: idTokenClaims.upn, - state: TEST_STATE_VALUES.TEST_STATE, + state: TEST_STATE_VALUES.TEST_STATE_REDIRECT, correlationId: RANDOM_TEST_GUID, nonce: RANDOM_TEST_GUID, authority: `${Constants.DEFAULT_AUTHORITY}`, - account: null, responseMode: ResponseMode.FRAGMENT, codeChallenge: TEST_CONFIG.TEST_CHALLENGE, codeChallengeMethod: Constants.S256_CODE_CHALLENGE_METHOD @@ -981,10 +983,9 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { const validatedRequest: AuthorizationUrlRequest = { ...loginRequest, scopes: [], - state: TEST_STATE_VALUES.TEST_STATE, + state: TEST_STATE_VALUES.TEST_STATE_REDIRECT, correlationId: RANDOM_TEST_GUID, authority: `${Constants.DEFAULT_AUTHORITY}`, - account: null, nonce: RANDOM_TEST_GUID, responseMode: ResponseMode.FRAGMENT, codeChallenge: TEST_CONFIG.TEST_CHALLENGE, @@ -1038,7 +1039,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { done(); }; - sinon.stub(RedirectHandler.prototype, "initiateAuthRequest").callsFake((navigateUrl, authCodeREquest, { + sinon.stub(RedirectHandler.prototype, "initiateAuthRequest").callsFake((navigateUrl, { redirectTimeout: timeout, redirectStartPage, onRedirectNavigate: onRedirectNavigateCb }): Promise => { expect(onRedirectNavigateCb).to.be.eq(onRedirectNavigate); @@ -1089,9 +1090,9 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { const testLogger = new Logger(loggerOptions); const browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig, browserCrypto, testLogger); await pca.loginRedirect(emptyRequest); - expect(browserStorage.getTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE))).to.be.deep.eq(TEST_STATE_VALUES.TEST_STATE); - expect(browserStorage.getTemporaryCache(browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE))).to.be.eq(RANDOM_TEST_GUID); - expect(browserStorage.getTemporaryCache(browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE))).to.be.eq(`${Constants.DEFAULT_AUTHORITY}`); + expect(browserStorage.getTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT))).to.be.deep.eq(TEST_STATE_VALUES.TEST_STATE_REDIRECT); + expect(browserStorage.getTemporaryCache(browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT))).to.be.eq(RANDOM_TEST_GUID); + expect(browserStorage.getTemporaryCache(browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT))).to.be.eq(`${Constants.DEFAULT_AUTHORITY}`); }); it("Caches token request correctly", async () => { @@ -1213,10 +1214,9 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { ...emptyRequest, scopes: [...emptyRequest.scopes], loginHint: idTokenClaims.upn, - state: TEST_STATE_VALUES.TEST_STATE, + state: TEST_STATE_VALUES.TEST_STATE_REDIRECT, correlationId: RANDOM_TEST_GUID, authority: `${Constants.DEFAULT_AUTHORITY}`, - account: null, nonce: RANDOM_TEST_GUID, responseMode: ResponseMode.FRAGMENT, codeChallenge: TEST_CONFIG.TEST_CHALLENGE, @@ -1271,10 +1271,9 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { const validatedRequest: AuthorizationUrlRequest = { ...loginRequest, scopes: [...loginRequest.scopes], - state: TEST_STATE_VALUES.TEST_STATE, + state: TEST_STATE_VALUES.TEST_STATE_REDIRECT, correlationId: RANDOM_TEST_GUID, authority: `${Constants.DEFAULT_AUTHORITY}`, - account: null, nonce: RANDOM_TEST_GUID, responseMode: ResponseMode.FRAGMENT, codeChallenge: TEST_CONFIG.TEST_CHALLENGE, @@ -1358,7 +1357,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { expect(requestUrl).to.be.eq(testNavUrl); return window; }); - sinon.stub(PopupHandler.prototype, "monitorPopupForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + sinon.stub(PopupHandler.prototype, "monitorPopupForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH_POPUP); sinon.stub(PopupHandler.prototype, "handleCodeResponse").resolves(testTokenResponse); sinon.stub(CryptoOps.prototype, "generatePkceCodes").resolves({ challenge: TEST_CONFIG.TEST_CHALLENGE, @@ -1530,7 +1529,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { expect(requestUrl).to.be.eq(testNavUrl); return window; }); - sinon.stub(PopupHandler.prototype, "monitorPopupForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + sinon.stub(PopupHandler.prototype, "monitorPopupForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH_POPUP); sinon.stub(PopupHandler.prototype, "handleCodeResponse").resolves(testTokenResponse); sinon.stub(CryptoOps.prototype, "generatePkceCodes").resolves({ challenge: TEST_CONFIG.TEST_CHALLENGE, @@ -1648,7 +1647,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }; sinon.stub(AuthorizationCodeClient.prototype, "getAuthCodeUrl").resolves(testNavUrl); const loadFrameSyncSpy = sinon.spy(SilentHandler.prototype, "loadFrameSync"); - sinon.stub(SilentHandler.prototype, "monitorIframeForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + sinon.stub(SilentHandler.prototype, "monitorIframeForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH_SILENT); sinon.stub(SilentHandler.prototype, "handleCodeResponse").resolves(testTokenResponse); sinon.stub(CryptoOps.prototype, "generatePkceCodes").resolves({ challenge: TEST_CONFIG.TEST_CHALLENGE, @@ -1705,7 +1704,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }; sinon.stub(AuthorizationCodeClient.prototype, "getAuthCodeUrl").resolves(testNavUrl); const loadFrameSyncSpy = sinon.spy(SilentHandler.prototype, "loadFrameSync"); - sinon.stub(SilentHandler.prototype, "monitorIframeForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + sinon.stub(SilentHandler.prototype, "monitorIframeForHash").resolves(TEST_HASHES.TEST_SUCCESS_CODE_HASH_SILENT); sinon.stub(SilentHandler.prototype, "handleCodeResponse").resolves(testTokenResponse); sinon.stub(CryptoOps.prototype, "generatePkceCodes").resolves({ challenge: TEST_CONFIG.TEST_CHALLENGE, @@ -1864,7 +1863,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { verifier: TEST_CONFIG.TEST_VERIFIER }); sinon.stub(CryptoOps.prototype, "createNewGuid").returns(RANDOM_TEST_GUID); - sinon.stub(ProtocolUtils, "setRequestState").returns(TEST_STATE_VALUES.TEST_STATE); + sinon.stub(ProtocolUtils, "setRequestState").returns(TEST_STATE_VALUES.TEST_STATE_SILENT); const silentFlowRequest: SilentRequest = { scopes: ["User.Read"], account: testAccount, @@ -1880,7 +1879,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { authority: `${Constants.DEFAULT_AUTHORITY}`, prompt: "none", redirectUri: TEST_URIS.TEST_REDIR_URI, - state: TEST_STATE_VALUES.TEST_STATE, + state: TEST_STATE_VALUES.TEST_STATE_SILENT, nonce: RANDOM_TEST_GUID, responseMode: ResponseMode.FRAGMENT, codeChallenge: TEST_CONFIG.TEST_CHALLENGE, diff --git a/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts b/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts index a459884e39..71cc0bbda8 100644 --- a/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts +++ b/lib/msal-browser/test/cache/BrowserCacheManager.spec.ts @@ -93,7 +93,7 @@ class TestCacheStorage extends CacheManager { describe("BrowserCacheManager tests", () => { - let cacheConfig: CacheOptions; + let cacheConfig: Required; let logger: Logger; let windowRef: Window & typeof globalThis; const browserCrypto = new CryptoOps(); @@ -471,19 +471,6 @@ describe("BrowserCacheManager tests", () => { expect(document.cookie).to.be.empty; }); - it("clearMsalCookie()", () => { - const stateString = TEST_STATE_VALUES.TEST_STATE; - const stateId = ProtocolUtils.parseRequestState(browserCrypto, stateString).libraryState.id; - const nonceKey = `${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.NONCE_IDTOKEN}.${stateId}`; - const stateKey = `${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`; - const originUriKey = `${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.ORIGIN_URI}`; - browserSessionStorage.setItemCookie(nonceKey, "thisIsANonce"); - browserSessionStorage.setItemCookie(stateKey, stateString); - browserSessionStorage.setItemCookie(originUriKey, "https://contoso.com"); - browserSessionStorage.clearMsalCookie(stateString); - expect(document.cookie).to.be.empty; - }); - it("getCookieExpirationTime()", () => { const COOKIE_LIFE_MULTIPLIER = 24 * 60 * 60 * 1000; const currentTime = new Date().getTime(); @@ -498,35 +485,34 @@ describe("BrowserCacheManager tests", () => { it("generateAuthorityKey() creates a valid cache key for authority strings", () => { const browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig, browserCrypto, logger); - const authorityKey = browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE); + const authorityKey = browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT); expect(authorityKey).to.be.eq(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.AUTHORITY}.${RANDOM_TEST_GUID}`); }); it("generateNonceKey() create a valid cache key for nonce strings", () => { const browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig, browserCrypto, logger); - const nonceKey = browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE); + const nonceKey = browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT); expect(nonceKey).to.be.eq(`${Constants.CACHE_PREFIX}.${TEST_CONFIG.MSAL_CLIENT_ID}.${TemporaryCacheKeys.NONCE_IDTOKEN}.${RANDOM_TEST_GUID}`); }); it("updateCacheEntries() correctly updates the authority, state and nonce in the cache", () => { - const authorityCacheSpy = sinon.spy(BrowserCacheManager.prototype, "setAuthorityCache"); const browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig, browserCrypto, logger); const testNonce = "testNonce"; - const stateString = TEST_STATE_VALUES.TEST_STATE; + const stateString = TEST_STATE_VALUES.TEST_STATE_REDIRECT; ProtocolUtils.parseRequestState(browserCrypto, stateString).libraryState.id; browserStorage.updateCacheEntries(stateString, testNonce, `${Constants.DEFAULT_AUTHORITY}/`); - expect(authorityCacheSpy.calledOnce).to.be.true; + const stateKey = browserStorage.generateStateKey(stateString); const nonceKey = browserStorage.generateNonceKey(stateString); const authorityKey = browserStorage.generateAuthorityKey(stateString); - expect(window.sessionStorage[browserStorage.generateStateKey(stateString)]).to.be.eq(stateString); + expect(window.sessionStorage[`${stateKey}`]).to.be.eq(stateString); expect(window.sessionStorage[`${nonceKey}`]).to.be.eq(testNonce); expect(window.sessionStorage[`${authorityKey}`]).to.be.eq(`${Constants.DEFAULT_AUTHORITY}/`); }); it("resetTempCacheItems() resets all temporary cache items with the given state", () => { - const stateString = TEST_STATE_VALUES.TEST_STATE; + const stateString = TEST_STATE_VALUES.TEST_STATE_REDIRECT; const browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig, browserCrypto, logger); browserStorage.updateCacheEntries(stateString, "nonce", `${TEST_URIS.DEFAULT_INSTANCE}/`); browserStorage.setItem(TemporaryCacheKeys.REQUEST_PARAMS, "TestRequestParams"); @@ -567,7 +553,7 @@ describe("BrowserCacheManager tests", () => { const cryptoObj = new CryptoOps(); // browserStorage.setItem(TemporaryCacheKeys.REQUEST_PARAMS, cryptoObj.base64Encode(JSON.stringify(tokenRequest))); - expect(() => browserStorage.getCachedRequest(RANDOM_TEST_GUID, cryptoObj)).to.throw(BrowserAuthErrorMessage.tokenRequestCacheError.desc); + expect(() => browserStorage.getCachedRequest(RANDOM_TEST_GUID, cryptoObj)).to.throw(BrowserAuthErrorMessage.noTokenRequestCacheError.desc); }); it("Throws error if cached request cannot be parsed correctly", async () => { @@ -588,7 +574,7 @@ describe("BrowserCacheManager tests", () => { }; const stringifiedRequest = JSON.stringify(tokenRequest); browserStorage.setTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, stringifiedRequest.substring(0, stringifiedRequest.length / 2), true); - expect(() => browserStorage.getCachedRequest(RANDOM_TEST_GUID, cryptoObj)).to.throw(BrowserAuthErrorMessage.tokenRequestCacheError.desc); + expect(() => browserStorage.getCachedRequest(RANDOM_TEST_GUID, cryptoObj)).to.throw(BrowserAuthErrorMessage.unableToParseTokenRequestCacheError.desc); }); it("Uses authority from cache if not present in cached request", async () => { @@ -598,7 +584,7 @@ describe("BrowserCacheManager tests", () => { }); const browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig, browserCrypto, logger); // Set up cache - const authorityKey = browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE); + const authorityKey = browserStorage.generateAuthorityKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT); const alternateAuthority = `${TEST_URIS.ALTERNATE_INSTANCE}/common/`; browserStorage.setItem(authorityKey, alternateAuthority); @@ -615,7 +601,7 @@ describe("BrowserCacheManager tests", () => { browserStorage.setTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, stringifiedRequest, true); // Perform test - const tokenRequest = browserStorage.getCachedRequest(TEST_STATE_VALUES.TEST_STATE, browserCrypto); + const tokenRequest = browserStorage.getCachedRequest(TEST_STATE_VALUES.TEST_STATE_REDIRECT, browserCrypto); expect(tokenRequest.authority).to.be.eq(alternateAuthority); }); }); diff --git a/lib/msal-browser/test/error/BrowserAuthError.spec.ts b/lib/msal-browser/test/error/BrowserAuthError.spec.ts index 1411771657..b8edaab339 100644 --- a/lib/msal-browser/test/error/BrowserAuthError.spec.ts +++ b/lib/msal-browser/test/error/BrowserAuthError.spec.ts @@ -259,15 +259,15 @@ describe("BrowserAuthError Unit Tests", () => { expect(err.stack).to.include("BrowserAuthError.spec.ts"); }); - it("createTokenRequestCacheError creates a ClientAuthError object", () => { - const err: BrowserAuthError = BrowserAuthError.createTokenRequestCacheError("Couldn't parse request from cache"); + it("createNoTokenRequestCacheError creates a ClientAuthError object", () => { + const err: BrowserAuthError = BrowserAuthError.createNoTokenRequestCacheError(); expect(err instanceof BrowserAuthError).to.be.true; expect(err instanceof AuthError).to.be.true; expect(err instanceof Error).to.be.true; - expect(err.errorCode).to.equal(BrowserAuthErrorMessage.tokenRequestCacheError.code); - expect(err.errorMessage).to.include(BrowserAuthErrorMessage.tokenRequestCacheError.desc); - expect(err.message).to.include(BrowserAuthErrorMessage.tokenRequestCacheError.desc); + expect(err.errorCode).to.equal(BrowserAuthErrorMessage.noTokenRequestCacheError.code); + expect(err.errorMessage).to.include(BrowserAuthErrorMessage.noTokenRequestCacheError.desc); + expect(err.message).to.include(BrowserAuthErrorMessage.noTokenRequestCacheError.desc); expect(err.name).to.equal("BrowserAuthError"); expect(err.stack).to.include("BrowserAuthError.spec.ts"); }); diff --git a/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts b/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts index d7e6b454cf..0e8b1b5818 100644 --- a/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts @@ -5,6 +5,7 @@ import { expect } from "chai"; import "mocha"; +import chaiAsPromised from "chai-as-promised"; import { InteractionHandler } from "../../src/interaction_handler/InteractionHandler"; import { PkceCodes, @@ -33,7 +34,7 @@ import { BrowserCacheManager } from "../../src/cache/BrowserCacheManager"; class TestInteractionHandler extends InteractionHandler { constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager) { - super(authCodeModule, storageImpl); + super(authCodeModule, storageImpl, testAuthCodeRequest); } showUI(requestUrl: string): Window { @@ -81,6 +82,27 @@ const networkInterface = { }, }; +const cryptoInterface = { + createNewGuid: (): string => { + return "newGuid"; + }, + base64Decode: (input: string): string => { + return "testDecodedString"; + }, + base64Encode: (input: string): string => { + return "testEncodedString"; + }, + generatePkceCodes: async (): Promise => { + return testPkceCodes; + }, + getPublicKeyThumbprint: async (): Promise => { + return TEST_POP_VALUES.ENCODED_REQ_CNF; + }, + signJwt: async (): Promise => { + return "signedJwt"; + } +} + let authorityInstance: Authority; let authConfig: ClientConfiguration; @@ -96,7 +118,7 @@ describe("InteractionHandler.ts Unit Tests", () => { clientId: TEST_CONFIG.MSAL_CLIENT_ID } }; - const configObj = buildConfiguration(appConfig); + const configObj = buildConfiguration(appConfig, true); authorityInstance = AuthorityFactory.createInstance(configObj.auth.authority, networkInterface, ProtocolMode.AAD); authConfig = { authOptions: { @@ -106,27 +128,8 @@ describe("InteractionHandler.ts Unit Tests", () => { systemOptions: { tokenRenewalOffsetSeconds: configObj.system.tokenRenewalOffsetSeconds }, - cryptoInterface: { - createNewGuid: (): string => { - return "newGuid"; - }, - base64Decode: (input: string): string => { - return "testDecodedString"; - }, - base64Encode: (input: string): string => { - return "testEncodedString"; - }, - generatePkceCodes: async (): Promise => { - return testPkceCodes; - }, - getPublicKeyThumbprint: async (): Promise => { - return TEST_POP_VALUES.ENCODED_REQ_CNF; - }, - signJwt: async (): Promise => { - return "signedJwt"; - } - }, - storageInterface: new TestStorageManager(), + cryptoInterface: cryptoInterface, + storageInterface: new TestStorageManager(TEST_CONFIG.MSAL_CLIENT_ID, cryptoInterface), networkInterface: { sendGetRequestAsync: async (url: string, options?: NetworkRequestOptions): Promise => { return testNetworkResult; @@ -160,11 +163,11 @@ describe("InteractionHandler.ts Unit Tests", () => { it("throws error if given location hash is empty", async () => { const interactionHandler = new TestInteractionHandler(authCodeModule, browserStorage); - await expect(interactionHandler.handleCodeResponse("", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); - await expect(interactionHandler.handleCodeResponse("", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); + await expect(interactionHandler.handleCodeResponse("", "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); + await expect(interactionHandler.handleCodeResponse("", "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); - await expect(interactionHandler.handleCodeResponse(null, authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); - await expect(interactionHandler.handleCodeResponse(null, authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); + await expect(interactionHandler.handleCodeResponse(null, "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); + await expect(interactionHandler.handleCodeResponse(null, "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); }); // TODO: Need to improve this test @@ -184,7 +187,7 @@ describe("InteractionHandler.ts Unit Tests", () => { const testCodeResponse: AuthorizationCodePayload = { code: "authcode", nonce: idTokenClaims.nonce, - state: TEST_STATE_VALUES.TEST_STATE, + state: TEST_STATE_VALUES.TEST_STATE_REDIRECT, cloud_instance_host_name: "contoso.com" }; @@ -209,8 +212,8 @@ describe("InteractionHandler.ts Unit Tests", () => { state: "testState", tokenType: AuthenticationScheme.BEARER }; - browserStorage.setTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE), TEST_STATE_VALUES.TEST_STATE); - browserStorage.setTemporaryCache(browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE), idTokenClaims.nonce); + browserStorage.setTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT), TEST_STATE_VALUES.TEST_STATE_REDIRECT); + browserStorage.setTemporaryCache(browserStorage.generateNonceKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT), idTokenClaims.nonce); sinon.stub(AuthorizationCodeClient.prototype, "handleFragmentResponse").returns(testCodeResponse); sinon.stub(Authority.prototype, "isAuthorityAlias").returns(false); const authority = new Authority("https://www.contoso.com/common/", networkInterface, ProtocolMode.AAD); @@ -220,7 +223,7 @@ describe("InteractionHandler.ts Unit Tests", () => { const acquireTokenSpy = sinon.stub(AuthorizationCodeClient.prototype, "acquireToken").resolves(testTokenResponse); const interactionHandler = new TestInteractionHandler(authCodeModule, browserStorage); await interactionHandler.initiateAuthRequest("testNavUrl"); - const tokenResponse = await interactionHandler.handleCodeResponse(TEST_HASHES.TEST_SUCCESS_CODE_HASH, authorityInstance, authConfig.networkInterface); + const tokenResponse = await interactionHandler.handleCodeResponse(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, TEST_STATE_VALUES.TEST_STATE_REDIRECT, authorityInstance, authConfig.networkInterface); expect(updateAuthoritySpy.calledWith(authority)).to.be.true; expect(tokenResponse).to.deep.eq(testTokenResponse); expect(acquireTokenSpy.calledWith(testAuthCodeRequest, testCodeResponse)).to.be.true; diff --git a/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts b/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts index 5d72ddbaeb..25264aecf1 100644 --- a/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts @@ -30,6 +30,16 @@ const testNetworkResult = { const testKeySet = ["testKey1", "testKey2"]; +const defaultTokenRequest: AuthorizationCodeRequest = { + authenticationScheme: AuthenticationScheme.BEARER, + redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, + code: "thisIsATestCode", + scopes: TEST_CONFIG.DEFAULT_SCOPES, + codeVerifier: TEST_CONFIG.TEST_VERIFIER, + authority: `${Constants.DEFAULT_AUTHORITY}/`, + correlationId: RANDOM_TEST_GUID +}; + const networkInterface = { sendGetRequestAsync( url: string, @@ -46,9 +56,8 @@ const networkInterface = { }; describe("PopupHandler.ts Unit Tests", () => { - + let authCodeModule: AuthorizationCodeClient; let browserStorage: BrowserCacheManager; - let popupHandler: PopupHandler; const cryptoOps = new CryptoOps(); beforeEach(() => { const appConfig: Configuration = { @@ -56,7 +65,7 @@ describe("PopupHandler.ts Unit Tests", () => { clientId: TEST_CONFIG.MSAL_CLIENT_ID } }; - const configObj = buildConfiguration(appConfig); + const configObj = buildConfiguration(appConfig, true); const authorityInstance = AuthorityFactory.createInstance(configObj.auth.authority, networkInterface, ProtocolMode.AAD); const authConfig = { authOptions: { @@ -112,10 +121,9 @@ describe("PopupHandler.ts Unit Tests", () => { }, }; authConfig.storageInterface = new TestStorageManager(TEST_CONFIG.MSAL_CLIENT_ID, authConfig.cryptoInterface); - const authCodeModule = new AuthorizationCodeClient(authConfig); + authCodeModule = new AuthorizationCodeClient(authConfig); const logger = new Logger(authConfig.loggerOptions); browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, configObj.cache, cryptoOps, logger); - popupHandler = new PopupHandler(authCodeModule, browserStorage); }); afterEach(() => { @@ -125,6 +133,7 @@ describe("PopupHandler.ts Unit Tests", () => { describe("Constructor", () => { it("creates a valid PopupHandler", () => { + const popupHandler = new PopupHandler(authCodeModule, browserStorage, defaultTokenRequest); expect(popupHandler instanceof PopupHandler).to.be.true; expect(popupHandler instanceof InteractionHandler).to.be.true; }); @@ -142,11 +151,12 @@ describe("PopupHandler.ts Unit Tests", () => { authority: `${Constants.DEFAULT_AUTHORITY}/`, correlationId: RANDOM_TEST_GUID }; - expect(() => popupHandler.initiateAuthRequest("", testTokenReq, {})).to.throw(BrowserAuthErrorMessage.emptyNavigateUriError.desc); - expect(() => popupHandler.initiateAuthRequest("", testTokenReq, {})).to.throw(BrowserAuthError); + const popupHandler = new PopupHandler(authCodeModule, browserStorage, testTokenReq); + expect(() => popupHandler.initiateAuthRequest("", {})).to.throw(BrowserAuthErrorMessage.emptyNavigateUriError.desc); + expect(() => popupHandler.initiateAuthRequest("", {})).to.throw(BrowserAuthError); - expect(() => popupHandler.initiateAuthRequest(null, testTokenReq, {})).to.throw(BrowserAuthErrorMessage.emptyNavigateUriError.desc); - expect(() => popupHandler.initiateAuthRequest(null, testTokenReq, {})).to.throw(BrowserAuthError); + expect(() => popupHandler.initiateAuthRequest(null, {})).to.throw(BrowserAuthErrorMessage.emptyNavigateUriError.desc); + expect(() => popupHandler.initiateAuthRequest(null, {})).to.throw(BrowserAuthError); }); it("opens a popup window", () => { @@ -168,7 +178,8 @@ describe("PopupHandler.ts Unit Tests", () => { return window; }; - popupHandler.initiateAuthRequest(TEST_URIS.ALTERNATE_INSTANCE, testTokenReq, {}); + const popupHandler = new PopupHandler(authCodeModule, browserStorage, testTokenReq); + popupHandler.initiateAuthRequest(TEST_URIS.ALTERNATE_INSTANCE, {}); expect(browserStorage.getTemporaryCache(TemporaryCacheKeys.INTERACTION_STATUS_KEY, true)).to.be.eq(BrowserConstants.INTERACTION_IN_PROGRESS_VALUE); }); }); @@ -183,6 +194,7 @@ describe("PopupHandler.ts Unit Tests", () => { close: () => {} }; + const popupHandler = new PopupHandler(authCodeModule, browserStorage, defaultTokenRequest); // @ts-ignore popupHandler.monitorPopupForHash(popup, 500) .catch(() => { @@ -199,6 +211,7 @@ describe("PopupHandler.ts Unit Tests", () => { close: () => {} }; + const popupHandler = new PopupHandler(authCodeModule, browserStorage, defaultTokenRequest); // @ts-ignore popupHandler.monitorPopupForHash(popup, 1000) .then((hash: string) => { @@ -224,6 +237,7 @@ describe("PopupHandler.ts Unit Tests", () => { closed: false }; + const popupHandler = new PopupHandler(authCodeModule, browserStorage, defaultTokenRequest); // @ts-ignore popupHandler.monitorPopupForHash(popup, 1000) .catch((error) => { @@ -263,8 +277,8 @@ describe("PopupHandler.ts Unit Tests", () => { authenticationScheme: AuthenticationScheme.BEARER }; - - const popupWindow = popupHandler.initiateAuthRequest("http://localhost/#/code=hello", testRequest, { + const popupHandler = new PopupHandler(authCodeModule, browserStorage, testRequest); + const popupWindow = popupHandler.initiateAuthRequest("http://localhost/#/code=hello", { // @ts-ignore popup: windowObject }); @@ -287,7 +301,8 @@ describe("PopupHandler.ts Unit Tests", () => { correlationId: RANDOM_TEST_GUID }; - const popupWindow = popupHandler.initiateAuthRequest("http://localhost/#/code=hello", testRequest, {}); + const popupHandler = new PopupHandler(authCodeModule, browserStorage, testRequest); + const popupWindow = popupHandler.initiateAuthRequest("http://localhost/#/code=hello", {}); expect(popupWindow).to.equal(window); }); @@ -305,7 +320,8 @@ describe("PopupHandler.ts Unit Tests", () => { authenticationScheme: AuthenticationScheme.BEARER }; - expect(() => popupHandler.initiateAuthRequest("http://localhost/#/code=hello", testRequest, {})).to.throw(BrowserAuthErrorMessage.emptyWindowError.desc); + const popupHandler = new PopupHandler(authCodeModule, browserStorage, testRequest); + expect(() => popupHandler.initiateAuthRequest("http://localhost/#/code=hello", {})).to.throw(BrowserAuthErrorMessage.emptyWindowError.desc); }); it("throws error if popup passed in is null", () => { @@ -319,10 +335,11 @@ describe("PopupHandler.ts Unit Tests", () => { authenticationScheme: AuthenticationScheme.BEARER }; - expect(() => popupHandler.initiateAuthRequest("http://localhost/#/code=hello", testRequest, { + const popupHandler = new PopupHandler(authCodeModule, browserStorage, testRequest); + expect(() => popupHandler.initiateAuthRequest("http://localhost/#/code=hello", { popup: null })).to.throw(BrowserAuthErrorMessage.emptyWindowError.desc); - expect(() => popupHandler.initiateAuthRequest("http://localhost/#/code=hello", testRequest, { + expect(() => popupHandler.initiateAuthRequest("http://localhost/#/code=hello", { popup: null })).to.throw(BrowserAuthError); }); diff --git a/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts b/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts index 4dcec51f78..f36267c80c 100644 --- a/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts @@ -26,6 +26,16 @@ const testPkceCodes = { verifier: "TestVerifier" } as PkceCodes; +const defaultTokenRequest: AuthorizationCodeRequest = { + authenticationScheme: AuthenticationScheme.BEARER, + redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, + code: "thisIsATestCode", + scopes: TEST_CONFIG.DEFAULT_SCOPES, + codeVerifier: TEST_CONFIG.TEST_VERIFIER, + authority: `${Constants.DEFAULT_AUTHORITY}/`, + correlationId: RANDOM_TEST_GUID +}; + const testNetworkResult = { testParam: "testValue" }; @@ -51,16 +61,15 @@ let authorityInstance: Authority; let authConfig: ClientConfiguration; describe("RedirectHandler.ts Unit Tests", () => { - + let authCodeModule: AuthorizationCodeClient; let browserStorage: BrowserCacheManager; - let redirectHandler: RedirectHandler; beforeEach(() => { const appConfig: Configuration = { auth: { clientId: TEST_CONFIG.MSAL_CLIENT_ID } }; - const configObj = buildConfiguration(appConfig); + const configObj = buildConfiguration(appConfig, true); authorityInstance = AuthorityFactory.createInstance(configObj.auth.authority, networkInterface, ProtocolMode.AAD); const browserCrypto = new CryptoOps(); const loggerConfig = { @@ -119,9 +128,7 @@ describe("RedirectHandler.ts Unit Tests", () => { }, loggerOptions: loggerConfig, }; - const authCodeModule = new AuthorizationCodeClient(authConfig); - - redirectHandler = new RedirectHandler(authCodeModule, browserStorage, browserCrypto); + authCodeModule = new AuthorizationCodeClient(authConfig); }); afterEach(() => { @@ -131,6 +138,7 @@ describe("RedirectHandler.ts Unit Tests", () => { describe("Constructor", () => { it("creates a subclass of InteractionHandler called RedirectHandler", () => { + const redirectHandler = new RedirectHandler(authCodeModule, browserStorage, defaultTokenRequest, browserCrypto); expect(redirectHandler instanceof RedirectHandler).to.be.true; }); }); @@ -138,29 +146,21 @@ describe("RedirectHandler.ts Unit Tests", () => { describe("initiateAuthRequest()", () => { it("throws error if requestUrl is empty", () => { - const testTokenReq: AuthorizationCodeRequest = { - authenticationScheme: AuthenticationScheme.BEARER, - redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, - code: "thisIsATestCode", - scopes: TEST_CONFIG.DEFAULT_SCOPES, - codeVerifier: TEST_CONFIG.TEST_VERIFIER, - authority: `${Constants.DEFAULT_AUTHORITY}/`, - correlationId: RANDOM_TEST_GUID - }; - expect(() => redirectHandler.initiateAuthRequest("", testTokenReq, { + const redirectHandler = new RedirectHandler(authCodeModule, browserStorage, defaultTokenRequest, browserCrypto); + expect(() => redirectHandler.initiateAuthRequest("", { redirectTimeout: 3000, redirectStartPage: "" })).to.throw(BrowserAuthErrorMessage.emptyNavigateUriError.desc); - expect(() => redirectHandler.initiateAuthRequest("", testTokenReq, { + expect(() => redirectHandler.initiateAuthRequest("", { redirectTimeout: 3000, redirectStartPage: "" })).to.throw(BrowserAuthError); - expect(() => redirectHandler.initiateAuthRequest(null, testTokenReq, { + expect(() => redirectHandler.initiateAuthRequest(null, { redirectTimeout: 3000, redirectStartPage: "" })).to.throw(BrowserAuthErrorMessage.emptyNavigateUriError.desc); - expect(() => redirectHandler.initiateAuthRequest(null, testTokenReq, { + expect(() => redirectHandler.initiateAuthRequest(null, { redirectTimeout: 3000, redirectStartPage: "" })).to.throw(BrowserAuthError); @@ -171,15 +171,6 @@ describe("RedirectHandler.ts Unit Tests", () => { sinon.stub(DatabaseStorage.prototype, "open").callsFake(async (): Promise => { dbStorage = {}; }); - const testTokenReq: AuthorizationCodeRequest = { - authenticationScheme: AuthenticationScheme.BEARER, - redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, - code: "thisIsATestCode", - scopes: TEST_CONFIG.DEFAULT_SCOPES, - codeVerifier: TEST_CONFIG.TEST_VERIFIER, - authority: `${Constants.DEFAULT_AUTHORITY}/`, - correlationId: RANDOM_TEST_GUID - }; sinon.stub(BrowserUtils, "navigateWindow").callsFake((requestUrl, timeout, logger) => { expect(requestUrl).to.be.eq(TEST_URIS.TEST_ALTERNATE_REDIR_URI); expect(timeout).to.be.eq(3000); @@ -187,7 +178,8 @@ describe("RedirectHandler.ts Unit Tests", () => { expect(browserStorage.getTemporaryCache(TemporaryCacheKeys.INTERACTION_STATUS_KEY, true)).to.be.eq(BrowserConstants.INTERACTION_IN_PROGRESS_VALUE); return Promise.resolve(done()); }); - redirectHandler.initiateAuthRequest(TEST_URIS.TEST_ALTERNATE_REDIR_URI, testTokenReq, { + const redirectHandler = new RedirectHandler(authCodeModule, browserStorage, defaultTokenRequest, browserCrypto); + redirectHandler.initiateAuthRequest(TEST_URIS.TEST_ALTERNATE_REDIR_URI, { redirectStartPage: "", redirectTimeout: 3000 }); @@ -198,14 +190,6 @@ describe("RedirectHandler.ts Unit Tests", () => { sinon.stub(DatabaseStorage.prototype, "open").callsFake(async (): Promise => { dbStorage = {}; }); - const testTokenReq: AuthorizationCodeRequest = { - redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, - code: "thisIsATestCode", - scopes: TEST_CONFIG.DEFAULT_SCOPES, - codeVerifier: TEST_CONFIG.TEST_VERIFIER, - authority: `${Constants.DEFAULT_AUTHORITY}/`, - correlationId: RANDOM_TEST_GUID - }; sinon.stub(BrowserUtils, "navigateWindow").callsFake((requestUrl, timeout, logger) => { done("Navigatation should not happen if onRedirectNavigate returns false"); return Promise.reject(); @@ -216,7 +200,8 @@ describe("RedirectHandler.ts Unit Tests", () => { done(); return false; } - redirectHandler.initiateAuthRequest(TEST_URIS.TEST_ALTERNATE_REDIR_URI, testTokenReq, { + const redirectHandler = new RedirectHandler(authCodeModule, browserStorage, defaultTokenRequest, browserCrypto); + redirectHandler.initiateAuthRequest(TEST_URIS.TEST_ALTERNATE_REDIR_URI, { redirectTimeout: 300, redirectStartPage: "", onRedirectNavigate, @@ -228,14 +213,6 @@ describe("RedirectHandler.ts Unit Tests", () => { sinon.stub(DatabaseStorage.prototype, "open").callsFake(async (): Promise => { dbStorage = {}; }); - const testTokenReq: AuthorizationCodeRequest = { - redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, - code: "thisIsATestCode", - scopes: TEST_CONFIG.DEFAULT_SCOPES, - codeVerifier: TEST_CONFIG.TEST_VERIFIER, - authority: `${Constants.DEFAULT_AUTHORITY}/`, - correlationId: RANDOM_TEST_GUID - }; sinon.stub(BrowserUtils, "navigateWindow").callsFake((requestUrl, timeout, logger) => { expect(requestUrl).to.equal(TEST_URIS.TEST_ALTERNATE_REDIR_URI); done(); @@ -245,7 +222,8 @@ describe("RedirectHandler.ts Unit Tests", () => { const onRedirectNavigate = url => { expect(url).to.equal(TEST_URIS.TEST_ALTERNATE_REDIR_URI); } - redirectHandler.initiateAuthRequest(TEST_URIS.TEST_ALTERNATE_REDIR_URI, testTokenReq, { + const redirectHandler = new RedirectHandler(authCodeModule, browserStorage, defaultTokenRequest, browserCrypto); + redirectHandler.initiateAuthRequest(TEST_URIS.TEST_ALTERNATE_REDIR_URI, { redirectTimeout: 3000, redirectStartPage: "", onRedirectNavigate @@ -256,11 +234,12 @@ describe("RedirectHandler.ts Unit Tests", () => { describe("handleCodeResponse()", () => { it("throws error if given hash is empty", async () => { - await expect(redirectHandler.handleCodeResponse("", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); - await expect(redirectHandler.handleCodeResponse("", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); + const redirectHandler = new RedirectHandler(authCodeModule, browserStorage, defaultTokenRequest, browserCrypto); + await expect(redirectHandler.handleCodeResponse("", "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); + await expect(redirectHandler.handleCodeResponse("", "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); - await expect(redirectHandler.handleCodeResponse(null, authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); - await expect(redirectHandler.handleCodeResponse(null, authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); + await expect(redirectHandler.handleCodeResponse(null, "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthErrorMessage.hashEmptyError.desc); + await expect(redirectHandler.handleCodeResponse(null, "", authorityInstance, authConfig.networkInterface)).to.be.rejectedWith(BrowserAuthError); }); it("successfully handles response", async () => { @@ -279,7 +258,7 @@ describe("RedirectHandler.ts Unit Tests", () => { const testCodeResponse: AuthorizationCodePayload = { code: "authcode", nonce: idTokenClaims.nonce, - state: TEST_STATE_VALUES.TEST_STATE + state: TEST_STATE_VALUES.TEST_STATE_REDIRECT }; const testAccount: AccountInfo = { @@ -315,14 +294,15 @@ describe("RedirectHandler.ts Unit Tests", () => { authority: authorityInstance.canonicalAuthority, correlationId: RANDOM_TEST_GUID }; - browserStorage.setTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE), TEST_STATE_VALUES.TEST_STATE); + browserStorage.setTemporaryCache(browserStorage.generateStateKey(TEST_STATE_VALUES.TEST_STATE_REDIRECT), TEST_STATE_VALUES.TEST_STATE_REDIRECT); browserStorage.setTemporaryCache(browserStorage.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS), browserCrypto.base64Encode(JSON.stringify(testAuthCodeRequest))); browserStorage.setTemporaryCache(browserStorage.generateCacheKey(TemporaryCacheKeys.INTERACTION_STATUS_KEY), BrowserConstants.INTERACTION_IN_PROGRESS_VALUE); - browserStorage.setTemporaryCache(browserStorage.generateCacheKey(TemporaryCacheKeys.URL_HASH), TEST_HASHES.TEST_SUCCESS_CODE_HASH); + browserStorage.setTemporaryCache(browserStorage.generateCacheKey(TemporaryCacheKeys.URL_HASH), TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT); sinon.stub(AuthorizationCodeClient.prototype, "handleFragmentResponse").returns(testCodeResponse); sinon.stub(AuthorizationCodeClient.prototype, "acquireToken").resolves(testTokenResponse); - const tokenResponse = await redirectHandler.handleCodeResponse(TEST_HASHES.TEST_SUCCESS_CODE_HASH, authorityInstance, authConfig.networkInterface); + const redirectHandler = new RedirectHandler(authCodeModule, browserStorage, testAuthCodeRequest, browserCrypto); + const tokenResponse = await redirectHandler.handleCodeResponse(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, TEST_STATE_VALUES.TEST_STATE_REDIRECT, authorityInstance, authConfig.networkInterface); expect(tokenResponse).to.deep.eq(testTokenResponse); expect(browserStorage.getTemporaryCache(browserStorage.generateCacheKey(TemporaryCacheKeys.INTERACTION_STATUS_KEY))).to.be.null; expect(browserStorage.getTemporaryCache(browserStorage.generateCacheKey(TemporaryCacheKeys.URL_HASH))).to.be.null; diff --git a/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts b/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts index 258bbe7502..7207c985f2 100644 --- a/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts @@ -31,6 +31,16 @@ const testNetworkResult = { testParam: "testValue" }; +const defaultTokenRequest: AuthorizationCodeRequest = { + redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, + code: "thisIsATestCode", + scopes: TEST_CONFIG.DEFAULT_SCOPES, + codeVerifier: TEST_CONFIG.TEST_VERIFIER, + authority: `${Constants.DEFAULT_AUTHORITY}/`, + correlationId: RANDOM_TEST_GUID, + authenticationScheme: AuthenticationScheme.BEARER +}; + const testKeySet = ["testKey1", "testKey2"]; const networkInterface = { @@ -49,9 +59,7 @@ const networkInterface = { }; describe("SilentHandler.ts Unit Tests", () => { - let browserStorage: BrowserCacheManager; - let silentHandler: SilentHandler; let authCodeModule: AuthorizationCodeClient; beforeEach(() => { const appConfig: Configuration = { @@ -59,7 +67,7 @@ describe("SilentHandler.ts Unit Tests", () => { clientId: TEST_CONFIG.MSAL_CLIENT_ID } }; - const configObj = buildConfiguration(appConfig); + const configObj = buildConfiguration(appConfig, true); const authorityInstance = AuthorityFactory.createInstance(configObj.auth.authority, networkInterface, ProtocolMode.AAD); const authConfig = { authOptions: { @@ -119,7 +127,6 @@ describe("SilentHandler.ts Unit Tests", () => { const browserCrypto = new CryptoOps(); const logger = new Logger(authConfig.loggerOptions); browserStorage = new BrowserCacheManager(TEST_CONFIG.MSAL_CLIENT_ID, configObj.cache, browserCrypto, logger); - silentHandler = new SilentHandler(authCodeModule, browserStorage, DEFAULT_IFRAME_TIMEOUT_MS); }); afterEach(() => { @@ -129,6 +136,7 @@ describe("SilentHandler.ts Unit Tests", () => { describe("Constructor", () => { it("creates a subclass of InteractionHandler called SilentHandler", () => { + const silentHandler = new SilentHandler(authCodeModule, browserStorage, defaultTokenRequest, DEFAULT_IFRAME_TIMEOUT_MS); expect(silentHandler instanceof SilentHandler).to.be.true; expect(silentHandler instanceof InteractionHandler).to.be.true; }); @@ -137,53 +145,28 @@ describe("SilentHandler.ts Unit Tests", () => { describe("initiateAuthRequest()", () => { it("throws error if requestUrl is empty", async () => { - const testTokenReq: AuthorizationCodeRequest = { - redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, - code: "thisIsATestCode", - scopes: TEST_CONFIG.DEFAULT_SCOPES, - codeVerifier: TEST_CONFIG.TEST_VERIFIER, - authority: `${Constants.DEFAULT_AUTHORITY}/`, - correlationId: RANDOM_TEST_GUID, - authenticationScheme: AuthenticationScheme.BEARER - }; - await expect(silentHandler.initiateAuthRequest("", testTokenReq)).to.be.rejectedWith(BrowserAuthErrorMessage.emptyNavigateUriError.desc); - await expect(silentHandler.initiateAuthRequest("", testTokenReq)).to.be.rejectedWith(BrowserAuthError); + const silentHandler = new SilentHandler(authCodeModule, browserStorage, defaultTokenRequest, DEFAULT_IFRAME_TIMEOUT_MS); + await expect(silentHandler.initiateAuthRequest("")).to.be.rejectedWith(BrowserAuthErrorMessage.emptyNavigateUriError.desc); + await expect(silentHandler.initiateAuthRequest("")).to.be.rejectedWith(BrowserAuthError); - await expect(silentHandler.initiateAuthRequest(null, testTokenReq)).to.be.rejectedWith(BrowserAuthErrorMessage.emptyNavigateUriError.desc); - await expect(silentHandler.initiateAuthRequest(null, testTokenReq)).to.be.rejectedWith(BrowserAuthError); + await expect(silentHandler.initiateAuthRequest(null)).to.be.rejectedWith(BrowserAuthErrorMessage.emptyNavigateUriError.desc); + await expect(silentHandler.initiateAuthRequest(null)).to.be.rejectedWith(BrowserAuthError); }); it("Creates a frame asynchronously when created with default timeout", async () => { - const testTokenReq: AuthorizationCodeRequest = { - redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, - code: "thisIsATestCode", - scopes: TEST_CONFIG.DEFAULT_SCOPES, - codeVerifier: TEST_CONFIG.TEST_VERIFIER, - authority: `${Constants.DEFAULT_AUTHORITY}/`, - correlationId: RANDOM_TEST_GUID, - authenticationScheme: AuthenticationScheme.BEARER - }; + const silentHandler = new SilentHandler(authCodeModule, browserStorage, defaultTokenRequest, DEFAULT_IFRAME_TIMEOUT_MS); const loadFrameSyncSpy = sinon.spy(silentHandler, "loadFrameSync"); const loadFrameSpy = sinon.spy(silentHandler, "loadFrame"); - const authFrame = await silentHandler.initiateAuthRequest(testNavUrl, testTokenReq); + const authFrame = await silentHandler.initiateAuthRequest(testNavUrl); expect(loadFrameSpy.called).to.be.true; expect(authFrame instanceof HTMLIFrameElement).to.be.true; }).timeout(DEFAULT_IFRAME_TIMEOUT_MS + 1000); it("Creates a frame synchronously when created with a timeout of 0", async () => { - const testTokenReq: AuthorizationCodeRequest = { - redirectUri: `${TEST_URIS.DEFAULT_INSTANCE}/`, - code: "thisIsATestCode", - scopes: TEST_CONFIG.DEFAULT_SCOPES, - codeVerifier: TEST_CONFIG.TEST_VERIFIER, - authority: `${Constants.DEFAULT_AUTHORITY}/`, - correlationId: RANDOM_TEST_GUID, - authenticationScheme: AuthenticationScheme.BEARER - }; - silentHandler = new SilentHandler(authCodeModule, browserStorage, 0); + const silentHandler = new SilentHandler(authCodeModule, browserStorage, defaultTokenRequest, 0); const loadFrameSyncSpy = sinon.spy(silentHandler, "loadFrameSync"); const loadFrameSpy = sinon.spy(silentHandler, "loadFrame"); - const authFrame = await silentHandler.initiateAuthRequest(testNavUrl, testTokenReq); + const authFrame = await silentHandler.initiateAuthRequest(testNavUrl); expect(loadFrameSyncSpy.calledOnce).to.be.true; expect(loadFrameSpy.called).to.be.false; expect(authFrame instanceof HTMLIFrameElement).to.be.true; @@ -199,6 +182,7 @@ describe("SilentHandler.ts Unit Tests", () => { } }; + const silentHandler = new SilentHandler(authCodeModule, browserStorage, defaultTokenRequest, DEFAULT_IFRAME_TIMEOUT_MS); // @ts-ignore silentHandler.monitorIframeForHash(iframe, 500) .catch(() => { @@ -218,6 +202,7 @@ describe("SilentHandler.ts Unit Tests", () => { } }; + const silentHandler = new SilentHandler(authCodeModule, browserStorage, defaultTokenRequest, DEFAULT_IFRAME_TIMEOUT_MS); // @ts-ignore silentHandler.monitorIframeForHash(iframe, 2000) .catch(() => { @@ -253,6 +238,7 @@ describe("SilentHandler.ts Unit Tests", () => { } }; + const silentHandler = new SilentHandler(authCodeModule, browserStorage, defaultTokenRequest, DEFAULT_IFRAME_TIMEOUT_MS); // @ts-ignore silentHandler.monitorIframeForHash(iframe, 1000) .then((hash: string) => { diff --git a/lib/msal-browser/test/utils/BrowserProtocolUtils.spec.ts b/lib/msal-browser/test/utils/BrowserProtocolUtils.spec.ts index 3bdc8160c8..9d60866256 100644 --- a/lib/msal-browser/test/utils/BrowserProtocolUtils.spec.ts +++ b/lib/msal-browser/test/utils/BrowserProtocolUtils.spec.ts @@ -9,8 +9,8 @@ import { TEST_HASHES } from "./StringConstants"; describe("BrowserProtocolUtils.ts Unit Tests", () => { - const browserRedirectRequestState: BrowserStateObject = { interactionType: InteractionType.REDIRECT }; - const browserPopupRequestState: BrowserStateObject = { interactionType: InteractionType.POPUP }; + const browserRedirectRequestState: BrowserStateObject = { interactionType: InteractionType.Redirect }; + const browserPopupRequestState: BrowserStateObject = { interactionType: InteractionType.Popup }; let cryptoInterface: CryptoOps; let dbStorage = {}; @@ -37,14 +37,14 @@ describe("BrowserProtocolUtils.ts Unit Tests", () => { const redirectState = ProtocolUtils.setRequestState(cryptoInterface, null, browserRedirectRequestState); const popupState = ProtocolUtils.setRequestState(cryptoInterface, null, browserPopupRequestState); const redirectPlatformState = BrowserProtocolUtils.extractBrowserRequestState(cryptoInterface, redirectState); - expect(redirectPlatformState.interactionType).to.be.eq(InteractionType.REDIRECT); + expect(redirectPlatformState.interactionType).to.be.eq(InteractionType.Redirect); const popupPlatformState = BrowserProtocolUtils.extractBrowserRequestState(cryptoInterface, popupState); - expect(popupPlatformState.interactionType).to.be.eq(InteractionType.POPUP); + expect(popupPlatformState.interactionType).to.be.eq(InteractionType.Popup); }); describe("parseServerResponseFromHash", () => { it("parses code from hash", () => { - const serverParams = BrowserProtocolUtils.parseServerResponseFromHash(TEST_HASHES.TEST_SUCCESS_CODE_HASH); + const serverParams = BrowserProtocolUtils.parseServerResponseFromHash(TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT); expect(serverParams).to.deep.equal({ "client_info": "eyJ1aWQiOiIxMjMtdGVzdC11aWQiLCJ1dGlkIjoiNDU2LXRlc3QtdXRpZCJ9", diff --git a/lib/msal-browser/test/utils/StringConstants.ts b/lib/msal-browser/test/utils/StringConstants.ts index 1b77211ddc..5a69d78d95 100644 --- a/lib/msal-browser/test/utils/StringConstants.ts +++ b/lib/msal-browser/test/utils/StringConstants.ts @@ -95,21 +95,25 @@ export const TEST_STATE_VALUES = { TEST_TIMESTAMP: 1592846482, DECODED_LIB_STATE: `{"id":"${RANDOM_TEST_GUID}","ts":1592846482}`, ENCODED_LIB_STATE: "eyJpZCI6IjExNTUzYTliLTcxMTYtNDhiMS05ZDQ4LWY2ZDRhOGZmODM3MSIsInRzIjoxNTkyODQ2NDgyfQ==", - TEST_STATE: `eyJpZCI6IjExNTUzYTliLTcxMTYtNDhiMS05ZDQ4LWY2ZDRhOGZmODM3MSIsInRzIjoxNTkyODQ2NDgyLCJtZXRhIjp7ImludGVyYWN0aW9uVHlwZSI6InJlZGlyZWN0In19${Constants.RESOURCE_DELIM}userState` + TEST_STATE_REDIRECT: `eyJpZCI6IjExNTUzYTliLTcxMTYtNDhiMS05ZDQ4LWY2ZDRhOGZmODM3MSIsInRzIjoxNTkyODQ2NDgyLCJtZXRhIjp7ImludGVyYWN0aW9uVHlwZSI6InJlZGlyZWN0In19${Constants.RESOURCE_DELIM}userState`, + TEST_STATE_POPUP: `eyJpZCI6IjExNTUzYTliLTcxMTYtNDhiMS05ZDQ4LWY2ZDRhOGZmODM3MSIsInRzIjoxNTkyODQ2NDgyLCJtZXRhIjp7ImludGVyYWN0aW9uVHlwZSI6InBvcHVwIn19${Constants.RESOURCE_DELIM}userState`, + TEST_STATE_SILENT: `eyJpZCI6IjExNTUzYTliLTcxMTYtNDhiMS05ZDQ4LWY2ZDRhOGZmODM3MSIsInRzIjoxNTkyODQ2NDgyLCJtZXRhIjp7ImludGVyYWN0aW9uVHlwZSI6InNpbGVudCJ9fQ==${Constants.RESOURCE_DELIM}userState` }; // Test Hashes export const TEST_HASHES = { - TEST_SUCCESS_ID_TOKEN_HASH: `#id_token=${TEST_TOKENS.IDTOKEN_V2}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_SUCCESS_ACCESS_TOKEN_HASH: `#access_token=${TEST_TOKENS.ACCESS_TOKEN}&id_token=${TEST_TOKENS.IDTOKEN_V2}&scope=test&expiresIn=${TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_SUCCESS_CODE_HASH: `#code=thisIsATestCode&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_ERROR_HASH: `#error=error_code&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_INTERACTION_REQ_ERROR_HASH1: `#error=interaction_required&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_INTERACTION_REQ_ERROR_HASH2: `#error=interaction_required&error_description=msal+error+description+interaction_required&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_LOGIN_REQ_ERROR_HASH1: `#error=login_required&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_LOGIN_REQ_ERROR_HASH2: `#error=login_required&error_description=msal+error+description+login_required&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_CONSENT_REQ_ERROR_HASH1: `#error=consent_required&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE}`, - TEST_CONSENT_REQ_ERROR_HASH2: `#error=consent_required&error_description=msal+error+description+consent_required&state=${TEST_STATE_VALUES.TEST_STATE}` + TEST_SUCCESS_ID_TOKEN_HASH: `#id_token=${TEST_TOKENS.IDTOKEN_V2}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_SUCCESS_ACCESS_TOKEN_HASH: `#access_token=${TEST_TOKENS.ACCESS_TOKEN}&id_token=${TEST_TOKENS.IDTOKEN_V2}&scope=test&expiresIn=${TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN}&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_SUCCESS_CODE_HASH_REDIRECT: `#code=thisIsATestCode&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_SUCCESS_CODE_HASH_POPUP: `#code=thisIsATestCode&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE_POPUP}`, + TEST_SUCCESS_CODE_HASH_SILENT: `#code=thisIsATestCode&client_info=${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}&state=${TEST_STATE_VALUES.TEST_STATE_SILENT}`, + TEST_ERROR_HASH: `#error=error_code&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_INTERACTION_REQ_ERROR_HASH1: `#error=interaction_required&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_INTERACTION_REQ_ERROR_HASH2: `#error=interaction_required&error_description=msal+error+description+interaction_required&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_LOGIN_REQ_ERROR_HASH1: `#error=login_required&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_LOGIN_REQ_ERROR_HASH2: `#error=login_required&error_description=msal+error+description+login_required&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_CONSENT_REQ_ERROR_HASH1: `#error=consent_required&error_description=msal+error+description&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + TEST_CONSENT_REQ_ERROR_HASH2: `#error=consent_required&error_description=msal+error+description+consent_required&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}` }; export const DEFAULT_OPENID_CONFIG_RESPONSE = { @@ -184,7 +188,7 @@ export const ALTERNATE_OPENID_CONFIG_RESPONSE = { } }; -export const testNavUrl = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${encodeURIComponent(`${TEST_CONFIG.MSAL_CLIENT_ID}`)}&scope=user.read%20openid%20profile&redirect_uri=https%3A%2F%2Flocalhost%3A8081%2Findex.html&client-request-id=${encodeURIComponent(`${RANDOM_TEST_GUID}`)}&response_mode=fragment&response_type=code&x-client-SKU=msal.js.browser&x-client-VER=${version}&x-client-OS=&x-client-CPU=&client_info=1&code_challenge=JsjesZmxJwehdhNY9kvyr0QOeSMEvryY_EHZo3BKrqg&code_challenge_method=S256&nonce=${encodeURIComponent(`${RANDOM_TEST_GUID}`)}&state=${encodeURIComponent(`${TEST_STATE_VALUES.TEST_STATE}`)}`; +export const testNavUrl = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${encodeURIComponent(`${TEST_CONFIG.MSAL_CLIENT_ID}`)}&scope=user.read%20openid%20profile&redirect_uri=https%3A%2F%2Flocalhost%3A8081%2Findex.html&client-request-id=${encodeURIComponent(`${RANDOM_TEST_GUID}`)}&response_mode=fragment&response_type=code&x-client-SKU=msal.js.browser&x-client-VER=${version}&x-client-OS=&x-client-CPU=&client_info=1&code_challenge=JsjesZmxJwehdhNY9kvyr0QOeSMEvryY_EHZo3BKrqg&code_challenge_method=S256&nonce=${encodeURIComponent(`${RANDOM_TEST_GUID}`)}&state=${encodeURIComponent(`${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`)}`; export const testNavUrlNoRequest = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${encodeURIComponent(`${TEST_CONFIG.MSAL_CLIENT_ID}`)}&scope=openid%20profile&redirect_uri=https%3A%2F%2Flocalhost%3A8081%2Findex.html&client-request-id=${encodeURIComponent(`${RANDOM_TEST_GUID}`)}&response_mode=fragment&response_type=code&x-client-SKU=msal.js.browser&x-client-VER=${version}&x-client-OS=&x-client-CPU=&client_info=1&code_challenge=JsjesZmxJwehdhNY9kvyr0QOeSMEvryY_EHZo3BKrqg&code_challenge_method=S256&nonce=${encodeURIComponent(`${RANDOM_TEST_GUID}`)}&state=`; diff --git a/lib/msal-browser/tsconfig.base.json b/lib/msal-browser/tsconfig.base.json index e90e9a5fd9..876f34e14d 100644 --- a/lib/msal-browser/tsconfig.base.json +++ b/lib/msal-browser/tsconfig.base.json @@ -6,7 +6,8 @@ "experimentalDecorators": true, "importHelpers": true, "noImplicitAny": true, - "downlevelIteration": true + "downlevelIteration": true, + "strict": true }, "compileOnSave": false, "buildOnSave": false diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index e56768ec7a..76261cd693 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -54,7 +54,7 @@ export class AuthorizationCodeClient extends BaseClient { * authorization_code_grant * @param request */ - async acquireToken(request: AuthorizationCodeRequest, authCodePayload?: AuthorizationCodePayload): Promise { + async acquireToken(request: AuthorizationCodeRequest, authCodePayload?: AuthorizationCodePayload): Promise { this.logger.info("in acquireToken call"); if (!request || StringUtils.isEmpty(request.code)) { throw ClientAuthError.createTokenRequestCannotBeMadeError(); diff --git a/lib/msal-common/src/client/RefreshTokenClient.ts b/lib/msal-common/src/client/RefreshTokenClient.ts index 61f94fa6c5..614f02fdb9 100644 --- a/lib/msal-common/src/client/RefreshTokenClient.ts +++ b/lib/msal-common/src/client/RefreshTokenClient.ts @@ -30,7 +30,7 @@ export class RefreshTokenClient extends BaseClient { super(configuration); } - public async acquireToken(request: RefreshTokenRequest): Promise{ + public async acquireToken(request: RefreshTokenRequest): Promise{ const response = await this.executeTokenRequest(request, this.authority); const responseHandler = new ResponseHandler( @@ -59,7 +59,7 @@ export class RefreshTokenClient extends BaseClient { * Gets cached refresh token and attaches to request, then calls acquireToken API * @param request */ - public async acquireTokenByRefreshToken(request: SilentFlowRequest): Promise { + public async acquireTokenByRefreshToken(request: SilentFlowRequest): Promise { // Cannot renew token if no request object is given. if (!request) { throw ClientConfigurationError.createEmptyTokenRequestError(); diff --git a/lib/msal-common/src/client/SilentFlowClient.ts b/lib/msal-common/src/client/SilentFlowClient.ts index 9f7ae47cb1..0ffb818cdf 100644 --- a/lib/msal-common/src/client/SilentFlowClient.ts +++ b/lib/msal-common/src/client/SilentFlowClient.ts @@ -29,7 +29,7 @@ export class SilentFlowClient extends BaseClient { * the given token and returns the renewed token * @param request */ - async acquireToken(request: SilentFlowRequest): Promise { + async acquireToken(request: SilentFlowRequest): Promise { try { return await this.acquireCachedToken(request); } catch (e) { diff --git a/lib/msal-common/src/config/ClientConfiguration.ts b/lib/msal-common/src/config/ClientConfiguration.ts index 3a103080c8..7e8ee8a0ec 100644 --- a/lib/msal-common/src/config/ClientConfiguration.ts +++ b/lib/msal-common/src/config/ClientConfiguration.ts @@ -4,7 +4,7 @@ */ import { INetworkModule } from "../network/INetworkModule"; -import { ICrypto, PkceCodes } from "../crypto/ICrypto"; +import { DEFAULT_CRYPTO_IMPLEMENTATION, ICrypto } from "../crypto/ICrypto"; import { AuthError } from "../error/AuthError"; import { ILoggerCallback, LogLevel } from "../logger/Logger"; import { Constants } from "../utils/Constants"; @@ -145,33 +145,6 @@ const DEFAULT_NETWORK_IMPLEMENTATION: INetworkModule = { } }; -const DEFAULT_CRYPTO_IMPLEMENTATION: ICrypto = { - createNewGuid: (): string => { - const notImplErr = "Crypto interface - createNewGuid() has not been implemented"; - throw AuthError.createUnexpectedError(notImplErr); - }, - base64Decode: (): string => { - const notImplErr = "Crypto interface - base64Decode() has not been implemented"; - throw AuthError.createUnexpectedError(notImplErr); - }, - base64Encode: (): string => { - const notImplErr = "Crypto interface - base64Encode() has not been implemented"; - throw AuthError.createUnexpectedError(notImplErr); - }, - async generatePkceCodes(): Promise { - const notImplErr = "Crypto interface - generatePkceCodes() has not been implemented"; - throw AuthError.createUnexpectedError(notImplErr); - }, - async getPublicKeyThumbprint(): Promise { - const notImplErr = "Crypto interface - getPublicKeyThumbprint() has not been implemented"; - throw AuthError.createUnexpectedError(notImplErr); - }, - async signJwt(): Promise { - const notImplErr = "Crypto interface - signJwt() has not been implemented"; - throw AuthError.createUnexpectedError(notImplErr); - } -}; - const DEFAULT_LIBRARY_INFO: LibraryInfo = { sku: Constants.SKU, version: version, @@ -210,7 +183,7 @@ export function buildClientConfiguration( authOptions: buildAuthOptions(userAuthOptions), systemOptions: { ...DEFAULT_SYSTEM_OPTIONS, ...userSystemOptions }, loggerOptions: { ...DEFAULT_LOGGER_IMPLEMENTATION, ...userLoggerOption }, - storageInterface: storageImplementation || new DefaultStorageClass(userAuthOptions.clientId, cryptoImplementation || DEFAULT_CRYPTO_IMPLEMENTATION), + storageInterface: storageImplementation || new DefaultStorageClass(userAuthOptions.clientId, DEFAULT_CRYPTO_IMPLEMENTATION), networkInterface: networkImplementation || DEFAULT_NETWORK_IMPLEMENTATION, cryptoInterface: cryptoImplementation || DEFAULT_CRYPTO_IMPLEMENTATION, clientCredentials: clientCredentials || DEFAULT_CLIENT_CREDENTIALS, diff --git a/lib/msal-common/src/crypto/ICrypto.ts b/lib/msal-common/src/crypto/ICrypto.ts index ecc009a55c..aa956747b9 100644 --- a/lib/msal-common/src/crypto/ICrypto.ts +++ b/lib/msal-common/src/crypto/ICrypto.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. */ +import { AuthError } from "../error/AuthError"; import { SignedHttpRequest } from "./SignedHttpRequest"; /** @@ -49,3 +50,30 @@ export interface ICrypto { */ signJwt(payload: SignedHttpRequest, kid: string): Promise; } + +export const DEFAULT_CRYPTO_IMPLEMENTATION: ICrypto = { + createNewGuid: (): string => { + const notImplErr = "Crypto interface - createNewGuid() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); + }, + base64Decode: (): string => { + const notImplErr = "Crypto interface - base64Decode() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); + }, + base64Encode: (): string => { + const notImplErr = "Crypto interface - base64Encode() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); + }, + async generatePkceCodes(): Promise { + const notImplErr = "Crypto interface - generatePkceCodes() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); + }, + async getPublicKeyThumbprint(): Promise { + const notImplErr = "Crypto interface - getPublicKeyThumbprint() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); + }, + async signJwt(): Promise { + const notImplErr = "Crypto interface - signJwt() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); + } +}; diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index b028d73fa7..f042d9eb4f 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -26,7 +26,7 @@ export { AuthorityFactory } from "./authority/AuthorityFactory"; export { AuthorityType } from "./authority/AuthorityType"; export { ProtocolMode } from "./authority/ProtocolMode"; // Cache -export { CacheManager } from "./cache/CacheManager"; +export { CacheManager, DefaultStorageClass } from "./cache/CacheManager"; export { AccountCache, AccessTokenCache, IdTokenCache, RefreshTokenCache, AppMetadataCache, ValidCacheType, ValidCredentialType } from "./cache/utils/CacheTypes"; export { CredentialEntity } from "./cache/entities/CredentialEntity"; export { AppMetadataEntity } from "./cache/entities/AppMetadataEntity"; @@ -40,14 +40,14 @@ export { ICachePlugin } from "./cache/interface/ICachePlugin"; export { TokenCacheContext } from "./cache/persistence/TokenCacheContext"; export { ISerializableTokenCache } from "./cache/interface/ISerializableTokenCache"; // Network Interface -export { INetworkModule, NetworkRequestOptions } from "./network/INetworkModule"; +export { INetworkModule, NetworkRequestOptions, StubbedNetworkModule } from "./network/INetworkModule"; export { NetworkManager, NetworkResponse } from "./network/NetworkManager"; export { ThrottlingUtils } from "./network/ThrottlingUtils"; export { RequestThumbprint } from "./network/RequestThumbprint"; export { IUri } from "./url/IUri"; export { UrlString } from "./url/UrlString"; // Crypto Interface -export { ICrypto, PkceCodes } from "./crypto/ICrypto"; +export { ICrypto, PkceCodes, DEFAULT_CRYPTO_IMPLEMENTATION } from "./crypto/ICrypto"; export { SignedHttpRequest } from "./crypto/SignedHttpRequest"; // Request and Response export { BaseAuthRequest } from "./request/BaseAuthRequest"; diff --git a/lib/msal-common/src/network/INetworkModule.ts b/lib/msal-common/src/network/INetworkModule.ts index 788498bb65..d16b69b800 100644 --- a/lib/msal-common/src/network/INetworkModule.ts +++ b/lib/msal-common/src/network/INetworkModule.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. */ +import { AuthError } from "../error/AuthError"; import { NetworkResponse } from "./NetworkManager"; /** @@ -35,3 +36,14 @@ export interface INetworkModule { */ sendPostRequestAsync(url: string, options?: NetworkRequestOptions): Promise>; } + +export const StubbedNetworkModule: INetworkModule = { + sendGetRequestAsync: () => { + const notImplErr = "Network interface - sendGetRequestAsync() has not been implemented for the Network interface."; + return Promise.reject(AuthError.createUnexpectedError(notImplErr)); + }, + sendPostRequestAsync: () => { + const notImplErr = "Network interface - sendPostRequestAsync() has not been implemented for the Network interface."; + return Promise.reject(AuthError.createUnexpectedError(notImplErr)); + } +}; diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index aae28a9a2a..f84f794b87 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -114,7 +114,7 @@ export class ResponseHandler { authCodePayload?: AuthorizationCodePayload, requestScopes?: string[], oboAssertion?: string, - handlingRefreshTokenResponse?: boolean): Promise { + handlingRefreshTokenResponse?: boolean): Promise { // create an idToken object (not entity) let idTokenObj: AuthToken | undefined; @@ -156,7 +156,7 @@ export class ResponseHandler { const account = this.cacheStorage.getAccount(key); if (!account) { this.logger.warning("Account used to refresh tokens not in persistence, refreshed tokens will not be stored in the cache"); - return null; + return ResponseHandler.generateAuthenticationResult(this.cryptoObj, authority, cacheRecord, false, idTokenObj, requestStateObj, resourceRequestMethod, resourceRequestUri); } } this.cacheStorage.saveCacheRecord(cacheRecord);