@azure/msal-browser 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/dist/app/IPublicClientApplication.d.ts +2 -0
  2. package/dist/app/IPublicClientApplication.d.ts.map +1 -1
  3. package/dist/app/IPublicClientApplication.mjs +4 -1
  4. package/dist/app/IPublicClientApplication.mjs.map +1 -1
  5. package/dist/app/PublicClientApplication.d.ts +6 -0
  6. package/dist/app/PublicClientApplication.d.ts.map +1 -1
  7. package/dist/app/PublicClientApplication.mjs +8 -1
  8. package/dist/app/PublicClientApplication.mjs.map +1 -1
  9. package/dist/broker/nativeBroker/NativeMessageHandler.d.ts.map +1 -1
  10. package/dist/broker/nativeBroker/NativeMessageHandler.mjs +5 -4
  11. package/dist/broker/nativeBroker/NativeMessageHandler.mjs.map +1 -1
  12. package/dist/cache/AsyncMemoryStorage.mjs +4 -3
  13. package/dist/cache/AsyncMemoryStorage.mjs.map +1 -1
  14. package/dist/cache/BrowserCacheManager.d.ts +9 -2
  15. package/dist/cache/BrowserCacheManager.d.ts.map +1 -1
  16. package/dist/cache/BrowserCacheManager.mjs +54 -26
  17. package/dist/cache/BrowserCacheManager.mjs.map +1 -1
  18. package/dist/cache/BrowserStorage.mjs +1 -1
  19. package/dist/cache/BrowserStorage.mjs.map +1 -1
  20. package/dist/cache/CryptoKeyStore.mjs +1 -1
  21. package/dist/cache/CryptoKeyStore.mjs.map +1 -1
  22. package/dist/cache/DatabaseStorage.d.ts.map +1 -1
  23. package/dist/cache/DatabaseStorage.mjs +9 -8
  24. package/dist/cache/DatabaseStorage.mjs.map +1 -1
  25. package/dist/cache/MemoryStorage.mjs +1 -1
  26. package/dist/cache/MemoryStorage.mjs.map +1 -1
  27. package/dist/cache/TokenCache.d.ts.map +1 -1
  28. package/dist/cache/TokenCache.mjs +30 -30
  29. package/dist/cache/TokenCache.mjs.map +1 -1
  30. package/dist/config/Configuration.d.ts +1 -0
  31. package/dist/config/Configuration.d.ts.map +1 -1
  32. package/dist/config/Configuration.mjs +7 -11
  33. package/dist/config/Configuration.mjs.map +1 -1
  34. package/dist/controllers/ControllerFactory.mjs +1 -1
  35. package/dist/controllers/ControllerFactory.mjs.map +1 -1
  36. package/dist/controllers/IController.d.ts +3 -27
  37. package/dist/controllers/IController.d.ts.map +1 -1
  38. package/dist/controllers/StandardController.d.ts +6 -25
  39. package/dist/controllers/StandardController.d.ts.map +1 -1
  40. package/dist/controllers/StandardController.mjs +28 -52
  41. package/dist/controllers/StandardController.mjs.map +1 -1
  42. package/dist/crypto/BrowserCrypto.d.ts.map +1 -1
  43. package/dist/crypto/BrowserCrypto.mjs +4 -3
  44. package/dist/crypto/BrowserCrypto.mjs.map +1 -1
  45. package/dist/crypto/CryptoOps.d.ts +0 -3
  46. package/dist/crypto/CryptoOps.d.ts.map +1 -1
  47. package/dist/crypto/CryptoOps.mjs +14 -17
  48. package/dist/crypto/CryptoOps.mjs.map +1 -1
  49. package/dist/crypto/ModernBrowserCrypto.mjs +1 -1
  50. package/dist/crypto/ModernBrowserCrypto.mjs.map +1 -1
  51. package/dist/crypto/PkceGenerator.d.ts +0 -1
  52. package/dist/crypto/PkceGenerator.d.ts.map +1 -1
  53. package/dist/crypto/PkceGenerator.mjs +8 -8
  54. package/dist/crypto/PkceGenerator.mjs.map +1 -1
  55. package/dist/crypto/SignedHttpRequest.mjs +1 -1
  56. package/dist/crypto/SignedHttpRequest.mjs.map +1 -1
  57. package/dist/encode/Base64Decode.d.ts +6 -19
  58. package/dist/encode/Base64Decode.d.ts.map +1 -1
  59. package/dist/encode/Base64Decode.mjs +17 -70
  60. package/dist/encode/Base64Decode.mjs.map +1 -1
  61. package/dist/encode/Base64Encode.d.ts +15 -27
  62. package/dist/encode/Base64Encode.d.ts.map +1 -1
  63. package/dist/encode/Base64Encode.mjs +36 -75
  64. package/dist/encode/Base64Encode.mjs.map +1 -1
  65. package/dist/error/BrowserAuthError.d.ts +54 -195
  66. package/dist/error/BrowserAuthError.d.ts.map +1 -1
  67. package/dist/error/BrowserAuthError.mjs +150 -381
  68. package/dist/error/BrowserAuthError.mjs.map +1 -1
  69. package/dist/error/BrowserAuthErrorCodes.d.ts +45 -0
  70. package/dist/error/BrowserAuthErrorCodes.d.ts.map +1 -0
  71. package/dist/error/BrowserAuthErrorCodes.mjs +53 -0
  72. package/dist/error/BrowserAuthErrorCodes.mjs.map +1 -0
  73. package/dist/error/BrowserConfigurationAuthError.mjs +1 -1
  74. package/dist/error/BrowserConfigurationAuthError.mjs.map +1 -1
  75. package/dist/error/NativeAuthError.d.ts.map +1 -1
  76. package/dist/error/NativeAuthError.mjs +5 -4
  77. package/dist/error/NativeAuthError.mjs.map +1 -1
  78. package/dist/event/EventHandler.mjs +1 -1
  79. package/dist/event/EventHandler.mjs.map +1 -1
  80. package/dist/event/EventMessage.mjs +1 -1
  81. package/dist/event/EventMessage.mjs.map +1 -1
  82. package/dist/event/EventType.mjs +1 -1
  83. package/dist/event/EventType.mjs.map +1 -1
  84. package/dist/index.d.ts +5 -5
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.mjs +5 -3
  87. package/dist/index.mjs.map +1 -1
  88. package/dist/interaction_client/BaseInteractionClient.d.ts +2 -1
  89. package/dist/interaction_client/BaseInteractionClient.d.ts.map +1 -1
  90. package/dist/interaction_client/BaseInteractionClient.mjs +1 -1
  91. package/dist/interaction_client/BaseInteractionClient.mjs.map +1 -1
  92. package/dist/interaction_client/HybridSpaAuthorizationCodeClient.mjs +1 -1
  93. package/dist/interaction_client/HybridSpaAuthorizationCodeClient.mjs.map +1 -1
  94. package/dist/interaction_client/NativeInteractionClient.d.ts +4 -10
  95. package/dist/interaction_client/NativeInteractionClient.d.ts.map +1 -1
  96. package/dist/interaction_client/NativeInteractionClient.mjs +22 -30
  97. package/dist/interaction_client/NativeInteractionClient.mjs.map +1 -1
  98. package/dist/interaction_client/PopupClient.d.ts.map +1 -1
  99. package/dist/interaction_client/PopupClient.mjs +16 -15
  100. package/dist/interaction_client/PopupClient.mjs.map +1 -1
  101. package/dist/interaction_client/RedirectClient.d.ts.map +1 -1
  102. package/dist/interaction_client/RedirectClient.mjs +7 -6
  103. package/dist/interaction_client/RedirectClient.mjs.map +1 -1
  104. package/dist/interaction_client/SilentAuthCodeClient.d.ts.map +1 -1
  105. package/dist/interaction_client/SilentAuthCodeClient.mjs +5 -4
  106. package/dist/interaction_client/SilentAuthCodeClient.mjs.map +1 -1
  107. package/dist/interaction_client/SilentCacheClient.d.ts +4 -2
  108. package/dist/interaction_client/SilentCacheClient.d.ts.map +1 -1
  109. package/dist/interaction_client/SilentCacheClient.mjs +10 -8
  110. package/dist/interaction_client/SilentCacheClient.mjs.map +1 -1
  111. package/dist/interaction_client/SilentIframeClient.d.ts.map +1 -1
  112. package/dist/interaction_client/SilentIframeClient.mjs +20 -47
  113. package/dist/interaction_client/SilentIframeClient.mjs.map +1 -1
  114. package/dist/interaction_client/SilentRefreshClient.d.ts.map +1 -1
  115. package/dist/interaction_client/SilentRefreshClient.mjs +4 -3
  116. package/dist/interaction_client/SilentRefreshClient.mjs.map +1 -1
  117. package/dist/interaction_client/StandardInteractionClient.d.ts.map +1 -1
  118. package/dist/interaction_client/StandardInteractionClient.mjs +8 -7
  119. package/dist/interaction_client/StandardInteractionClient.mjs.map +1 -1
  120. package/dist/interaction_handler/InteractionHandler.d.ts.map +1 -1
  121. package/dist/interaction_handler/InteractionHandler.mjs +9 -9
  122. package/dist/interaction_handler/InteractionHandler.mjs.map +1 -1
  123. package/dist/interaction_handler/RedirectHandler.d.ts.map +1 -1
  124. package/dist/interaction_handler/RedirectHandler.mjs +11 -10
  125. package/dist/interaction_handler/RedirectHandler.mjs.map +1 -1
  126. package/dist/interaction_handler/SilentHandler.d.ts.map +1 -1
  127. package/dist/interaction_handler/SilentHandler.mjs +10 -9
  128. package/dist/interaction_handler/SilentHandler.mjs.map +1 -1
  129. package/dist/navigation/NavigationClient.mjs +1 -1
  130. package/dist/navigation/NavigationClient.mjs.map +1 -1
  131. package/dist/network/FetchClient.d.ts.map +1 -1
  132. package/dist/network/FetchClient.mjs +9 -8
  133. package/dist/network/FetchClient.mjs.map +1 -1
  134. package/dist/operatingcontext/BaseOperatingContext.mjs +1 -1
  135. package/dist/operatingcontext/BaseOperatingContext.mjs.map +1 -1
  136. package/dist/operatingcontext/StandardOperatingContext.mjs +1 -1
  137. package/dist/operatingcontext/StandardOperatingContext.mjs.map +1 -1
  138. package/dist/operatingcontext/TeamsAppOperatingContext.mjs +1 -1
  139. package/dist/operatingcontext/TeamsAppOperatingContext.mjs.map +1 -1
  140. package/dist/packageMetadata.d.ts +1 -1
  141. package/dist/packageMetadata.mjs +2 -2
  142. package/dist/packageMetadata.mjs.map +1 -1
  143. package/dist/request/ClearCacheRequest.d.ts +11 -0
  144. package/dist/request/ClearCacheRequest.d.ts.map +1 -0
  145. package/dist/telemetry/BrowserPerformanceClient.d.ts +2 -4
  146. package/dist/telemetry/BrowserPerformanceClient.d.ts.map +1 -1
  147. package/dist/telemetry/BrowserPerformanceClient.mjs +2 -6
  148. package/dist/telemetry/BrowserPerformanceClient.mjs.map +1 -1
  149. package/dist/telemetry/BrowserPerformanceMeasurement.mjs +1 -1
  150. package/dist/telemetry/BrowserPerformanceMeasurement.mjs.map +1 -1
  151. package/dist/utils/BrowserConstants.mjs +1 -1
  152. package/dist/utils/BrowserConstants.mjs.map +1 -1
  153. package/dist/utils/BrowserProtocolUtils.d.ts.map +1 -1
  154. package/dist/utils/BrowserProtocolUtils.mjs +3 -3
  155. package/dist/utils/BrowserProtocolUtils.mjs.map +1 -1
  156. package/dist/utils/BrowserStringUtils.mjs +1 -1
  157. package/dist/utils/BrowserStringUtils.mjs.map +1 -1
  158. package/dist/utils/BrowserUtils.d.ts +0 -9
  159. package/dist/utils/BrowserUtils.d.ts.map +1 -1
  160. package/dist/utils/BrowserUtils.mjs +8 -33
  161. package/dist/utils/BrowserUtils.mjs.map +1 -1
  162. package/lib/msal-browser.cjs +6579 -7102
  163. package/lib/msal-browser.cjs.map +1 -1
  164. package/lib/msal-browser.js +6579 -7102
  165. package/lib/msal-browser.js.map +1 -1
  166. package/lib/msal-browser.min.js +65 -62
  167. package/package.json +7 -5
  168. package/src/app/IPublicClientApplication.ts +199 -0
  169. package/src/app/PublicClientApplication.ts +405 -0
  170. package/src/broker/nativeBroker/NativeMessageHandler.ts +419 -0
  171. package/src/broker/nativeBroker/NativeRequest.ts +52 -0
  172. package/src/broker/nativeBroker/NativeResponse.ts +56 -0
  173. package/src/cache/AsyncMemoryStorage.ts +158 -0
  174. package/src/cache/BrowserCacheManager.ts +1994 -0
  175. package/src/cache/BrowserStorage.ts +54 -0
  176. package/src/cache/CryptoKeyStore.ts +63 -0
  177. package/src/cache/DatabaseStorage.ts +287 -0
  178. package/src/cache/IAsyncMemoryStorage.ts +36 -0
  179. package/src/cache/ITokenCache.ts +21 -0
  180. package/src/cache/IWindowStorage.ts +36 -0
  181. package/src/cache/MemoryStorage.ts +42 -0
  182. package/src/cache/TokenCache.ts +484 -0
  183. package/src/config/Configuration.ts +375 -0
  184. package/src/controllers/ControllerFactory.ts +55 -0
  185. package/src/controllers/IController.ts +131 -0
  186. package/src/controllers/StandardController.ts +2107 -0
  187. package/src/crypto/BrowserCrypto.ts +135 -0
  188. package/src/crypto/CryptoOps.ts +247 -0
  189. package/src/crypto/ISubtleCrypto.ts +30 -0
  190. package/src/crypto/ModernBrowserCrypto.ts +69 -0
  191. package/src/crypto/PkceGenerator.ts +76 -0
  192. package/src/crypto/SignedHttpRequest.ts +76 -0
  193. package/src/encode/Base64Decode.ts +26 -0
  194. package/src/encode/Base64Encode.ts +52 -0
  195. package/src/error/BrowserAuthError.ts +340 -0
  196. package/src/error/BrowserAuthErrorCodes.ts +56 -0
  197. package/src/error/BrowserConfigurationAuthError.ts +133 -0
  198. package/src/error/NativeAuthError.ts +141 -0
  199. package/src/event/EventHandler.ts +176 -0
  200. package/src/event/EventMessage.ts +125 -0
  201. package/src/event/EventType.ts +33 -0
  202. package/src/index.ts +152 -0
  203. package/src/interaction_client/BaseInteractionClient.ts +301 -0
  204. package/src/interaction_client/HybridSpaAuthorizationCodeClient.ts +16 -0
  205. package/src/interaction_client/NativeInteractionClient.ts +907 -0
  206. package/src/interaction_client/PopupClient.ts +926 -0
  207. package/src/interaction_client/RedirectClient.ts +616 -0
  208. package/src/interaction_client/SilentAuthCodeClient.ts +163 -0
  209. package/src/interaction_client/SilentCacheClient.ts +133 -0
  210. package/src/interaction_client/SilentIframeClient.ts +301 -0
  211. package/src/interaction_client/SilentRefreshClient.ts +120 -0
  212. package/src/interaction_client/StandardInteractionClient.ts +481 -0
  213. package/src/interaction_handler/InteractionHandler.ts +246 -0
  214. package/src/interaction_handler/RedirectHandler.ts +225 -0
  215. package/src/interaction_handler/SilentHandler.ts +266 -0
  216. package/src/navigation/INavigationClient.ts +23 -0
  217. package/src/navigation/NavigationClient.ts +55 -0
  218. package/src/navigation/NavigationOptions.ts +18 -0
  219. package/src/network/FetchClient.ts +130 -0
  220. package/src/operatingcontext/BaseOperatingContext.ts +82 -0
  221. package/src/operatingcontext/StandardOperatingContext.ts +49 -0
  222. package/src/operatingcontext/TeamsAppOperatingContext.ts +48 -0
  223. package/src/packageMetadata.ts +3 -0
  224. package/src/request/AuthorizationCodeRequest.ts +19 -0
  225. package/src/request/AuthorizationUrlRequest.ts +17 -0
  226. package/src/request/ClearCacheRequest.ts +16 -0
  227. package/src/request/EndSessionPopupRequest.ts +26 -0
  228. package/src/request/EndSessionRequest.ts +23 -0
  229. package/src/request/PopupRequest.ts +51 -0
  230. package/src/request/PopupWindowAttributes.ts +22 -0
  231. package/src/request/RedirectRequest.ts +51 -0
  232. package/src/request/SilentRequest.ts +49 -0
  233. package/src/request/SsoSilentRequest.ts +44 -0
  234. package/src/response/AuthenticationResult.ts +13 -0
  235. package/src/telemetry/BrowserPerformanceClient.ts +226 -0
  236. package/src/telemetry/BrowserPerformanceMeasurement.ts +144 -0
  237. package/src/utils/BrowserConstants.ts +241 -0
  238. package/src/utils/BrowserProtocolUtils.ts +57 -0
  239. package/src/utils/BrowserStringUtils.ts +143 -0
  240. package/src/utils/BrowserUtils.ts +156 -0
  241. package/dist/crypto/GuidGenerator.d.ts +0 -13
  242. package/dist/crypto/GuidGenerator.d.ts.map +0 -1
  243. package/dist/crypto/GuidGenerator.mjs +0 -104
  244. package/dist/crypto/GuidGenerator.mjs.map +0 -1
  245. package/dist/internals.d.ts +0 -23
  246. package/dist/internals.d.ts.map +0 -1
  247. package/dist/internals.mjs +0 -28
  248. package/dist/internals.mjs.map +0 -1
  249. package/dist/network/XhrClient.d.ts +0 -41
  250. package/dist/network/XhrClient.d.ts.map +0 -1
  251. package/dist/network/XhrClient.mjs +0 -125
  252. package/dist/network/XhrClient.mjs.map +0 -1
  253. package/dist/utils/MathUtils.d.ts +0 -12
  254. package/dist/utils/MathUtils.d.ts.map +0 -1
  255. package/dist/utils/MathUtils.mjs +0 -26
  256. package/dist/utils/MathUtils.mjs.map +0 -1
@@ -0,0 +1,1994 @@
1
+ /*
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ Constants,
8
+ PersistentCacheKeys,
9
+ StringUtils,
10
+ CommonAuthorizationCodeRequest,
11
+ ICrypto,
12
+ AccountEntity,
13
+ IdTokenEntity,
14
+ AccessTokenEntity,
15
+ RefreshTokenEntity,
16
+ AppMetadataEntity,
17
+ CacheManager,
18
+ ServerTelemetryEntity,
19
+ ThrottlingEntity,
20
+ ProtocolUtils,
21
+ Logger,
22
+ AuthorityMetadataEntity,
23
+ DEFAULT_CRYPTO_IMPLEMENTATION,
24
+ AccountInfo,
25
+ ActiveAccountFilters,
26
+ CcsCredential,
27
+ CcsCredentialType,
28
+ AuthToken,
29
+ ValidCredentialType,
30
+ ClientAuthError,
31
+ TokenKeys,
32
+ CredentialType,
33
+ CacheRecord,
34
+ AuthenticationScheme,
35
+ } from "@azure/msal-common";
36
+ import { CacheOptions } from "../config/Configuration";
37
+ import {
38
+ createBrowserAuthError,
39
+ BrowserAuthErrorCodes,
40
+ } from "../error/BrowserAuthError";
41
+ import {
42
+ BrowserCacheLocation,
43
+ InteractionType,
44
+ TemporaryCacheKeys,
45
+ InMemoryCacheKeys,
46
+ StaticCacheKeys,
47
+ } from "../utils/BrowserConstants";
48
+ import { BrowserStorage } from "./BrowserStorage";
49
+ import { MemoryStorage } from "./MemoryStorage";
50
+ import { IWindowStorage } from "./IWindowStorage";
51
+ import { BrowserProtocolUtils } from "../utils/BrowserProtocolUtils";
52
+ import { NativeTokenRequest } from "../broker/nativeBroker/NativeRequest";
53
+ import { AuthenticationResult } from "../response/AuthenticationResult";
54
+ import { SilentRequest } from "../request/SilentRequest";
55
+ import { SsoSilentRequest } from "../request/SsoSilentRequest";
56
+ import { RedirectRequest } from "../request/RedirectRequest";
57
+ import { PopupRequest } from "../request/PopupRequest";
58
+ import { base64Decode } from "../encode/Base64Decode";
59
+ import { base64Encode } from "../encode/Base64Encode";
60
+
61
+ /**
62
+ * This class implements the cache storage interface for MSAL through browser local or session storage.
63
+ * Cookies are only used if storeAuthStateInCookie is true, and are only used for
64
+ * parameters such as state and nonce, generally.
65
+ */
66
+ export class BrowserCacheManager extends CacheManager {
67
+ // Cache configuration, either set by user or default values.
68
+ protected cacheConfig: Required<CacheOptions>;
69
+ // Window storage object (either local or sessionStorage)
70
+ protected browserStorage: IWindowStorage<string>;
71
+ // Internal in-memory storage object used for data used by msal that does not need to persist across page loads
72
+ protected internalStorage: MemoryStorage<string>;
73
+ // Temporary cache
74
+ protected temporaryCacheStorage: IWindowStorage<string>;
75
+ // Logger instance
76
+ protected logger: Logger;
77
+
78
+ // Cookie life calculation (hours * minutes * seconds * ms)
79
+ protected readonly COOKIE_LIFE_MULTIPLIER = 24 * 60 * 60 * 1000;
80
+
81
+ constructor(
82
+ clientId: string,
83
+ cacheConfig: Required<CacheOptions>,
84
+ cryptoImpl: ICrypto,
85
+ logger: Logger
86
+ ) {
87
+ super(clientId, cryptoImpl, logger);
88
+ this.cacheConfig = cacheConfig;
89
+ this.logger = logger;
90
+ this.internalStorage = new MemoryStorage();
91
+ this.browserStorage = this.setupBrowserStorage(
92
+ this.cacheConfig.cacheLocation
93
+ );
94
+ this.temporaryCacheStorage = this.setupTemporaryCacheStorage(
95
+ this.cacheConfig.temporaryCacheLocation,
96
+ this.cacheConfig.cacheLocation
97
+ );
98
+
99
+ // Migrate cache entries from older versions of MSAL.
100
+ if (cacheConfig.cacheMigrationEnabled) {
101
+ this.migrateCacheEntries();
102
+ this.createKeyMaps();
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Returns a window storage class implementing the IWindowStorage interface that corresponds to the configured cacheLocation.
108
+ * @param cacheLocation
109
+ */
110
+ protected setupBrowserStorage(
111
+ cacheLocation: BrowserCacheLocation | string
112
+ ): IWindowStorage<string> {
113
+ switch (cacheLocation) {
114
+ case BrowserCacheLocation.LocalStorage:
115
+ case BrowserCacheLocation.SessionStorage:
116
+ try {
117
+ return new BrowserStorage(cacheLocation);
118
+ } catch (e) {
119
+ this.logger.verbose(e as string);
120
+ break;
121
+ }
122
+ case BrowserCacheLocation.MemoryStorage:
123
+ default:
124
+ break;
125
+ }
126
+ this.cacheConfig.cacheLocation = BrowserCacheLocation.MemoryStorage;
127
+ return new MemoryStorage();
128
+ }
129
+
130
+ /**
131
+ * Returns a window storage class implementing the IWindowStorage interface that corresponds to the configured temporaryCacheLocation.
132
+ * @param temporaryCacheLocation
133
+ * @param cacheLocation
134
+ */
135
+ protected setupTemporaryCacheStorage(
136
+ temporaryCacheLocation: BrowserCacheLocation | string,
137
+ cacheLocation: BrowserCacheLocation | string
138
+ ): IWindowStorage<string> {
139
+ switch (cacheLocation) {
140
+ case BrowserCacheLocation.LocalStorage:
141
+ case BrowserCacheLocation.SessionStorage:
142
+ try {
143
+ // Temporary cache items will always be stored in session storage to mitigate problems caused by multiple tabs
144
+ return new BrowserStorage(
145
+ temporaryCacheLocation ||
146
+ BrowserCacheLocation.SessionStorage
147
+ );
148
+ } catch (e) {
149
+ this.logger.verbose(e as string);
150
+ return this.internalStorage;
151
+ }
152
+ case BrowserCacheLocation.MemoryStorage:
153
+ default:
154
+ return this.internalStorage;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Migrate all old cache entries to new schema. No rollback supported.
160
+ * @param storeAuthStateInCookie
161
+ */
162
+ protected migrateCacheEntries(): void {
163
+ const idTokenKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.ID_TOKEN}`;
164
+ const clientInfoKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.CLIENT_INFO}`;
165
+ const errorKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.ERROR}`;
166
+ const errorDescKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.ERROR_DESC}`;
167
+
168
+ const idTokenValue = this.browserStorage.getItem(idTokenKey);
169
+ const clientInfoValue = this.browserStorage.getItem(clientInfoKey);
170
+ const errorValue = this.browserStorage.getItem(errorKey);
171
+ const errorDescValue = this.browserStorage.getItem(errorDescKey);
172
+
173
+ const values = [
174
+ idTokenValue,
175
+ clientInfoValue,
176
+ errorValue,
177
+ errorDescValue,
178
+ ];
179
+ const keysToMigrate = [
180
+ PersistentCacheKeys.ID_TOKEN,
181
+ PersistentCacheKeys.CLIENT_INFO,
182
+ PersistentCacheKeys.ERROR,
183
+ PersistentCacheKeys.ERROR_DESC,
184
+ ];
185
+
186
+ keysToMigrate.forEach((cacheKey: string, index: number) =>
187
+ this.migrateCacheEntry(cacheKey, values[index])
188
+ );
189
+ }
190
+
191
+ /**
192
+ * Utility function to help with migration.
193
+ * @param newKey
194
+ * @param value
195
+ * @param storeAuthStateInCookie
196
+ */
197
+ protected migrateCacheEntry(newKey: string, value: string | null): void {
198
+ if (value) {
199
+ this.setTemporaryCache(newKey, value, true);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Searches all cache entries for MSAL accounts and creates the account key map
205
+ * This is used to migrate users from older versions of MSAL which did not create the map.
206
+ * @returns
207
+ */
208
+ private createKeyMaps(): void {
209
+ this.logger.trace("BrowserCacheManager - createKeyMaps called.");
210
+ const accountKeys = this.getItem(StaticCacheKeys.ACCOUNT_KEYS);
211
+ const tokenKeys = this.getItem(
212
+ `${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`
213
+ );
214
+ if (accountKeys && tokenKeys) {
215
+ this.logger.verbose(
216
+ "BrowserCacheManager:createKeyMaps - account and token key maps already exist, skipping migration."
217
+ );
218
+ // Key maps already exist, no need to iterate through cache
219
+ return;
220
+ }
221
+
222
+ const allKeys = this.browserStorage.getKeys();
223
+ allKeys.forEach((key) => {
224
+ if (this.isCredentialKey(key)) {
225
+ // Get item, parse, validate and write key to map
226
+ const value = this.getItem(key);
227
+ if (value) {
228
+ const credObj = this.validateAndParseJson(value);
229
+ if (credObj && credObj.hasOwnProperty("credentialType")) {
230
+ switch (credObj["credentialType"]) {
231
+ case CredentialType.ID_TOKEN:
232
+ if (IdTokenEntity.isIdTokenEntity(credObj)) {
233
+ this.logger.trace(
234
+ "BrowserCacheManager:createKeyMaps - idToken found, saving key to token key map"
235
+ );
236
+ this.logger.tracePii(
237
+ `BrowserCacheManager:createKeyMaps - idToken with key: ${key} found, saving key to token key map`
238
+ );
239
+ const idTokenEntity = CacheManager.toObject(
240
+ new IdTokenEntity(),
241
+ credObj
242
+ );
243
+ const newKey =
244
+ this.updateCredentialCacheKey(
245
+ key,
246
+ idTokenEntity
247
+ );
248
+ this.addTokenKey(
249
+ newKey,
250
+ CredentialType.ID_TOKEN
251
+ );
252
+ return;
253
+ } else {
254
+ this.logger.trace(
255
+ "BrowserCacheManager:createKeyMaps - key found matching idToken schema with value containing idToken credentialType field but value failed IdTokenEntity validation, skipping."
256
+ );
257
+ this.logger.tracePii(
258
+ `BrowserCacheManager:createKeyMaps - failed idToken validation on key: ${key}`
259
+ );
260
+ }
261
+ break;
262
+ case CredentialType.ACCESS_TOKEN:
263
+ case CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME:
264
+ if (
265
+ AccessTokenEntity.isAccessTokenEntity(
266
+ credObj
267
+ )
268
+ ) {
269
+ this.logger.trace(
270
+ "BrowserCacheManager:createKeyMaps - accessToken found, saving key to token key map"
271
+ );
272
+ this.logger.tracePii(
273
+ `BrowserCacheManager:createKeyMaps - accessToken with key: ${key} found, saving key to token key map`
274
+ );
275
+ const accessTokenEntity =
276
+ CacheManager.toObject(
277
+ new AccessTokenEntity(),
278
+ credObj
279
+ );
280
+ const newKey =
281
+ this.updateCredentialCacheKey(
282
+ key,
283
+ accessTokenEntity
284
+ );
285
+ this.addTokenKey(
286
+ newKey,
287
+ CredentialType.ACCESS_TOKEN
288
+ );
289
+ return;
290
+ } else {
291
+ this.logger.trace(
292
+ "BrowserCacheManager:createKeyMaps - key found matching accessToken schema with value containing accessToken credentialType field but value failed AccessTokenEntity validation, skipping."
293
+ );
294
+ this.logger.tracePii(
295
+ `BrowserCacheManager:createKeyMaps - failed accessToken validation on key: ${key}`
296
+ );
297
+ }
298
+ break;
299
+ case CredentialType.REFRESH_TOKEN:
300
+ if (
301
+ RefreshTokenEntity.isRefreshTokenEntity(
302
+ credObj
303
+ )
304
+ ) {
305
+ this.logger.trace(
306
+ "BrowserCacheManager:createKeyMaps - refreshToken found, saving key to token key map"
307
+ );
308
+ this.logger.tracePii(
309
+ `BrowserCacheManager:createKeyMaps - refreshToken with key: ${key} found, saving key to token key map`
310
+ );
311
+ const refreshTokenEntity =
312
+ CacheManager.toObject(
313
+ new RefreshTokenEntity(),
314
+ credObj
315
+ );
316
+ const newKey =
317
+ this.updateCredentialCacheKey(
318
+ key,
319
+ refreshTokenEntity
320
+ );
321
+ this.addTokenKey(
322
+ newKey,
323
+ CredentialType.REFRESH_TOKEN
324
+ );
325
+ return;
326
+ } else {
327
+ this.logger.trace(
328
+ "BrowserCacheManager:createKeyMaps - key found matching refreshToken schema with value containing refreshToken credentialType field but value failed RefreshTokenEntity validation, skipping."
329
+ );
330
+ this.logger.tracePii(
331
+ `BrowserCacheManager:createKeyMaps - failed refreshToken validation on key: ${key}`
332
+ );
333
+ }
334
+ break;
335
+ default:
336
+ // If credentialType isn't one of our predefined ones, it may not be an MSAL cache value. Ignore.
337
+ }
338
+ }
339
+ }
340
+ }
341
+
342
+ if (this.isAccountKey(key)) {
343
+ const value = this.getItem(key);
344
+ if (value) {
345
+ const accountObj = this.validateAndParseJson(value);
346
+ if (
347
+ accountObj &&
348
+ AccountEntity.isAccountEntity(accountObj)
349
+ ) {
350
+ this.logger.trace(
351
+ "BrowserCacheManager:createKeyMaps - account found, saving key to account key map"
352
+ );
353
+ this.logger.tracePii(
354
+ `BrowserCacheManager:createKeyMaps - account with key: ${key} found, saving key to account key map`
355
+ );
356
+ this.addAccountKeyToMap(key);
357
+ }
358
+ }
359
+ }
360
+ });
361
+ }
362
+
363
+ /**
364
+ * Parses passed value as JSON object, JSON.parse() will throw an error.
365
+ * @param input
366
+ */
367
+ protected validateAndParseJson(jsonValue: string): object | null {
368
+ try {
369
+ const parsedJson = JSON.parse(jsonValue);
370
+ /**
371
+ * There are edge cases in which JSON.parse will successfully parse a non-valid JSON object
372
+ * (e.g. JSON.parse will parse an escaped string into an unescaped string), so adding a type check
373
+ * of the parsed value is necessary in order to be certain that the string represents a valid JSON object.
374
+ *
375
+ */
376
+ return parsedJson && typeof parsedJson === "object"
377
+ ? parsedJson
378
+ : null;
379
+ } catch (error) {
380
+ return null;
381
+ }
382
+ }
383
+
384
+ /**
385
+ * fetches the entry from the browser storage based off the key
386
+ * @param key
387
+ */
388
+ getItem(key: string): string | null {
389
+ return this.browserStorage.getItem(key);
390
+ }
391
+
392
+ /**
393
+ * sets the entry in the browser storage
394
+ * @param key
395
+ * @param value
396
+ */
397
+ setItem(key: string, value: string): void {
398
+ this.browserStorage.setItem(key, value);
399
+ }
400
+
401
+ /**
402
+ * fetch the account entity from the platform cache
403
+ * @param accountKey
404
+ */
405
+ getAccount(accountKey: string): AccountEntity | null {
406
+ this.logger.trace("BrowserCacheManager.getAccount called");
407
+ const account = this.getItem(accountKey);
408
+ if (!account) {
409
+ this.removeAccountKeyFromMap(accountKey);
410
+ return null;
411
+ }
412
+
413
+ const parsedAccount = this.validateAndParseJson(account);
414
+ if (!parsedAccount || !AccountEntity.isAccountEntity(parsedAccount)) {
415
+ this.removeAccountKeyFromMap(accountKey);
416
+ return null;
417
+ }
418
+
419
+ return CacheManager.toObject<AccountEntity>(
420
+ new AccountEntity(),
421
+ parsedAccount
422
+ );
423
+ }
424
+
425
+ /**
426
+ * set account entity in the platform cache
427
+ * @param account
428
+ */
429
+ setAccount(account: AccountEntity): void {
430
+ this.logger.trace("BrowserCacheManager.setAccount called");
431
+ const key = account.generateAccountKey();
432
+ this.setItem(key, JSON.stringify(account));
433
+ this.addAccountKeyToMap(key);
434
+ }
435
+
436
+ /**
437
+ * Returns the array of account keys currently cached
438
+ * @returns
439
+ */
440
+ getAccountKeys(): Array<string> {
441
+ this.logger.trace("BrowserCacheManager.getAccountKeys called");
442
+ const accountKeys = this.getItem(StaticCacheKeys.ACCOUNT_KEYS);
443
+ if (accountKeys) {
444
+ return JSON.parse(accountKeys);
445
+ }
446
+
447
+ this.logger.verbose(
448
+ "BrowserCacheManager.getAccountKeys - No account keys found"
449
+ );
450
+ return [];
451
+ }
452
+
453
+ /**
454
+ * Add a new account to the key map
455
+ * @param key
456
+ */
457
+ addAccountKeyToMap(key: string): void {
458
+ this.logger.trace("BrowserCacheManager.addAccountKeyToMap called");
459
+ this.logger.tracePii(
460
+ `BrowserCacheManager.addAccountKeyToMap called with key: ${key}`
461
+ );
462
+ const accountKeys = this.getAccountKeys();
463
+ if (accountKeys.indexOf(key) === -1) {
464
+ // Only add key if it does not already exist in the map
465
+ accountKeys.push(key);
466
+ this.setItem(
467
+ StaticCacheKeys.ACCOUNT_KEYS,
468
+ JSON.stringify(accountKeys)
469
+ );
470
+ this.logger.verbose(
471
+ "BrowserCacheManager.addAccountKeyToMap account key added"
472
+ );
473
+ } else {
474
+ this.logger.verbose(
475
+ "BrowserCacheManager.addAccountKeyToMap account key already exists in map"
476
+ );
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Remove an account from the key map
482
+ * @param key
483
+ */
484
+ removeAccountKeyFromMap(key: string): void {
485
+ this.logger.trace("BrowserCacheManager.removeAccountKeyFromMap called");
486
+ this.logger.tracePii(
487
+ `BrowserCacheManager.removeAccountKeyFromMap called with key: ${key}`
488
+ );
489
+ const accountKeys = this.getAccountKeys();
490
+ const removalIndex = accountKeys.indexOf(key);
491
+ if (removalIndex > -1) {
492
+ accountKeys.splice(removalIndex, 1);
493
+ this.setItem(
494
+ StaticCacheKeys.ACCOUNT_KEYS,
495
+ JSON.stringify(accountKeys)
496
+ );
497
+ this.logger.trace(
498
+ "BrowserCacheManager.removeAccountKeyFromMap account key removed"
499
+ );
500
+ } else {
501
+ this.logger.trace(
502
+ "BrowserCacheManager.removeAccountKeyFromMap key not found in existing map"
503
+ );
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Extends inherited removeAccount function to include removal of the account key from the map
509
+ * @param key
510
+ */
511
+ async removeAccount(key: string): Promise<void> {
512
+ void super.removeAccount(key);
513
+ this.removeAccountKeyFromMap(key);
514
+ }
515
+
516
+ /**
517
+ * Removes given idToken from the cache and from the key map
518
+ * @param key
519
+ */
520
+ removeIdToken(key: string): void {
521
+ super.removeIdToken(key);
522
+ this.removeTokenKey(key, CredentialType.ID_TOKEN);
523
+ }
524
+
525
+ /**
526
+ * Removes given accessToken from the cache and from the key map
527
+ * @param key
528
+ */
529
+ async removeAccessToken(key: string): Promise<void> {
530
+ void super.removeAccessToken(key);
531
+ this.removeTokenKey(key, CredentialType.ACCESS_TOKEN);
532
+ }
533
+
534
+ /**
535
+ * Removes given refreshToken from the cache and from the key map
536
+ * @param key
537
+ */
538
+ removeRefreshToken(key: string): void {
539
+ super.removeRefreshToken(key);
540
+ this.removeTokenKey(key, CredentialType.REFRESH_TOKEN);
541
+ }
542
+
543
+ /**
544
+ * Gets the keys for the cached tokens associated with this clientId
545
+ * @returns
546
+ */
547
+ getTokenKeys(): TokenKeys {
548
+ this.logger.trace("BrowserCacheManager.getTokenKeys called");
549
+ const item = this.getItem(
550
+ `${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`
551
+ );
552
+ if (item) {
553
+ const tokenKeys = this.validateAndParseJson(item);
554
+ if (
555
+ tokenKeys &&
556
+ tokenKeys.hasOwnProperty("idToken") &&
557
+ tokenKeys.hasOwnProperty("accessToken") &&
558
+ tokenKeys.hasOwnProperty("refreshToken")
559
+ ) {
560
+ return tokenKeys as TokenKeys;
561
+ } else {
562
+ this.logger.error(
563
+ "BrowserCacheManager.getTokenKeys - Token keys found but in an unknown format. Returning empty key map."
564
+ );
565
+ }
566
+ } else {
567
+ this.logger.verbose(
568
+ "BrowserCacheManager.getTokenKeys - No token keys found"
569
+ );
570
+ }
571
+
572
+ return {
573
+ idToken: [],
574
+ accessToken: [],
575
+ refreshToken: [],
576
+ };
577
+ }
578
+
579
+ /**
580
+ * Adds the given key to the token key map
581
+ * @param key
582
+ * @param type
583
+ */
584
+ addTokenKey(key: string, type: CredentialType): void {
585
+ this.logger.trace("BrowserCacheManager addTokenKey called");
586
+ const tokenKeys = this.getTokenKeys();
587
+
588
+ switch (type) {
589
+ case CredentialType.ID_TOKEN:
590
+ if (tokenKeys.idToken.indexOf(key) === -1) {
591
+ this.logger.info(
592
+ "BrowserCacheManager: addTokenKey - idToken added to map"
593
+ );
594
+ tokenKeys.idToken.push(key);
595
+ }
596
+ break;
597
+ case CredentialType.ACCESS_TOKEN:
598
+ if (tokenKeys.accessToken.indexOf(key) === -1) {
599
+ this.logger.info(
600
+ "BrowserCacheManager: addTokenKey - accessToken added to map"
601
+ );
602
+ tokenKeys.accessToken.push(key);
603
+ }
604
+ break;
605
+ case CredentialType.REFRESH_TOKEN:
606
+ if (tokenKeys.refreshToken.indexOf(key) === -1) {
607
+ this.logger.info(
608
+ "BrowserCacheManager: addTokenKey - refreshToken added to map"
609
+ );
610
+ tokenKeys.refreshToken.push(key);
611
+ }
612
+ break;
613
+ default:
614
+ this.logger.error(
615
+ `BrowserCacheManager:addTokenKey - CredentialType provided invalid. CredentialType: ${type}`
616
+ );
617
+ ClientAuthError.createUnexpectedCredentialTypeError();
618
+ }
619
+
620
+ this.setItem(
621
+ `${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`,
622
+ JSON.stringify(tokenKeys)
623
+ );
624
+ }
625
+
626
+ /**
627
+ * Removes the given key from the token key map
628
+ * @param key
629
+ * @param type
630
+ */
631
+ removeTokenKey(key: string, type: CredentialType): void {
632
+ this.logger.trace("BrowserCacheManager removeTokenKey called");
633
+ const tokenKeys = this.getTokenKeys();
634
+
635
+ switch (type) {
636
+ case CredentialType.ID_TOKEN:
637
+ this.logger.infoPii(
638
+ `BrowserCacheManager: removeTokenKey - attempting to remove idToken with key: ${key} from map`
639
+ );
640
+ const idRemoval = tokenKeys.idToken.indexOf(key);
641
+ if (idRemoval > -1) {
642
+ this.logger.info(
643
+ "BrowserCacheManager: removeTokenKey - idToken removed from map"
644
+ );
645
+ tokenKeys.idToken.splice(idRemoval, 1);
646
+ } else {
647
+ this.logger.info(
648
+ "BrowserCacheManager: removeTokenKey - idToken does not exist in map. Either it was previously removed or it was never added."
649
+ );
650
+ }
651
+ break;
652
+ case CredentialType.ACCESS_TOKEN:
653
+ this.logger.infoPii(
654
+ `BrowserCacheManager: removeTokenKey - attempting to remove accessToken with key: ${key} from map`
655
+ );
656
+ const accessRemoval = tokenKeys.accessToken.indexOf(key);
657
+ if (accessRemoval > -1) {
658
+ this.logger.info(
659
+ "BrowserCacheManager: removeTokenKey - accessToken removed from map"
660
+ );
661
+ tokenKeys.accessToken.splice(accessRemoval, 1);
662
+ } else {
663
+ this.logger.info(
664
+ "BrowserCacheManager: removeTokenKey - accessToken does not exist in map. Either it was previously removed or it was never added."
665
+ );
666
+ }
667
+ break;
668
+ case CredentialType.REFRESH_TOKEN:
669
+ this.logger.infoPii(
670
+ `BrowserCacheManager: removeTokenKey - attempting to remove refreshToken with key: ${key} from map`
671
+ );
672
+ const refreshRemoval = tokenKeys.refreshToken.indexOf(key);
673
+ if (refreshRemoval > -1) {
674
+ this.logger.info(
675
+ "BrowserCacheManager: removeTokenKey - refreshToken removed from map"
676
+ );
677
+ tokenKeys.refreshToken.splice(refreshRemoval, 1);
678
+ } else {
679
+ this.logger.info(
680
+ "BrowserCacheManager: removeTokenKey - refreshToken does not exist in map. Either it was previously removed or it was never added."
681
+ );
682
+ }
683
+ break;
684
+ default:
685
+ this.logger.error(
686
+ `BrowserCacheManager:removeTokenKey - CredentialType provided invalid. CredentialType: ${type}`
687
+ );
688
+ ClientAuthError.createUnexpectedCredentialTypeError();
689
+ }
690
+
691
+ this.setItem(
692
+ `${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`,
693
+ JSON.stringify(tokenKeys)
694
+ );
695
+ }
696
+
697
+ /**
698
+ * generates idToken entity from a string
699
+ * @param idTokenKey
700
+ */
701
+ getIdTokenCredential(idTokenKey: string): IdTokenEntity | null {
702
+ const value = this.getItem(idTokenKey);
703
+ if (!value) {
704
+ this.logger.trace(
705
+ "BrowserCacheManager.getIdTokenCredential: called, no cache hit"
706
+ );
707
+ this.removeTokenKey(idTokenKey, CredentialType.ID_TOKEN);
708
+ return null;
709
+ }
710
+
711
+ const parsedIdToken = this.validateAndParseJson(value);
712
+ if (!parsedIdToken || !IdTokenEntity.isIdTokenEntity(parsedIdToken)) {
713
+ this.logger.trace(
714
+ "BrowserCacheManager.getIdTokenCredential: called, no cache hit"
715
+ );
716
+ this.removeTokenKey(idTokenKey, CredentialType.ID_TOKEN);
717
+ return null;
718
+ }
719
+
720
+ this.logger.trace(
721
+ "BrowserCacheManager.getIdTokenCredential: cache hit"
722
+ );
723
+ return CacheManager.toObject(new IdTokenEntity(), parsedIdToken);
724
+ }
725
+
726
+ /**
727
+ * set IdToken credential to the platform cache
728
+ * @param idToken
729
+ */
730
+ setIdTokenCredential(idToken: IdTokenEntity): void {
731
+ this.logger.trace("BrowserCacheManager.setIdTokenCredential called");
732
+ const idTokenKey = idToken.generateCredentialKey();
733
+
734
+ this.setItem(idTokenKey, JSON.stringify(idToken));
735
+
736
+ this.addTokenKey(idTokenKey, CredentialType.ID_TOKEN);
737
+ }
738
+
739
+ /**
740
+ * generates accessToken entity from a string
741
+ * @param key
742
+ */
743
+ getAccessTokenCredential(accessTokenKey: string): AccessTokenEntity | null {
744
+ const value = this.getItem(accessTokenKey);
745
+ if (!value) {
746
+ this.logger.trace(
747
+ "BrowserCacheManager.getAccessTokenCredential: called, no cache hit"
748
+ );
749
+ this.removeTokenKey(accessTokenKey, CredentialType.ACCESS_TOKEN);
750
+ return null;
751
+ }
752
+ const parsedAccessToken = this.validateAndParseJson(value);
753
+ if (
754
+ !parsedAccessToken ||
755
+ !AccessTokenEntity.isAccessTokenEntity(parsedAccessToken)
756
+ ) {
757
+ this.logger.trace(
758
+ "BrowserCacheManager.getAccessTokenCredential: called, no cache hit"
759
+ );
760
+ this.removeTokenKey(accessTokenKey, CredentialType.ACCESS_TOKEN);
761
+ return null;
762
+ }
763
+
764
+ this.logger.trace(
765
+ "BrowserCacheManager.getAccessTokenCredential: cache hit"
766
+ );
767
+ return CacheManager.toObject(
768
+ new AccessTokenEntity(),
769
+ parsedAccessToken
770
+ );
771
+ }
772
+
773
+ /**
774
+ * set accessToken credential to the platform cache
775
+ * @param accessToken
776
+ */
777
+ setAccessTokenCredential(accessToken: AccessTokenEntity): void {
778
+ this.logger.trace(
779
+ "BrowserCacheManager.setAccessTokenCredential called"
780
+ );
781
+ const accessTokenKey = accessToken.generateCredentialKey();
782
+ this.setItem(accessTokenKey, JSON.stringify(accessToken));
783
+
784
+ this.addTokenKey(accessTokenKey, CredentialType.ACCESS_TOKEN);
785
+ }
786
+
787
+ /**
788
+ * generates refreshToken entity from a string
789
+ * @param refreshTokenKey
790
+ */
791
+ getRefreshTokenCredential(
792
+ refreshTokenKey: string
793
+ ): RefreshTokenEntity | null {
794
+ const value = this.getItem(refreshTokenKey);
795
+ if (!value) {
796
+ this.logger.trace(
797
+ "BrowserCacheManager.getRefreshTokenCredential: called, no cache hit"
798
+ );
799
+ this.removeTokenKey(refreshTokenKey, CredentialType.REFRESH_TOKEN);
800
+ return null;
801
+ }
802
+ const parsedRefreshToken = this.validateAndParseJson(value);
803
+ if (
804
+ !parsedRefreshToken ||
805
+ !RefreshTokenEntity.isRefreshTokenEntity(parsedRefreshToken)
806
+ ) {
807
+ this.logger.trace(
808
+ "BrowserCacheManager.getRefreshTokenCredential: called, no cache hit"
809
+ );
810
+ this.removeTokenKey(refreshTokenKey, CredentialType.REFRESH_TOKEN);
811
+ return null;
812
+ }
813
+
814
+ this.logger.trace(
815
+ "BrowserCacheManager.getRefreshTokenCredential: cache hit"
816
+ );
817
+ return CacheManager.toObject(
818
+ new RefreshTokenEntity(),
819
+ parsedRefreshToken
820
+ );
821
+ }
822
+
823
+ /**
824
+ * set refreshToken credential to the platform cache
825
+ * @param refreshToken
826
+ */
827
+ setRefreshTokenCredential(refreshToken: RefreshTokenEntity): void {
828
+ this.logger.trace(
829
+ "BrowserCacheManager.setRefreshTokenCredential called"
830
+ );
831
+ const refreshTokenKey = refreshToken.generateCredentialKey();
832
+ this.setItem(refreshTokenKey, JSON.stringify(refreshToken));
833
+
834
+ this.addTokenKey(refreshTokenKey, CredentialType.REFRESH_TOKEN);
835
+ }
836
+
837
+ /**
838
+ * fetch appMetadata entity from the platform cache
839
+ * @param appMetadataKey
840
+ */
841
+ getAppMetadata(appMetadataKey: string): AppMetadataEntity | null {
842
+ const value = this.getItem(appMetadataKey);
843
+ if (!value) {
844
+ this.logger.trace(
845
+ "BrowserCacheManager.getAppMetadata: called, no cache hit"
846
+ );
847
+ return null;
848
+ }
849
+
850
+ const parsedMetadata = this.validateAndParseJson(value);
851
+ if (
852
+ !parsedMetadata ||
853
+ !AppMetadataEntity.isAppMetadataEntity(
854
+ appMetadataKey,
855
+ parsedMetadata
856
+ )
857
+ ) {
858
+ this.logger.trace(
859
+ "BrowserCacheManager.getAppMetadata: called, no cache hit"
860
+ );
861
+ return null;
862
+ }
863
+
864
+ this.logger.trace("BrowserCacheManager.getAppMetadata: cache hit");
865
+ return CacheManager.toObject(new AppMetadataEntity(), parsedMetadata);
866
+ }
867
+
868
+ /**
869
+ * set appMetadata entity to the platform cache
870
+ * @param appMetadata
871
+ */
872
+ setAppMetadata(appMetadata: AppMetadataEntity): void {
873
+ this.logger.trace("BrowserCacheManager.setAppMetadata called");
874
+ const appMetadataKey = appMetadata.generateAppMetadataKey();
875
+ this.setItem(appMetadataKey, JSON.stringify(appMetadata));
876
+ }
877
+
878
+ /**
879
+ * fetch server telemetry entity from the platform cache
880
+ * @param serverTelemetryKey
881
+ */
882
+ getServerTelemetry(
883
+ serverTelemetryKey: string
884
+ ): ServerTelemetryEntity | null {
885
+ const value = this.getItem(serverTelemetryKey);
886
+ if (!value) {
887
+ this.logger.trace(
888
+ "BrowserCacheManager.getServerTelemetry: called, no cache hit"
889
+ );
890
+ return null;
891
+ }
892
+ const parsedMetadata = this.validateAndParseJson(value);
893
+ if (
894
+ !parsedMetadata ||
895
+ !ServerTelemetryEntity.isServerTelemetryEntity(
896
+ serverTelemetryKey,
897
+ parsedMetadata
898
+ )
899
+ ) {
900
+ this.logger.trace(
901
+ "BrowserCacheManager.getServerTelemetry: called, no cache hit"
902
+ );
903
+ return null;
904
+ }
905
+
906
+ this.logger.trace("BrowserCacheManager.getServerTelemetry: cache hit");
907
+ return CacheManager.toObject(
908
+ new ServerTelemetryEntity(),
909
+ parsedMetadata
910
+ );
911
+ }
912
+
913
+ /**
914
+ * set server telemetry entity to the platform cache
915
+ * @param serverTelemetryKey
916
+ * @param serverTelemetry
917
+ */
918
+ setServerTelemetry(
919
+ serverTelemetryKey: string,
920
+ serverTelemetry: ServerTelemetryEntity
921
+ ): void {
922
+ this.logger.trace("BrowserCacheManager.setServerTelemetry called");
923
+ this.setItem(serverTelemetryKey, JSON.stringify(serverTelemetry));
924
+ }
925
+
926
+ /**
927
+ *
928
+ */
929
+ getAuthorityMetadata(key: string): AuthorityMetadataEntity | null {
930
+ const value = this.internalStorage.getItem(key);
931
+ if (!value) {
932
+ this.logger.trace(
933
+ "BrowserCacheManager.getAuthorityMetadata: called, no cache hit"
934
+ );
935
+ return null;
936
+ }
937
+ const parsedMetadata = this.validateAndParseJson(value);
938
+ if (
939
+ parsedMetadata &&
940
+ AuthorityMetadataEntity.isAuthorityMetadataEntity(
941
+ key,
942
+ parsedMetadata
943
+ )
944
+ ) {
945
+ this.logger.trace(
946
+ "BrowserCacheManager.getAuthorityMetadata: cache hit"
947
+ );
948
+ return CacheManager.toObject(
949
+ new AuthorityMetadataEntity(),
950
+ parsedMetadata
951
+ );
952
+ }
953
+ return null;
954
+ }
955
+
956
+ /**
957
+ *
958
+ */
959
+ getAuthorityMetadataKeys(): Array<string> {
960
+ const allKeys = this.internalStorage.getKeys();
961
+ return allKeys.filter((key) => {
962
+ return this.isAuthorityMetadata(key);
963
+ });
964
+ }
965
+
966
+ /**
967
+ * Sets wrapper metadata in memory
968
+ * @param wrapperSKU
969
+ * @param wrapperVersion
970
+ */
971
+ setWrapperMetadata(wrapperSKU: string, wrapperVersion: string): void {
972
+ this.internalStorage.setItem(InMemoryCacheKeys.WRAPPER_SKU, wrapperSKU);
973
+ this.internalStorage.setItem(
974
+ InMemoryCacheKeys.WRAPPER_VER,
975
+ wrapperVersion
976
+ );
977
+ }
978
+
979
+ /**
980
+ * Returns wrapper metadata from in-memory storage
981
+ */
982
+ getWrapperMetadata(): [string, string] {
983
+ const sku =
984
+ this.internalStorage.getItem(InMemoryCacheKeys.WRAPPER_SKU) ||
985
+ Constants.EMPTY_STRING;
986
+ const version =
987
+ this.internalStorage.getItem(InMemoryCacheKeys.WRAPPER_VER) ||
988
+ Constants.EMPTY_STRING;
989
+ return [sku, version];
990
+ }
991
+
992
+ /**
993
+ *
994
+ * @param entity
995
+ */
996
+ setAuthorityMetadata(key: string, entity: AuthorityMetadataEntity): void {
997
+ this.logger.trace("BrowserCacheManager.setAuthorityMetadata called");
998
+ this.internalStorage.setItem(key, JSON.stringify(entity));
999
+ }
1000
+
1001
+ /**
1002
+ * Gets the active account
1003
+ */
1004
+ getActiveAccount(): AccountInfo | null {
1005
+ const activeAccountKeyFilters = this.generateCacheKey(
1006
+ PersistentCacheKeys.ACTIVE_ACCOUNT_FILTERS
1007
+ );
1008
+ const activeAccountValueFilters = this.getItem(activeAccountKeyFilters);
1009
+ if (!activeAccountValueFilters) {
1010
+ // if new active account cache type isn't found, it's an old version, so look for that instead
1011
+ this.logger.trace(
1012
+ "BrowserCacheManager.getActiveAccount: No active account filters cache schema found, looking for legacy schema"
1013
+ );
1014
+ const activeAccountKeyLocal = this.generateCacheKey(
1015
+ PersistentCacheKeys.ACTIVE_ACCOUNT
1016
+ );
1017
+ const activeAccountValueLocal = this.getItem(activeAccountKeyLocal);
1018
+ if (!activeAccountValueLocal) {
1019
+ this.logger.trace(
1020
+ "BrowserCacheManager.getActiveAccount: No active account found"
1021
+ );
1022
+ return null;
1023
+ }
1024
+ const activeAccount =
1025
+ this.getAccountInfoByFilter({
1026
+ localAccountId: activeAccountValueLocal,
1027
+ })[0] || null;
1028
+ if (activeAccount) {
1029
+ this.logger.trace(
1030
+ "BrowserCacheManager.getActiveAccount: Legacy active account cache schema found"
1031
+ );
1032
+ this.logger.trace(
1033
+ "BrowserCacheManager.getActiveAccount: Adding active account filters cache schema"
1034
+ );
1035
+ this.setActiveAccount(activeAccount);
1036
+ return activeAccount;
1037
+ }
1038
+ return null;
1039
+ }
1040
+ const activeAccountValueObj = this.validateAndParseJson(
1041
+ activeAccountValueFilters
1042
+ ) as AccountInfo;
1043
+ if (activeAccountValueObj) {
1044
+ this.logger.trace(
1045
+ "BrowserCacheManager.getActiveAccount: Active account filters schema found"
1046
+ );
1047
+ return (
1048
+ this.getAccountInfoByFilter({
1049
+ homeAccountId: activeAccountValueObj.homeAccountId,
1050
+ localAccountId: activeAccountValueObj.localAccountId,
1051
+ })[0] || null
1052
+ );
1053
+ }
1054
+ this.logger.trace(
1055
+ "BrowserCacheManager.getActiveAccount: No active account found"
1056
+ );
1057
+ return null;
1058
+ }
1059
+
1060
+ /**
1061
+ * Sets the active account's localAccountId in cache
1062
+ * @param account
1063
+ */
1064
+ setActiveAccount(account: AccountInfo | null): void {
1065
+ const activeAccountKey = this.generateCacheKey(
1066
+ PersistentCacheKeys.ACTIVE_ACCOUNT_FILTERS
1067
+ );
1068
+ const activeAccountKeyLocal = this.generateCacheKey(
1069
+ PersistentCacheKeys.ACTIVE_ACCOUNT
1070
+ );
1071
+ if (account) {
1072
+ this.logger.verbose("setActiveAccount: Active account set");
1073
+ const activeAccountValue: ActiveAccountFilters = {
1074
+ homeAccountId: account.homeAccountId,
1075
+ localAccountId: account.localAccountId,
1076
+ };
1077
+ this.browserStorage.setItem(
1078
+ activeAccountKey,
1079
+ JSON.stringify(activeAccountValue)
1080
+ );
1081
+ this.browserStorage.setItem(
1082
+ activeAccountKeyLocal,
1083
+ account.localAccountId
1084
+ );
1085
+ } else {
1086
+ this.logger.verbose(
1087
+ "setActiveAccount: No account passed, active account not set"
1088
+ );
1089
+ this.browserStorage.removeItem(activeAccountKey);
1090
+ this.browserStorage.removeItem(activeAccountKeyLocal);
1091
+ }
1092
+ }
1093
+
1094
+ /**
1095
+ * Gets a list of accounts that match all of the filters provided
1096
+ * @param account
1097
+ */
1098
+ getAccountInfoByFilter(
1099
+ accountFilter: Partial<Omit<AccountInfo, "idTokenClaims" | "name">>
1100
+ ): AccountInfo[] {
1101
+ const allAccounts = this.getAllAccounts();
1102
+ this.logger.trace(
1103
+ `BrowserCacheManager.getAccountInfoByFilter: total ${allAccounts.length} accounts found`
1104
+ );
1105
+
1106
+ return allAccounts.filter((accountObj) => {
1107
+ if (
1108
+ accountFilter.username &&
1109
+ accountFilter.username.toLowerCase() !==
1110
+ accountObj.username.toLowerCase()
1111
+ ) {
1112
+ return false;
1113
+ }
1114
+
1115
+ if (
1116
+ accountFilter.homeAccountId &&
1117
+ accountFilter.homeAccountId !== accountObj.homeAccountId
1118
+ ) {
1119
+ return false;
1120
+ }
1121
+
1122
+ if (
1123
+ accountFilter.localAccountId &&
1124
+ accountFilter.localAccountId !== accountObj.localAccountId
1125
+ ) {
1126
+ return false;
1127
+ }
1128
+
1129
+ if (
1130
+ accountFilter.tenantId &&
1131
+ accountFilter.tenantId !== accountObj.tenantId
1132
+ ) {
1133
+ return false;
1134
+ }
1135
+
1136
+ if (
1137
+ accountFilter.environment &&
1138
+ accountFilter.environment !== accountObj.environment
1139
+ ) {
1140
+ return false;
1141
+ }
1142
+
1143
+ return true;
1144
+ });
1145
+ }
1146
+
1147
+ /**
1148
+ * Checks the cache for accounts matching loginHint or SID
1149
+ * @param loginHint
1150
+ * @param sid
1151
+ */
1152
+ getAccountInfoByHints(
1153
+ loginHint?: string,
1154
+ sid?: string
1155
+ ): AccountInfo | null {
1156
+ const matchingAccounts = this.getAllAccounts().filter((accountInfo) => {
1157
+ if (sid) {
1158
+ const accountSid =
1159
+ accountInfo.idTokenClaims &&
1160
+ accountInfo.idTokenClaims["sid"];
1161
+ return sid === accountSid;
1162
+ }
1163
+
1164
+ if (loginHint) {
1165
+ return loginHint === accountInfo.username;
1166
+ }
1167
+
1168
+ return false;
1169
+ });
1170
+
1171
+ if (matchingAccounts.length === 1) {
1172
+ return matchingAccounts[0];
1173
+ } else if (matchingAccounts.length > 1) {
1174
+ throw ClientAuthError.createMultipleMatchingAccountsInCacheError();
1175
+ }
1176
+
1177
+ return null;
1178
+ }
1179
+
1180
+ /**
1181
+ * fetch throttling entity from the platform cache
1182
+ * @param throttlingCacheKey
1183
+ */
1184
+ getThrottlingCache(throttlingCacheKey: string): ThrottlingEntity | null {
1185
+ const value = this.getItem(throttlingCacheKey);
1186
+ if (!value) {
1187
+ this.logger.trace(
1188
+ "BrowserCacheManager.getThrottlingCache: called, no cache hit"
1189
+ );
1190
+ return null;
1191
+ }
1192
+
1193
+ const parsedThrottlingCache = this.validateAndParseJson(value);
1194
+ if (
1195
+ !parsedThrottlingCache ||
1196
+ !ThrottlingEntity.isThrottlingEntity(
1197
+ throttlingCacheKey,
1198
+ parsedThrottlingCache
1199
+ )
1200
+ ) {
1201
+ this.logger.trace(
1202
+ "BrowserCacheManager.getThrottlingCache: called, no cache hit"
1203
+ );
1204
+ return null;
1205
+ }
1206
+
1207
+ this.logger.trace("BrowserCacheManager.getThrottlingCache: cache hit");
1208
+ return CacheManager.toObject(
1209
+ new ThrottlingEntity(),
1210
+ parsedThrottlingCache
1211
+ );
1212
+ }
1213
+
1214
+ /**
1215
+ * set throttling entity to the platform cache
1216
+ * @param throttlingCacheKey
1217
+ * @param throttlingCache
1218
+ */
1219
+ setThrottlingCache(
1220
+ throttlingCacheKey: string,
1221
+ throttlingCache: ThrottlingEntity
1222
+ ): void {
1223
+ this.logger.trace("BrowserCacheManager.setThrottlingCache called");
1224
+ this.setItem(throttlingCacheKey, JSON.stringify(throttlingCache));
1225
+ }
1226
+
1227
+ /**
1228
+ * Gets cache item with given key.
1229
+ * Will retrieve from cookies if storeAuthStateInCookie is set to true.
1230
+ * @param key
1231
+ */
1232
+ getTemporaryCache(cacheKey: string, generateKey?: boolean): string | null {
1233
+ const key = generateKey ? this.generateCacheKey(cacheKey) : cacheKey;
1234
+ if (this.cacheConfig.storeAuthStateInCookie) {
1235
+ const itemCookie = this.getItemCookie(key);
1236
+ if (itemCookie) {
1237
+ this.logger.trace(
1238
+ "BrowserCacheManager.getTemporaryCache: storeAuthStateInCookies set to true, retrieving from cookies"
1239
+ );
1240
+ return itemCookie;
1241
+ }
1242
+ }
1243
+
1244
+ const value = this.temporaryCacheStorage.getItem(key);
1245
+ if (!value) {
1246
+ // If temp cache item not found in session/memory, check local storage for items set by old versions
1247
+ if (
1248
+ this.cacheConfig.cacheLocation ===
1249
+ BrowserCacheLocation.LocalStorage
1250
+ ) {
1251
+ const item = this.browserStorage.getItem(key);
1252
+ if (item) {
1253
+ this.logger.trace(
1254
+ "BrowserCacheManager.getTemporaryCache: Temporary cache item found in local storage"
1255
+ );
1256
+ return item;
1257
+ }
1258
+ }
1259
+ this.logger.trace(
1260
+ "BrowserCacheManager.getTemporaryCache: No cache item found in local storage"
1261
+ );
1262
+ return null;
1263
+ }
1264
+ this.logger.trace(
1265
+ "BrowserCacheManager.getTemporaryCache: Temporary cache item returned"
1266
+ );
1267
+ return value;
1268
+ }
1269
+
1270
+ /**
1271
+ * Sets the cache item with the key and value given.
1272
+ * Stores in cookie if storeAuthStateInCookie is set to true.
1273
+ * This can cause cookie overflow if used incorrectly.
1274
+ * @param key
1275
+ * @param value
1276
+ */
1277
+ setTemporaryCache(
1278
+ cacheKey: string,
1279
+ value: string,
1280
+ generateKey?: boolean
1281
+ ): void {
1282
+ const key = generateKey ? this.generateCacheKey(cacheKey) : cacheKey;
1283
+
1284
+ this.temporaryCacheStorage.setItem(key, value);
1285
+ if (this.cacheConfig.storeAuthStateInCookie) {
1286
+ this.logger.trace(
1287
+ "BrowserCacheManager.setTemporaryCache: storeAuthStateInCookie set to true, setting item cookie"
1288
+ );
1289
+ this.setItemCookie(key, value);
1290
+ }
1291
+ }
1292
+
1293
+ /**
1294
+ * Removes the cache item with the given key.
1295
+ * Will also clear the cookie item if storeAuthStateInCookie is set to true.
1296
+ * @param key
1297
+ */
1298
+ removeItem(key: string): void {
1299
+ this.browserStorage.removeItem(key);
1300
+ this.temporaryCacheStorage.removeItem(key);
1301
+ if (this.cacheConfig.storeAuthStateInCookie) {
1302
+ this.logger.trace(
1303
+ "BrowserCacheManager.removeItem: storeAuthStateInCookie is true, clearing item cookie"
1304
+ );
1305
+ this.clearItemCookie(key);
1306
+ }
1307
+ }
1308
+
1309
+ /**
1310
+ * Checks whether key is in cache.
1311
+ * @param key
1312
+ */
1313
+ containsKey(key: string): boolean {
1314
+ return (
1315
+ this.browserStorage.containsKey(key) ||
1316
+ this.temporaryCacheStorage.containsKey(key)
1317
+ );
1318
+ }
1319
+
1320
+ /**
1321
+ * Gets all keys in window.
1322
+ */
1323
+ getKeys(): string[] {
1324
+ return [
1325
+ ...this.browserStorage.getKeys(),
1326
+ ...this.temporaryCacheStorage.getKeys(),
1327
+ ];
1328
+ }
1329
+
1330
+ /**
1331
+ * Clears all cache entries created by MSAL.
1332
+ */
1333
+ async clear(): Promise<void> {
1334
+ // Removes all accounts and their credentials
1335
+ await this.removeAllAccounts();
1336
+ this.removeAppMetadata();
1337
+
1338
+ // Removes all remaining MSAL cache items
1339
+ this.getKeys().forEach((cacheKey: string) => {
1340
+ // Check if key contains msal prefix; For now, we are clearing all the cache items created by MSAL.js
1341
+ if (
1342
+ (this.browserStorage.containsKey(cacheKey) ||
1343
+ this.temporaryCacheStorage.containsKey(cacheKey)) &&
1344
+ (cacheKey.indexOf(Constants.CACHE_PREFIX) !== -1 ||
1345
+ cacheKey.indexOf(this.clientId) !== -1)
1346
+ ) {
1347
+ this.removeItem(cacheKey);
1348
+ }
1349
+ });
1350
+
1351
+ this.internalStorage.clear();
1352
+ }
1353
+
1354
+ /**
1355
+ * Clears all access tokes that have claims prior to saving the current one
1356
+ * @param credential
1357
+ * @returns
1358
+ */
1359
+ async clearTokensAndKeysWithClaims(): Promise<void> {
1360
+ const tokenKeys = this.getTokenKeys();
1361
+
1362
+ const removedAccessTokens: Array<Promise<void>> = [];
1363
+ tokenKeys.accessToken.forEach((key: string) => {
1364
+ // if the access token has claims in its key, remove the token key and the token
1365
+ const credential = this.getAccessTokenCredential(key);
1366
+ if (
1367
+ credential?.requestedClaimsHash &&
1368
+ key.includes(credential.requestedClaimsHash.toLowerCase())
1369
+ ) {
1370
+ removedAccessTokens.push(this.removeAccessToken(key));
1371
+ }
1372
+ });
1373
+ await Promise.all(removedAccessTokens);
1374
+
1375
+ // warn if any access tokens are removed
1376
+ if (removedAccessTokens.length > 0) {
1377
+ this.logger.warning(
1378
+ `${removedAccessTokens.length} access tokens with claims in the cache keys have been removed from the cache.`
1379
+ );
1380
+ }
1381
+ }
1382
+
1383
+ /**
1384
+ * Add value to cookies
1385
+ * @param cookieName
1386
+ * @param cookieValue
1387
+ * @param expires
1388
+ */
1389
+ setItemCookie(
1390
+ cookieName: string,
1391
+ cookieValue: string,
1392
+ expires?: number
1393
+ ): void {
1394
+ let cookieStr = `${encodeURIComponent(cookieName)}=${encodeURIComponent(
1395
+ cookieValue
1396
+ )};path=/;SameSite=Lax;`;
1397
+ if (expires) {
1398
+ const expireTime = this.getCookieExpirationTime(expires);
1399
+ cookieStr += `expires=${expireTime};`;
1400
+ }
1401
+
1402
+ if (this.cacheConfig.secureCookies) {
1403
+ cookieStr += "Secure;";
1404
+ }
1405
+
1406
+ document.cookie = cookieStr;
1407
+ }
1408
+
1409
+ /**
1410
+ * Get one item by key from cookies
1411
+ * @param cookieName
1412
+ */
1413
+ getItemCookie(cookieName: string): string {
1414
+ const name = `${encodeURIComponent(cookieName)}=`;
1415
+ const cookieList = document.cookie.split(";");
1416
+ for (let i: number = 0; i < cookieList.length; i++) {
1417
+ let cookie = cookieList[i];
1418
+ while (cookie.charAt(0) === " ") {
1419
+ cookie = cookie.substring(1);
1420
+ }
1421
+ if (cookie.indexOf(name) === 0) {
1422
+ return decodeURIComponent(
1423
+ cookie.substring(name.length, cookie.length)
1424
+ );
1425
+ }
1426
+ }
1427
+ return Constants.EMPTY_STRING;
1428
+ }
1429
+
1430
+ /**
1431
+ * Clear all msal-related cookies currently set in the browser. Should only be used to clear temporary cache items.
1432
+ */
1433
+ clearMsalCookies(): void {
1434
+ const cookiePrefix = `${Constants.CACHE_PREFIX}.${this.clientId}`;
1435
+ const cookieList = document.cookie.split(";");
1436
+ cookieList.forEach((cookie: string): void => {
1437
+ while (cookie.charAt(0) === " ") {
1438
+ // eslint-disable-next-line no-param-reassign
1439
+ cookie = cookie.substring(1);
1440
+ }
1441
+ if (cookie.indexOf(cookiePrefix) === 0) {
1442
+ const cookieKey = cookie.split("=")[0];
1443
+ this.clearItemCookie(cookieKey);
1444
+ }
1445
+ });
1446
+ }
1447
+
1448
+ /**
1449
+ * Clear an item in the cookies by key
1450
+ * @param cookieName
1451
+ */
1452
+ clearItemCookie(cookieName: string): void {
1453
+ this.setItemCookie(cookieName, Constants.EMPTY_STRING, -1);
1454
+ }
1455
+
1456
+ /**
1457
+ * Get cookie expiration time
1458
+ * @param cookieLifeDays
1459
+ */
1460
+ getCookieExpirationTime(cookieLifeDays: number): string {
1461
+ const today = new Date();
1462
+ const expr = new Date(
1463
+ today.getTime() + cookieLifeDays * this.COOKIE_LIFE_MULTIPLIER
1464
+ );
1465
+ return expr.toUTCString();
1466
+ }
1467
+
1468
+ /**
1469
+ * Gets the cache object referenced by the browser
1470
+ */
1471
+ getCache(): object {
1472
+ return this.browserStorage;
1473
+ }
1474
+
1475
+ /**
1476
+ * interface compat, we cannot overwrite browser cache; Functionality is supported by individual entities in browser
1477
+ */
1478
+ setCache(): void {
1479
+ // sets nothing
1480
+ }
1481
+
1482
+ /**
1483
+ * Prepend msal.<client-id> to each key; Skip for any JSON object as Key (defined schemas do not need the key appended: AccessToken Keys or the upcoming schema)
1484
+ * @param key
1485
+ * @param addInstanceId
1486
+ */
1487
+ generateCacheKey(key: string): string {
1488
+ const generatedKey = this.validateAndParseJson(key);
1489
+ if (!generatedKey) {
1490
+ if (
1491
+ StringUtils.startsWith(key, Constants.CACHE_PREFIX) ||
1492
+ StringUtils.startsWith(key, PersistentCacheKeys.ADAL_ID_TOKEN)
1493
+ ) {
1494
+ return key;
1495
+ }
1496
+ return `${Constants.CACHE_PREFIX}.${this.clientId}.${key}`;
1497
+ }
1498
+
1499
+ return JSON.stringify(key);
1500
+ }
1501
+
1502
+ /**
1503
+ * Create authorityKey to cache authority
1504
+ * @param state
1505
+ */
1506
+ generateAuthorityKey(stateString: string): string {
1507
+ const {
1508
+ libraryState: { id: stateId },
1509
+ } = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString);
1510
+
1511
+ return this.generateCacheKey(
1512
+ `${TemporaryCacheKeys.AUTHORITY}.${stateId}`
1513
+ );
1514
+ }
1515
+
1516
+ /**
1517
+ * Create Nonce key to cache nonce
1518
+ * @param state
1519
+ */
1520
+ generateNonceKey(stateString: string): string {
1521
+ const {
1522
+ libraryState: { id: stateId },
1523
+ } = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString);
1524
+
1525
+ return this.generateCacheKey(
1526
+ `${TemporaryCacheKeys.NONCE_IDTOKEN}.${stateId}`
1527
+ );
1528
+ }
1529
+
1530
+ /**
1531
+ * Creates full cache key for the request state
1532
+ * @param stateString State string for the request
1533
+ */
1534
+ generateStateKey(stateString: string): string {
1535
+ // Use the library state id to key temp storage for uniqueness for multiple concurrent requests
1536
+ const {
1537
+ libraryState: { id: stateId },
1538
+ } = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString);
1539
+ return this.generateCacheKey(
1540
+ `${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`
1541
+ );
1542
+ }
1543
+
1544
+ /**
1545
+ * Gets the cached authority based on the cached state. Returns empty if no cached state found.
1546
+ */
1547
+ getCachedAuthority(cachedState: string): string | null {
1548
+ const stateCacheKey = this.generateStateKey(cachedState);
1549
+ const state = this.getTemporaryCache(stateCacheKey);
1550
+ if (!state) {
1551
+ return null;
1552
+ }
1553
+
1554
+ const authorityCacheKey = this.generateAuthorityKey(state);
1555
+ return this.getTemporaryCache(authorityCacheKey);
1556
+ }
1557
+
1558
+ /**
1559
+ * Updates account, authority, and state in cache
1560
+ * @param serverAuthenticationRequest
1561
+ * @param account
1562
+ */
1563
+ updateCacheEntries(
1564
+ state: string,
1565
+ nonce: string,
1566
+ authorityInstance: string,
1567
+ loginHint: string,
1568
+ account: AccountInfo | null
1569
+ ): void {
1570
+ this.logger.trace("BrowserCacheManager.updateCacheEntries called");
1571
+ // Cache the request state
1572
+ const stateCacheKey = this.generateStateKey(state);
1573
+ this.setTemporaryCache(stateCacheKey, state, false);
1574
+
1575
+ // Cache the nonce
1576
+ const nonceCacheKey = this.generateNonceKey(state);
1577
+ this.setTemporaryCache(nonceCacheKey, nonce, false);
1578
+
1579
+ // Cache authorityKey
1580
+ const authorityCacheKey = this.generateAuthorityKey(state);
1581
+ this.setTemporaryCache(authorityCacheKey, authorityInstance, false);
1582
+
1583
+ if (account) {
1584
+ const ccsCredential: CcsCredential = {
1585
+ credential: account.homeAccountId,
1586
+ type: CcsCredentialType.HOME_ACCOUNT_ID,
1587
+ };
1588
+ this.setTemporaryCache(
1589
+ TemporaryCacheKeys.CCS_CREDENTIAL,
1590
+ JSON.stringify(ccsCredential),
1591
+ true
1592
+ );
1593
+ } else if (loginHint) {
1594
+ const ccsCredential: CcsCredential = {
1595
+ credential: loginHint,
1596
+ type: CcsCredentialType.UPN,
1597
+ };
1598
+ this.setTemporaryCache(
1599
+ TemporaryCacheKeys.CCS_CREDENTIAL,
1600
+ JSON.stringify(ccsCredential),
1601
+ true
1602
+ );
1603
+ }
1604
+ }
1605
+
1606
+ /**
1607
+ * Reset all temporary cache items
1608
+ * @param state
1609
+ */
1610
+ resetRequestCache(state: string): void {
1611
+ this.logger.trace("BrowserCacheManager.resetRequestCache called");
1612
+ // check state and remove associated cache items
1613
+ if (state) {
1614
+ this.getKeys().forEach((key) => {
1615
+ if (key.indexOf(state) !== -1) {
1616
+ this.removeItem(key);
1617
+ }
1618
+ });
1619
+
1620
+ // delete generic interactive request parameters
1621
+ this.removeItem(this.generateStateKey(state));
1622
+ this.removeItem(this.generateNonceKey(state));
1623
+ this.removeItem(this.generateAuthorityKey(state));
1624
+ }
1625
+ this.removeItem(
1626
+ this.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS)
1627
+ );
1628
+ this.removeItem(this.generateCacheKey(TemporaryCacheKeys.ORIGIN_URI));
1629
+ this.removeItem(this.generateCacheKey(TemporaryCacheKeys.URL_HASH));
1630
+ this.removeItem(
1631
+ this.generateCacheKey(TemporaryCacheKeys.CORRELATION_ID)
1632
+ );
1633
+ this.removeItem(
1634
+ this.generateCacheKey(TemporaryCacheKeys.CCS_CREDENTIAL)
1635
+ );
1636
+ this.removeItem(
1637
+ this.generateCacheKey(TemporaryCacheKeys.NATIVE_REQUEST)
1638
+ );
1639
+ this.setInteractionInProgress(false);
1640
+ }
1641
+
1642
+ /**
1643
+ * Removes temporary cache for the provided state
1644
+ * @param stateString
1645
+ */
1646
+ cleanRequestByState(stateString: string): void {
1647
+ this.logger.trace("BrowserCacheManager.cleanRequestByState called");
1648
+ // Interaction is completed - remove interaction status.
1649
+ if (stateString) {
1650
+ const stateKey = this.generateStateKey(stateString);
1651
+ const cachedState = this.temporaryCacheStorage.getItem(stateKey);
1652
+ this.logger.infoPii(
1653
+ `BrowserCacheManager.cleanRequestByState: Removing temporary cache items for state: ${cachedState}`
1654
+ );
1655
+ this.resetRequestCache(cachedState || Constants.EMPTY_STRING);
1656
+ }
1657
+ this.clearMsalCookies();
1658
+ }
1659
+
1660
+ /**
1661
+ * Looks in temporary cache for any state values with the provided interactionType and removes all temporary cache items for that state
1662
+ * Used in scenarios where temp cache needs to be cleaned but state is not known, such as clicking browser back button.
1663
+ * @param interactionType
1664
+ */
1665
+ cleanRequestByInteractionType(interactionType: InteractionType): void {
1666
+ this.logger.trace(
1667
+ "BrowserCacheManager.cleanRequestByInteractionType called"
1668
+ );
1669
+ // Loop through all keys to find state key
1670
+ this.getKeys().forEach((key) => {
1671
+ // If this key is not the state key, move on
1672
+ if (key.indexOf(TemporaryCacheKeys.REQUEST_STATE) === -1) {
1673
+ return;
1674
+ }
1675
+
1676
+ // Retrieve state value, return if not a valid value
1677
+ const stateValue = this.temporaryCacheStorage.getItem(key);
1678
+ if (!stateValue) {
1679
+ return;
1680
+ }
1681
+ // Extract state and ensure it matches given InteractionType, then clean request cache
1682
+ const parsedState = BrowserProtocolUtils.extractBrowserRequestState(
1683
+ this.cryptoImpl,
1684
+ stateValue
1685
+ );
1686
+ if (
1687
+ parsedState &&
1688
+ parsedState.interactionType === interactionType
1689
+ ) {
1690
+ this.logger.infoPii(
1691
+ `BrowserCacheManager.cleanRequestByInteractionType: Removing temporary cache items for state: ${stateValue}`
1692
+ );
1693
+ this.resetRequestCache(stateValue);
1694
+ }
1695
+ });
1696
+ this.clearMsalCookies();
1697
+ this.setInteractionInProgress(false);
1698
+ }
1699
+
1700
+ cacheCodeRequest(authCodeRequest: CommonAuthorizationCodeRequest): void {
1701
+ this.logger.trace("BrowserCacheManager.cacheCodeRequest called");
1702
+
1703
+ const encodedValue = base64Encode(JSON.stringify(authCodeRequest));
1704
+ this.setTemporaryCache(
1705
+ TemporaryCacheKeys.REQUEST_PARAMS,
1706
+ encodedValue,
1707
+ true
1708
+ );
1709
+ }
1710
+
1711
+ /**
1712
+ * Gets the token exchange parameters from the cache. Throws an error if nothing is found.
1713
+ */
1714
+ getCachedRequest(state: string): CommonAuthorizationCodeRequest {
1715
+ this.logger.trace("BrowserCacheManager.getCachedRequest called");
1716
+ // Get token request from cache and parse as TokenExchangeParameters.
1717
+ const encodedTokenRequest = this.getTemporaryCache(
1718
+ TemporaryCacheKeys.REQUEST_PARAMS,
1719
+ true
1720
+ );
1721
+ if (!encodedTokenRequest) {
1722
+ throw createBrowserAuthError(
1723
+ BrowserAuthErrorCodes.noTokenRequestCacheError
1724
+ );
1725
+ }
1726
+
1727
+ let parsedRequest: CommonAuthorizationCodeRequest;
1728
+ try {
1729
+ parsedRequest = JSON.parse(base64Decode(encodedTokenRequest));
1730
+ } catch (e) {
1731
+ this.logger.errorPii(`Attempted to parse: ${encodedTokenRequest}`);
1732
+ this.logger.error(
1733
+ `Parsing cached token request threw with error: ${e}`
1734
+ );
1735
+ throw createBrowserAuthError(
1736
+ BrowserAuthErrorCodes.unableToParseTokenRequestCacheError
1737
+ );
1738
+ }
1739
+ this.removeItem(
1740
+ this.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS)
1741
+ );
1742
+
1743
+ // Get cached authority and use if no authority is cached with request.
1744
+ if (!parsedRequest.authority) {
1745
+ const authorityCacheKey: string = this.generateAuthorityKey(state);
1746
+ const cachedAuthority = this.getTemporaryCache(authorityCacheKey);
1747
+ if (!cachedAuthority) {
1748
+ throw createBrowserAuthError(
1749
+ BrowserAuthErrorCodes.noCachedAuthorityError
1750
+ );
1751
+ }
1752
+ parsedRequest.authority = cachedAuthority;
1753
+ }
1754
+
1755
+ return parsedRequest;
1756
+ }
1757
+
1758
+ /**
1759
+ * Gets cached native request for redirect flows
1760
+ */
1761
+ getCachedNativeRequest(): NativeTokenRequest | null {
1762
+ this.logger.trace("BrowserCacheManager.getCachedNativeRequest called");
1763
+ const cachedRequest = this.getTemporaryCache(
1764
+ TemporaryCacheKeys.NATIVE_REQUEST,
1765
+ true
1766
+ );
1767
+ if (!cachedRequest) {
1768
+ this.logger.trace(
1769
+ "BrowserCacheManager.getCachedNativeRequest: No cached native request found"
1770
+ );
1771
+ return null;
1772
+ }
1773
+
1774
+ const parsedRequest = this.validateAndParseJson(
1775
+ cachedRequest
1776
+ ) as NativeTokenRequest;
1777
+ if (!parsedRequest) {
1778
+ this.logger.error(
1779
+ "BrowserCacheManager.getCachedNativeRequest: Unable to parse native request"
1780
+ );
1781
+ return null;
1782
+ }
1783
+
1784
+ return parsedRequest;
1785
+ }
1786
+
1787
+ isInteractionInProgress(matchClientId?: boolean): boolean {
1788
+ const clientId = this.getInteractionInProgress();
1789
+
1790
+ if (matchClientId) {
1791
+ return clientId === this.clientId;
1792
+ } else {
1793
+ return !!clientId;
1794
+ }
1795
+ }
1796
+
1797
+ getInteractionInProgress(): string | null {
1798
+ const key = `${Constants.CACHE_PREFIX}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`;
1799
+ return this.getTemporaryCache(key, false);
1800
+ }
1801
+
1802
+ setInteractionInProgress(inProgress: boolean): void {
1803
+ // Ensure we don't overwrite interaction in progress for a different clientId
1804
+ const key = `${Constants.CACHE_PREFIX}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`;
1805
+ if (inProgress) {
1806
+ if (this.getInteractionInProgress()) {
1807
+ throw createBrowserAuthError(
1808
+ BrowserAuthErrorCodes.interactionInProgress
1809
+ );
1810
+ } else {
1811
+ // No interaction is in progress
1812
+ this.setTemporaryCache(key, this.clientId, false);
1813
+ }
1814
+ } else if (
1815
+ !inProgress &&
1816
+ this.getInteractionInProgress() === this.clientId
1817
+ ) {
1818
+ this.removeItem(key);
1819
+ }
1820
+ }
1821
+
1822
+ /**
1823
+ * Returns username retrieved from ADAL or MSAL v1 idToken
1824
+ * @deprecated
1825
+ */
1826
+ getLegacyLoginHint(): string | null {
1827
+ // Only check for adal/msal token if no SSO params are being used
1828
+ const adalIdTokenString = this.getTemporaryCache(
1829
+ PersistentCacheKeys.ADAL_ID_TOKEN
1830
+ );
1831
+ if (adalIdTokenString) {
1832
+ this.browserStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN);
1833
+ this.logger.verbose("Cached ADAL id token retrieved.");
1834
+ }
1835
+
1836
+ // Check for cached MSAL v1 id token
1837
+ const msalIdTokenString = this.getTemporaryCache(
1838
+ PersistentCacheKeys.ID_TOKEN,
1839
+ true
1840
+ );
1841
+ if (msalIdTokenString) {
1842
+ this.removeItem(
1843
+ this.generateCacheKey(PersistentCacheKeys.ID_TOKEN)
1844
+ );
1845
+ this.logger.verbose("Cached MSAL.js v1 id token retrieved");
1846
+ }
1847
+
1848
+ const cachedIdTokenString = msalIdTokenString || adalIdTokenString;
1849
+ if (cachedIdTokenString) {
1850
+ const idTokenClaims = AuthToken.extractTokenClaims(
1851
+ cachedIdTokenString,
1852
+ base64Decode
1853
+ );
1854
+ if (idTokenClaims.preferred_username) {
1855
+ this.logger.verbose(
1856
+ "No SSO params used and ADAL/MSAL v1 token retrieved, setting ADAL/MSAL v1 preferred_username as loginHint"
1857
+ );
1858
+ return idTokenClaims.preferred_username;
1859
+ } else if (idTokenClaims.upn) {
1860
+ this.logger.verbose(
1861
+ "No SSO params used and ADAL/MSAL v1 token retrieved, setting ADAL/MSAL v1 upn as loginHint"
1862
+ );
1863
+ return idTokenClaims.upn;
1864
+ } else {
1865
+ this.logger.verbose(
1866
+ "No SSO params used and ADAL/MSAL v1 token retrieved, however, no account hint claim found. Enable preferred_username or upn id token claim to get SSO."
1867
+ );
1868
+ }
1869
+ }
1870
+
1871
+ return null;
1872
+ }
1873
+
1874
+ /**
1875
+ * Updates a credential's cache key if the current cache key is outdated
1876
+ */
1877
+ updateCredentialCacheKey(
1878
+ currentCacheKey: string,
1879
+ credential: ValidCredentialType
1880
+ ): string {
1881
+ const updatedCacheKey = credential.generateCredentialKey();
1882
+
1883
+ if (currentCacheKey !== updatedCacheKey) {
1884
+ const cacheItem = this.getItem(currentCacheKey);
1885
+ if (cacheItem) {
1886
+ this.removeItem(currentCacheKey);
1887
+ this.setItem(updatedCacheKey, cacheItem);
1888
+ this.logger.verbose(
1889
+ `Updated an outdated ${credential.credentialType} cache key`
1890
+ );
1891
+ return updatedCacheKey;
1892
+ } else {
1893
+ this.logger.error(
1894
+ `Attempted to update an outdated ${credential.credentialType} cache key but no item matching the outdated key was found in storage`
1895
+ );
1896
+ }
1897
+ }
1898
+
1899
+ return currentCacheKey;
1900
+ }
1901
+
1902
+ /**
1903
+ * Returns application id as redirect context during AcquireTokenRedirect flow.
1904
+ */
1905
+ getRedirectRequestContext(): string | null {
1906
+ return this.getTemporaryCache(
1907
+ TemporaryCacheKeys.REDIRECT_CONTEXT,
1908
+ true
1909
+ );
1910
+ }
1911
+
1912
+ /**
1913
+ * Sets application id as the redirect context during AcquireTokenRedirect flow.
1914
+ * @param value
1915
+ */
1916
+ setRedirectRequestContext(value: string): void {
1917
+ this.setTemporaryCache(
1918
+ TemporaryCacheKeys.REDIRECT_CONTEXT,
1919
+ value,
1920
+ true
1921
+ );
1922
+ }
1923
+
1924
+ /**
1925
+ * Builds credential entities from AuthenticationResult object and saves the resulting credentials to the cache
1926
+ * @param result
1927
+ * @param request
1928
+ */
1929
+ async hydrateCache(
1930
+ result: AuthenticationResult,
1931
+ request:
1932
+ | SilentRequest
1933
+ | SsoSilentRequest
1934
+ | RedirectRequest
1935
+ | PopupRequest
1936
+ ): Promise<void> {
1937
+ const idTokenEntity = IdTokenEntity.createIdTokenEntity(
1938
+ result.account?.homeAccountId,
1939
+ result.account?.environment,
1940
+ result.idToken,
1941
+ this.clientId,
1942
+ result.tenantId
1943
+ );
1944
+
1945
+ let claimsHash;
1946
+ if (request.claims) {
1947
+ claimsHash = await this.cryptoImpl.hashString(request.claims);
1948
+ }
1949
+ const accessTokenEntity = AccessTokenEntity.createAccessTokenEntity(
1950
+ result.account?.homeAccountId,
1951
+ result.account.environment,
1952
+ result.accessToken,
1953
+ this.clientId,
1954
+ result.tenantId,
1955
+ result.scopes.join(" "),
1956
+ result.expiresOn?.getTime() || 0,
1957
+ result.extExpiresOn?.getTime() || 0,
1958
+ this.cryptoImpl,
1959
+ undefined, // refreshOn
1960
+ result.tokenType as AuthenticationScheme,
1961
+ undefined, // userAssertionHash
1962
+ request.sshKid,
1963
+ request.claims,
1964
+ claimsHash
1965
+ );
1966
+
1967
+ const cacheRecord = new CacheRecord(
1968
+ undefined,
1969
+ idTokenEntity,
1970
+ accessTokenEntity
1971
+ );
1972
+ return this.saveCacheRecord(cacheRecord);
1973
+ }
1974
+ }
1975
+
1976
+ export const DEFAULT_BROWSER_CACHE_MANAGER = (
1977
+ clientId: string,
1978
+ logger: Logger
1979
+ ): BrowserCacheManager => {
1980
+ const cacheOptions: Required<CacheOptions> = {
1981
+ cacheLocation: BrowserCacheLocation.MemoryStorage,
1982
+ temporaryCacheLocation: BrowserCacheLocation.MemoryStorage,
1983
+ storeAuthStateInCookie: false,
1984
+ secureCookies: false,
1985
+ cacheMigrationEnabled: false,
1986
+ claimsBasedCachingEnabled: false,
1987
+ };
1988
+ return new BrowserCacheManager(
1989
+ clientId,
1990
+ cacheOptions,
1991
+ DEFAULT_CRYPTO_IMPLEMENTATION,
1992
+ logger
1993
+ );
1994
+ };