@azure/msal-common 14.4.1-beta.1 → 14.6.0-alpha.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 (209) hide show
  1. package/dist/account/AccountInfo.d.ts +27 -0
  2. package/dist/account/AccountInfo.d.ts.map +1 -1
  3. package/dist/account/AccountInfo.mjs +64 -0
  4. package/dist/account/AccountInfo.mjs.map +1 -0
  5. package/dist/account/AuthToken.mjs +1 -1
  6. package/dist/account/AuthToken.mjs.map +1 -1
  7. package/dist/account/CcsCredential.mjs +1 -1
  8. package/dist/account/CcsCredential.mjs.map +1 -1
  9. package/dist/account/ClientInfo.d.ts +1 -2
  10. package/dist/account/ClientInfo.d.ts.map +1 -1
  11. package/dist/account/ClientInfo.mjs +3 -3
  12. package/dist/account/ClientInfo.mjs.map +1 -1
  13. package/dist/account/TokenClaims.d.ts +18 -0
  14. package/dist/account/TokenClaims.d.ts.map +1 -1
  15. package/dist/account/TokenClaims.mjs +25 -0
  16. package/dist/account/TokenClaims.mjs.map +1 -0
  17. package/dist/authority/Authority.d.ts +7 -2
  18. package/dist/authority/Authority.d.ts.map +1 -1
  19. package/dist/authority/Authority.mjs +34 -7
  20. package/dist/authority/Authority.mjs.map +1 -1
  21. package/dist/authority/AuthorityFactory.mjs +1 -1
  22. package/dist/authority/AuthorityFactory.mjs.map +1 -1
  23. package/dist/authority/AuthorityMetadata.mjs +1 -1
  24. package/dist/authority/AuthorityMetadata.mjs.map +1 -1
  25. package/dist/authority/AuthorityOptions.mjs +1 -1
  26. package/dist/authority/AuthorityOptions.mjs.map +1 -1
  27. package/dist/authority/AuthorityType.mjs +1 -1
  28. package/dist/authority/AuthorityType.mjs.map +1 -1
  29. package/dist/authority/CloudInstanceDiscoveryErrorResponse.mjs +1 -1
  30. package/dist/authority/CloudInstanceDiscoveryErrorResponse.mjs.map +1 -1
  31. package/dist/authority/CloudInstanceDiscoveryResponse.mjs +1 -1
  32. package/dist/authority/CloudInstanceDiscoveryResponse.mjs.map +1 -1
  33. package/dist/authority/OpenIdConfigResponse.mjs +1 -1
  34. package/dist/authority/OpenIdConfigResponse.mjs.map +1 -1
  35. package/dist/authority/ProtocolMode.mjs +1 -1
  36. package/dist/authority/ProtocolMode.mjs.map +1 -1
  37. package/dist/authority/RegionDiscovery.mjs +1 -1
  38. package/dist/authority/RegionDiscovery.mjs.map +1 -1
  39. package/dist/cache/CacheManager.d.ts +50 -27
  40. package/dist/cache/CacheManager.d.ts.map +1 -1
  41. package/dist/cache/CacheManager.mjs +243 -107
  42. package/dist/cache/CacheManager.mjs.map +1 -1
  43. package/dist/cache/entities/AccountEntity.d.ts +9 -4
  44. package/dist/cache/entities/AccountEntity.d.ts.map +1 -1
  45. package/dist/cache/entities/AccountEntity.mjs +56 -28
  46. package/dist/cache/entities/AccountEntity.mjs.map +1 -1
  47. package/dist/cache/entities/AppMetadataEntity.mjs +1 -1
  48. package/dist/cache/entities/AppMetadataEntity.mjs.map +1 -1
  49. package/dist/cache/entities/AuthorityMetadataEntity.mjs +1 -1
  50. package/dist/cache/entities/AuthorityMetadataEntity.mjs.map +1 -1
  51. package/dist/cache/entities/CacheRecord.mjs +1 -1
  52. package/dist/cache/entities/CacheRecord.mjs.map +1 -1
  53. package/dist/cache/entities/ThrottlingEntity.d.ts +2 -8
  54. package/dist/cache/entities/ThrottlingEntity.d.ts.map +1 -1
  55. package/dist/cache/persistence/TokenCacheContext.mjs +1 -1
  56. package/dist/cache/persistence/TokenCacheContext.mjs.map +1 -1
  57. package/dist/cache/utils/CacheHelpers.d.ts +7 -1
  58. package/dist/cache/utils/CacheHelpers.d.ts.map +1 -1
  59. package/dist/cache/utils/CacheHelpers.mjs +20 -4
  60. package/dist/cache/utils/CacheHelpers.mjs.map +1 -1
  61. package/dist/cache/utils/CacheTypes.d.ts +2 -0
  62. package/dist/cache/utils/CacheTypes.d.ts.map +1 -1
  63. package/dist/client/AuthorizationCodeClient.d.ts.map +1 -1
  64. package/dist/client/AuthorizationCodeClient.mjs +10 -9
  65. package/dist/client/AuthorizationCodeClient.mjs.map +1 -1
  66. package/dist/client/BaseClient.mjs +1 -1
  67. package/dist/client/BaseClient.mjs.map +1 -1
  68. package/dist/client/RefreshTokenClient.d.ts.map +1 -1
  69. package/dist/client/RefreshTokenClient.mjs +25 -11
  70. package/dist/client/RefreshTokenClient.mjs.map +1 -1
  71. package/dist/client/SilentFlowClient.d.ts.map +1 -1
  72. package/dist/client/SilentFlowClient.mjs +20 -9
  73. package/dist/client/SilentFlowClient.mjs.map +1 -1
  74. package/dist/config/ClientConfiguration.d.ts.map +1 -1
  75. package/dist/config/ClientConfiguration.mjs +2 -4
  76. package/dist/config/ClientConfiguration.mjs.map +1 -1
  77. package/dist/constants/AADServerParamKeys.d.ts +53 -0
  78. package/dist/constants/AADServerParamKeys.d.ts.map +1 -0
  79. package/dist/constants/AADServerParamKeys.mjs +61 -0
  80. package/dist/constants/AADServerParamKeys.mjs.map +1 -0
  81. package/dist/crypto/ICrypto.mjs +1 -1
  82. package/dist/crypto/ICrypto.mjs.map +1 -1
  83. package/dist/crypto/JoseHeader.mjs +1 -1
  84. package/dist/crypto/JoseHeader.mjs.map +1 -1
  85. package/dist/crypto/PopTokenGenerator.mjs +2 -2
  86. package/dist/crypto/PopTokenGenerator.mjs.map +1 -1
  87. package/dist/error/AuthError.mjs +1 -1
  88. package/dist/error/AuthError.mjs.map +1 -1
  89. package/dist/error/AuthErrorCodes.mjs +1 -1
  90. package/dist/error/AuthErrorCodes.mjs.map +1 -1
  91. package/dist/error/ClientAuthError.mjs +1 -1
  92. package/dist/error/ClientAuthError.mjs.map +1 -1
  93. package/dist/error/ClientAuthErrorCodes.mjs +1 -1
  94. package/dist/error/ClientAuthErrorCodes.mjs.map +1 -1
  95. package/dist/error/ClientConfigurationError.mjs +1 -1
  96. package/dist/error/ClientConfigurationError.mjs.map +1 -1
  97. package/dist/error/ClientConfigurationErrorCodes.mjs +1 -1
  98. package/dist/error/ClientConfigurationErrorCodes.mjs.map +1 -1
  99. package/dist/error/InteractionRequiredAuthError.d.ts +4 -0
  100. package/dist/error/InteractionRequiredAuthError.d.ts.map +1 -1
  101. package/dist/error/InteractionRequiredAuthError.mjs +10 -3
  102. package/dist/error/InteractionRequiredAuthError.mjs.map +1 -1
  103. package/dist/error/InteractionRequiredAuthErrorCodes.d.ts +1 -0
  104. package/dist/error/InteractionRequiredAuthErrorCodes.d.ts.map +1 -1
  105. package/dist/error/InteractionRequiredAuthErrorCodes.mjs +4 -3
  106. package/dist/error/InteractionRequiredAuthErrorCodes.mjs.map +1 -1
  107. package/dist/error/JoseHeaderError.mjs +1 -1
  108. package/dist/error/JoseHeaderError.mjs.map +1 -1
  109. package/dist/error/JoseHeaderErrorCodes.mjs +1 -1
  110. package/dist/error/JoseHeaderErrorCodes.mjs.map +1 -1
  111. package/dist/error/ServerError.mjs +1 -1
  112. package/dist/error/ServerError.mjs.map +1 -1
  113. package/dist/index.cjs +797 -416
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.d.ts +5 -4
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.mjs +7 -4
  118. package/dist/index.mjs.map +1 -1
  119. package/dist/logger/Logger.mjs +1 -1
  120. package/dist/logger/Logger.mjs.map +1 -1
  121. package/dist/network/INetworkModule.mjs +1 -1
  122. package/dist/network/INetworkModule.mjs.map +1 -1
  123. package/dist/network/NetworkManager.mjs +1 -1
  124. package/dist/network/NetworkManager.mjs.map +1 -1
  125. package/dist/network/ThrottlingUtils.mjs +1 -1
  126. package/dist/network/ThrottlingUtils.mjs.map +1 -1
  127. package/dist/packageMetadata.d.ts +1 -1
  128. package/dist/packageMetadata.d.ts.map +1 -1
  129. package/dist/packageMetadata.mjs +2 -2
  130. package/dist/packageMetadata.mjs.map +1 -1
  131. package/dist/request/AuthenticationHeaderParser.mjs +1 -1
  132. package/dist/request/AuthenticationHeaderParser.mjs.map +1 -1
  133. package/dist/request/CommonRefreshTokenRequest.d.ts +0 -1
  134. package/dist/request/CommonRefreshTokenRequest.d.ts.map +1 -1
  135. package/dist/request/CommonSilentFlowRequest.d.ts +7 -1
  136. package/dist/request/CommonSilentFlowRequest.d.ts.map +1 -1
  137. package/dist/request/RequestParameterBuilder.d.ts.map +1 -1
  138. package/dist/request/RequestParameterBuilder.mjs +46 -45
  139. package/dist/request/RequestParameterBuilder.mjs.map +1 -1
  140. package/dist/request/RequestValidator.mjs +1 -1
  141. package/dist/request/RequestValidator.mjs.map +1 -1
  142. package/dist/request/ScopeSet.mjs +1 -1
  143. package/dist/request/ScopeSet.mjs.map +1 -1
  144. package/dist/response/ResponseHandler.d.ts +3 -1
  145. package/dist/response/ResponseHandler.d.ts.map +1 -1
  146. package/dist/response/ResponseHandler.mjs +56 -20
  147. package/dist/response/ResponseHandler.mjs.map +1 -1
  148. package/dist/response/ServerAuthorizationTokenResponse.d.ts +1 -0
  149. package/dist/response/ServerAuthorizationTokenResponse.d.ts.map +1 -1
  150. package/dist/telemetry/performance/IPerformanceClient.d.ts +6 -0
  151. package/dist/telemetry/performance/IPerformanceClient.d.ts.map +1 -1
  152. package/dist/telemetry/performance/PerformanceClient.d.ts +9 -2
  153. package/dist/telemetry/performance/PerformanceClient.d.ts.map +1 -1
  154. package/dist/telemetry/performance/PerformanceClient.mjs +17 -13
  155. package/dist/telemetry/performance/PerformanceClient.mjs.map +1 -1
  156. package/dist/telemetry/performance/PerformanceEvent.mjs +1 -1
  157. package/dist/telemetry/performance/PerformanceEvent.mjs.map +1 -1
  158. package/dist/telemetry/performance/StubPerformanceClient.d.ts.map +1 -1
  159. package/dist/telemetry/performance/StubPerformanceClient.mjs +1 -1
  160. package/dist/telemetry/performance/StubPerformanceClient.mjs.map +1 -1
  161. package/dist/telemetry/server/ServerTelemetryManager.mjs +1 -1
  162. package/dist/telemetry/server/ServerTelemetryManager.mjs.map +1 -1
  163. package/dist/url/UrlString.mjs +1 -1
  164. package/dist/url/UrlString.mjs.map +1 -1
  165. package/dist/utils/Constants.d.ts +2 -69
  166. package/dist/utils/Constants.d.ts.map +1 -1
  167. package/dist/utils/Constants.mjs +6 -70
  168. package/dist/utils/Constants.mjs.map +1 -1
  169. package/dist/utils/FunctionWrappers.mjs +1 -1
  170. package/dist/utils/FunctionWrappers.mjs.map +1 -1
  171. package/dist/utils/ProtocolUtils.mjs +1 -1
  172. package/dist/utils/ProtocolUtils.mjs.map +1 -1
  173. package/dist/utils/StringUtils.mjs +1 -1
  174. package/dist/utils/StringUtils.mjs.map +1 -1
  175. package/dist/utils/TimeUtils.mjs +1 -1
  176. package/dist/utils/TimeUtils.mjs.map +1 -1
  177. package/dist/utils/UrlUtils.mjs +1 -1
  178. package/dist/utils/UrlUtils.mjs.map +1 -1
  179. package/package.json +93 -92
  180. package/src/account/AccountInfo.ts +96 -0
  181. package/src/account/ClientInfo.ts +2 -3
  182. package/src/account/TokenClaims.ts +28 -0
  183. package/src/authority/Authority.ts +38 -5
  184. package/src/cache/CacheManager.ts +410 -162
  185. package/src/cache/entities/AccountEntity.ts +84 -33
  186. package/src/cache/entities/ThrottlingEntity.ts +2 -24
  187. package/src/cache/utils/CacheHelpers.ts +22 -2
  188. package/src/cache/utils/CacheTypes.ts +11 -0
  189. package/src/client/AuthorizationCodeClient.ts +4 -5
  190. package/src/client/RefreshTokenClient.ts +35 -14
  191. package/src/client/SilentFlowClient.ts +31 -11
  192. package/src/config/ClientConfiguration.ts +4 -4
  193. package/src/constants/AADServerParamKeys.ts +57 -0
  194. package/src/crypto/PopTokenGenerator.ts +1 -1
  195. package/src/error/InteractionRequiredAuthError.ts +12 -2
  196. package/src/error/InteractionRequiredAuthErrorCodes.ts +1 -0
  197. package/src/index.ts +18 -5
  198. package/src/packageMetadata.ts +1 -1
  199. package/src/request/CommonRefreshTokenRequest.ts +0 -1
  200. package/src/request/CommonSilentFlowRequest.ts +7 -1
  201. package/src/request/RequestParameterBuilder.ts +7 -5
  202. package/src/response/ResponseHandler.ts +109 -25
  203. package/src/response/ServerAuthorizationTokenResponse.ts +1 -0
  204. package/src/telemetry/performance/IPerformanceClient.ts +6 -0
  205. package/src/telemetry/performance/PerformanceClient.ts +24 -31
  206. package/src/telemetry/performance/StubPerformanceClient.ts +0 -1
  207. package/src/utils/Constants.ts +4 -72
  208. package/dist/cache/entities/ThrottlingEntity.mjs +0 -30
  209. package/dist/cache/entities/ThrottlingEntity.mjs.map +0 -1
@@ -10,6 +10,7 @@ import {
10
10
  AppMetadataFilter,
11
11
  AppMetadataCache,
12
12
  TokenKeys,
13
+ TenantProfileFilter,
13
14
  } from "./utils/CacheTypes";
14
15
  import { CacheRecord } from "./entities/CacheRecord";
15
16
  import {
@@ -32,7 +33,12 @@ import {
32
33
  createClientAuthError,
33
34
  ClientAuthErrorCodes,
34
35
  } from "../error/ClientAuthError";
35
- import { AccountInfo } from "../account/AccountInfo";
36
+ import {
37
+ AccountInfo,
38
+ TenantProfile,
39
+ tenantIdMatchesHomeTenant,
40
+ updateAccountTenantProfileData,
41
+ } from "../account/AccountInfo";
36
42
  import { AppMetadataEntity } from "./entities/AppMetadataEntity";
37
43
  import { ServerTelemetryEntity } from "./entities/ServerTelemetryEntity";
38
44
  import { ThrottlingEntity } from "./entities/ThrottlingEntity";
@@ -75,7 +81,15 @@ export abstract class CacheManager implements ICacheManager {
75
81
  * fetch the account entity from the platform cache
76
82
  * @param accountKey
77
83
  */
78
- abstract getAccount(accountKey: string): AccountEntity | null;
84
+ abstract getAccount(
85
+ accountKey: string,
86
+ logger?: Logger
87
+ ): AccountEntity | null;
88
+
89
+ /**
90
+ * Returns deserialized account if found in the cache, otherwiser returns null
91
+ */
92
+ abstract getCachedAccountEntity(accountKey: string): AccountEntity | null;
79
93
 
80
94
  /**
81
95
  * set account entity in the platform cache
@@ -83,6 +97,11 @@ export abstract class CacheManager implements ICacheManager {
83
97
  */
84
98
  abstract setAccount(account: AccountEntity): void;
85
99
 
100
+ /**
101
+ * remove account entity from the platform cache if it's outdated
102
+ */
103
+ abstract removeOutdatedAccount(accountKey: string): void;
104
+
86
105
  /**
87
106
  * fetch the idToken entity from the platform cache
88
107
  * @param idTokenKey
@@ -238,31 +257,23 @@ export abstract class CacheManager implements ICacheManager {
238
257
  * @returns Array of AccountInfo objects in cache
239
258
  */
240
259
  getAllAccounts(accountFilter?: AccountFilter): AccountInfo[] {
241
- const validAccounts: AccountInfo[] = [];
242
- this.getAccountsFilteredBy(accountFilter || {}).forEach(
243
- (accountEntity: AccountEntity) => {
244
- const accountInfo = this.getAccountInfoFromEntity(
245
- accountEntity,
246
- accountFilter
247
- );
248
- if (accountInfo) {
249
- validAccounts.push(accountInfo);
250
- }
251
- }
260
+ return this.buildTenantProfiles(
261
+ this.getAccountsFilteredBy(accountFilter || {}),
262
+ accountFilter
252
263
  );
253
- return validAccounts;
254
264
  }
255
265
 
256
266
  /**
257
- * Gets accountInfo object based on provided filters
267
+ * Gets first tenanted AccountInfo object found based on provided filters
258
268
  */
259
269
  getAccountInfoFilteredBy(accountFilter: AccountFilter): AccountInfo | null {
260
270
  const allAccounts = this.getAllAccounts(accountFilter);
261
271
  if (allAccounts.length > 1) {
262
- // If one or more accounts are found, further filter to the first account that has an ID token
263
- return allAccounts.filter((account) => {
264
- return !!account.idTokenClaims;
265
- })[0];
272
+ // If one or more accounts are found, prioritize accounts that have an ID token
273
+ const sortedAccounts = allAccounts.sort((account) => {
274
+ return account.idTokenClaims ? -1 : 1;
275
+ });
276
+ return sortedAccounts[0];
266
277
  } else if (allAccounts.length === 1) {
267
278
  // If only one account is found, return it regardless of whether a matching ID token was found
268
279
  return allAccounts[0];
@@ -285,52 +296,214 @@ export abstract class CacheManager implements ICacheManager {
285
296
  }
286
297
  }
287
298
 
288
- private getAccountInfoFromEntity(
299
+ /**
300
+ * Matches filtered account entities with cached ID tokens that match the tenant profile-specific account filters
301
+ * and builds the account info objects from the matching ID token's claims
302
+ * @param cachedAccounts
303
+ * @param accountFilter
304
+ * @returns Array of AccountInfo objects that match account and tenant profile filters
305
+ */
306
+ private buildTenantProfiles(
307
+ cachedAccounts: AccountEntity[],
308
+ accountFilter?: AccountFilter
309
+ ): AccountInfo[] {
310
+ return cachedAccounts.flatMap((accountEntity) => {
311
+ return this.getAccountInfoForTenantProfiles(
312
+ accountEntity,
313
+ accountFilter
314
+ );
315
+ });
316
+ }
317
+
318
+ private getAccountInfoForTenantProfiles(
289
319
  accountEntity: AccountEntity,
290
320
  accountFilter?: AccountFilter
321
+ ): AccountInfo[] {
322
+ return this.getTenantProfilesFromAccountEntity(
323
+ accountEntity,
324
+ accountFilter?.tenantId,
325
+ accountFilter
326
+ );
327
+ }
328
+
329
+ private getTenantedAccountInfoByFilter(
330
+ accountInfo: AccountInfo,
331
+ tokenKeys: TokenKeys,
332
+ tenantProfile: TenantProfile,
333
+ tenantProfileFilter?: TenantProfileFilter
291
334
  ): AccountInfo | null {
292
- const accountInfo = accountEntity.getAccountInfo();
293
- const idToken = this.getIdToken(accountInfo);
335
+ let tenantedAccountInfo: AccountInfo | null = null;
336
+ let idTokenClaims: TokenClaims | undefined;
337
+
338
+ if (tenantProfileFilter) {
339
+ if (
340
+ !this.tenantProfileMatchesFilter(
341
+ tenantProfile,
342
+ tenantProfileFilter
343
+ )
344
+ ) {
345
+ return null;
346
+ }
347
+ }
348
+
349
+ const idToken = this.getIdToken(
350
+ accountInfo,
351
+ tokenKeys,
352
+ tenantProfile.tenantId
353
+ );
354
+
294
355
  if (idToken) {
295
- const idTokenClaims = extractTokenClaims(
356
+ idTokenClaims = extractTokenClaims(
296
357
  idToken.secret,
297
358
  this.cryptoImpl.base64Decode
298
359
  );
299
360
 
300
361
  if (
301
- this.idTokenClaimsMatchAccountFilter(
362
+ !this.idTokenClaimsMatchTenantProfileFilter(
302
363
  idTokenClaims,
303
- accountFilter
364
+ tenantProfileFilter
304
365
  )
305
366
  ) {
306
- accountInfo.idToken = idToken.secret;
307
- accountInfo.idTokenClaims = idTokenClaims;
308
- return accountInfo;
367
+ // ID token sourced claims don't match so this tenant profile is not a match
368
+ return null;
309
369
  }
310
370
  }
311
- return accountInfo;
371
+
372
+ // Expand tenant profile into account info based on matching tenant profile and if available matching ID token claims
373
+ tenantedAccountInfo = updateAccountTenantProfileData(
374
+ accountInfo,
375
+ tenantProfile,
376
+ idTokenClaims
377
+ );
378
+
379
+ return tenantedAccountInfo;
312
380
  }
313
381
 
314
- private idTokenClaimsMatchAccountFilter(
382
+ private getTenantProfilesFromAccountEntity(
383
+ accountEntity: AccountEntity,
384
+ targetTenantId?: string,
385
+ tenantProfileFilter?: TenantProfileFilter
386
+ ): AccountInfo[] {
387
+ const accountInfo = accountEntity.getAccountInfo();
388
+ let searchTenantProfiles: Map<string, TenantProfile> =
389
+ accountInfo.tenantProfiles || new Map<string, TenantProfile>();
390
+ const tokenKeys = this.getTokenKeys();
391
+
392
+ // If a tenant ID was provided, only return the tenant profile for that tenant ID if it exists
393
+ if (targetTenantId) {
394
+ const tenantProfile = searchTenantProfiles.get(targetTenantId);
395
+ if (tenantProfile) {
396
+ // Reduce search field to just this tenant profile
397
+ searchTenantProfiles = new Map<string, TenantProfile>([
398
+ [targetTenantId, tenantProfile],
399
+ ]);
400
+ } else {
401
+ // No tenant profile for search tenant ID, return empty array
402
+ return [];
403
+ }
404
+ }
405
+
406
+ const matchingTenantProfiles: AccountInfo[] = [];
407
+ searchTenantProfiles.forEach((tenantProfile: TenantProfile) => {
408
+ const tenantedAccountInfo = this.getTenantedAccountInfoByFilter(
409
+ accountInfo,
410
+ tokenKeys,
411
+ tenantProfile,
412
+ tenantProfileFilter
413
+ );
414
+ if (tenantedAccountInfo) {
415
+ matchingTenantProfiles.push(tenantedAccountInfo);
416
+ }
417
+ });
418
+
419
+ return matchingTenantProfiles;
420
+ }
421
+
422
+ private tenantProfileMatchesFilter(
423
+ tenantProfile: TenantProfile,
424
+ tenantProfileFilter: TenantProfileFilter
425
+ ): boolean {
426
+ if (
427
+ !!tenantProfileFilter.localAccountId &&
428
+ !this.matchLocalAccountIdFromTenantProfile(
429
+ tenantProfile,
430
+ tenantProfileFilter.localAccountId
431
+ )
432
+ ) {
433
+ return false;
434
+ }
435
+
436
+ if (
437
+ !!tenantProfileFilter.name &&
438
+ !(tenantProfile.name === tenantProfileFilter.name)
439
+ ) {
440
+ return false;
441
+ }
442
+
443
+ if (
444
+ tenantProfileFilter.isHomeTenant !== undefined &&
445
+ !(tenantProfile.isHomeTenant === tenantProfileFilter.isHomeTenant)
446
+ ) {
447
+ return false;
448
+ }
449
+
450
+ return true;
451
+ }
452
+
453
+ private idTokenClaimsMatchTenantProfileFilter(
315
454
  idTokenClaims: TokenClaims,
316
- accountFilter?: AccountFilter
455
+ tenantProfileFilter?: TenantProfileFilter
317
456
  ): boolean {
318
- if (accountFilter) {
457
+ // Tenant Profile filtering
458
+ if (tenantProfileFilter) {
319
459
  if (
320
- !!accountFilter.loginHint &&
321
- !this.matchLoginHint(idTokenClaims, accountFilter.loginHint)
460
+ !!tenantProfileFilter.localAccountId &&
461
+ !this.matchLocalAccountIdFromTokenClaims(
462
+ idTokenClaims,
463
+ tenantProfileFilter.localAccountId
464
+ )
322
465
  ) {
323
466
  return false;
324
467
  }
468
+
325
469
  if (
326
- !!accountFilter.sid &&
327
- !this.matchSid(idTokenClaims, accountFilter.sid)
470
+ !!tenantProfileFilter.loginHint &&
471
+ !this.matchLoginHintFromTokenClaims(
472
+ idTokenClaims,
473
+ tenantProfileFilter.loginHint
474
+ )
475
+ ) {
476
+ return false;
477
+ }
478
+
479
+ if (
480
+ !!tenantProfileFilter.username &&
481
+ !this.matchUsername(
482
+ idTokenClaims.preferred_username,
483
+ tenantProfileFilter.username
484
+ )
485
+ ) {
486
+ return false;
487
+ }
488
+
489
+ if (
490
+ !!tenantProfileFilter.name &&
491
+ !this.matchName(idTokenClaims, tenantProfileFilter.name)
492
+ ) {
493
+ return false;
494
+ }
495
+
496
+ if (
497
+ !!tenantProfileFilter.sid &&
498
+ !this.matchSid(idTokenClaims, tenantProfileFilter.sid)
328
499
  ) {
329
500
  return false;
330
501
  }
331
502
  }
503
+
332
504
  return true;
333
505
  }
506
+
334
507
  /**
335
508
  * saves a cache record
336
509
  * @param cacheRecord
@@ -414,7 +587,7 @@ export abstract class CacheManager implements ICacheManager {
414
587
  }
415
588
 
416
589
  /**
417
- * Retrieve accounts matching all provided filters; if no filter is set, get all accounts
590
+ * Retrieve account entities matching all provided tenant-agnostic filters; if no filter is set, get all account entities in the cache
418
591
  * Not checking for casing as keys are all generated in lower case, remember to convert to lower case if object properties are compared
419
592
  * @param accountFilter - An object containing Account properties to filter by
420
593
  */
@@ -422,18 +595,17 @@ export abstract class CacheManager implements ICacheManager {
422
595
  const allAccountKeys = this.getAccountKeys();
423
596
  const matchingAccounts: AccountEntity[] = [];
424
597
  allAccountKeys.forEach((cacheKey) => {
425
- if (
426
- !this.isAccountKey(
427
- cacheKey,
428
- accountFilter.homeAccountId,
429
- accountFilter.tenantId
430
- )
431
- ) {
598
+ if (!this.isAccountKey(cacheKey, accountFilter.homeAccountId)) {
432
599
  // Don't parse value if the key doesn't match the account filters
433
600
  return;
434
601
  }
435
602
 
436
- const entity: AccountEntity | null = this.getAccount(cacheKey);
603
+ const entity: AccountEntity | null = this.getAccount(
604
+ cacheKey,
605
+ this.commonLogger
606
+ );
607
+
608
+ // Match base account fields
437
609
 
438
610
  if (!entity) {
439
611
  return;
@@ -446,16 +618,9 @@ export abstract class CacheManager implements ICacheManager {
446
618
  return;
447
619
  }
448
620
 
449
- if (
450
- !!accountFilter.localAccountId &&
451
- !this.matchLocalAccountId(entity, accountFilter.localAccountId)
452
- ) {
453
- return;
454
- }
455
-
456
621
  if (
457
622
  !!accountFilter.username &&
458
- !this.matchUsername(entity, accountFilter.username)
623
+ !this.matchUsername(entity.username, accountFilter.username)
459
624
  ) {
460
625
  return;
461
626
  }
@@ -474,14 +639,6 @@ export abstract class CacheManager implements ICacheManager {
474
639
  return;
475
640
  }
476
641
 
477
- // tenantId is another name for realm
478
- if (
479
- !!accountFilter.tenantId &&
480
- !this.matchRealm(entity, accountFilter.tenantId)
481
- ) {
482
- return;
483
- }
484
-
485
642
  if (
486
643
  !!accountFilter.nativeAccountId &&
487
644
  !this.matchNativeAccountId(
@@ -499,10 +656,23 @@ export abstract class CacheManager implements ICacheManager {
499
656
  return;
500
657
  }
501
658
 
502
- if (
503
- !!accountFilter.name &&
504
- !this.matchName(entity, accountFilter.name)
505
- ) {
659
+ // If at least one tenant profile matches the tenant profile filter, add the account to the list of matching accounts
660
+ const tenantProfileFilter: TenantProfileFilter = {
661
+ localAccountId: accountFilter?.localAccountId,
662
+ name: accountFilter?.name,
663
+ };
664
+
665
+ const matchingTenantProfiles = entity.tenantProfiles?.filter(
666
+ (tenantProfile: TenantProfile) => {
667
+ return this.tenantProfileMatchesFilter(
668
+ tenantProfile,
669
+ tenantProfileFilter
670
+ );
671
+ }
672
+ );
673
+
674
+ if (matchingTenantProfiles && matchingTenantProfiles.length === 0) {
675
+ // No tenant profile for this account matches filter, don't add to list of matching accounts
506
676
  return;
507
677
  }
508
678
 
@@ -788,7 +958,7 @@ export abstract class CacheManager implements ICacheManager {
788
958
  * @param account
789
959
  */
790
960
  async removeAccount(accountKey: string): Promise<void> {
791
- const account = this.getAccount(accountKey);
961
+ const account = this.getAccount(accountKey, this.commonLogger);
792
962
  if (!account) {
793
963
  return;
794
964
  }
@@ -826,6 +996,88 @@ export abstract class CacheManager implements ICacheManager {
826
996
  await Promise.all(removedCredentials);
827
997
  }
828
998
 
999
+ /**
1000
+ * Migrates a single-tenant account and all it's associated alternate cross-tenant account objects in the
1001
+ * cache into a condensed multi-tenant account object with tenant profiles.
1002
+ * @param accountKey
1003
+ * @param accountEntity
1004
+ * @param logger
1005
+ * @returns
1006
+ */
1007
+ protected updateOutdatedCachedAccount(
1008
+ accountKey: string,
1009
+ accountEntity: AccountEntity | null,
1010
+ logger?: Logger
1011
+ ): AccountEntity | null {
1012
+ // Only update if account entity is defined and has no tenantProfiles object (is outdated)
1013
+ if (accountEntity && accountEntity.isSingleTenant()) {
1014
+ this.commonLogger?.verbose(
1015
+ "updateOutdatedCachedAccount: Found a single-tenant (outdated) account entity in the cache, migrating to multi-tenant account entity"
1016
+ );
1017
+
1018
+ // Get keys of all accounts belonging to user
1019
+ const matchingAccountKeys = this.getAccountKeys().filter(
1020
+ (key: string) => {
1021
+ return key.startsWith(accountEntity.homeAccountId);
1022
+ }
1023
+ );
1024
+
1025
+ // Get all account entities belonging to user
1026
+ const accountsToMerge: AccountEntity[] = [];
1027
+ matchingAccountKeys.forEach((key: string) => {
1028
+ const account = this.getCachedAccountEntity(key);
1029
+ if (account) {
1030
+ accountsToMerge.push(account);
1031
+ }
1032
+ });
1033
+
1034
+ // Set base account to home account if available, any account if not
1035
+ const baseAccount =
1036
+ accountsToMerge.find((account) => {
1037
+ return tenantIdMatchesHomeTenant(
1038
+ account.realm,
1039
+ account.homeAccountId
1040
+ );
1041
+ }) || accountsToMerge[0];
1042
+
1043
+ // Populate tenant profiles built from each account entity belonging to the user
1044
+ baseAccount.tenantProfiles = accountsToMerge.map(
1045
+ (account: AccountEntity) => {
1046
+ return {
1047
+ tenantId: account.realm,
1048
+ localAccountId: account.localAccountId,
1049
+ name: account.name,
1050
+ isHomeTenant: tenantIdMatchesHomeTenant(
1051
+ account.realm,
1052
+ account.homeAccountId
1053
+ ),
1054
+ };
1055
+ }
1056
+ );
1057
+
1058
+ const updatedAccount = CacheManager.toObject(new AccountEntity(), {
1059
+ ...baseAccount,
1060
+ });
1061
+
1062
+ const newAccountKey = updatedAccount.generateAccountKey();
1063
+
1064
+ // Clear cache of legacy account objects that have been collpsed into tenant profiles
1065
+ matchingAccountKeys.forEach((key: string) => {
1066
+ if (key !== newAccountKey) {
1067
+ this.removeOutdatedAccount(accountKey);
1068
+ }
1069
+ });
1070
+
1071
+ // Cache updated account object
1072
+ this.setAccount(updatedAccount);
1073
+ logger?.verbose("Updated an outdated account entity in the cache");
1074
+ return updatedAccount;
1075
+ }
1076
+
1077
+ // No update is necessary
1078
+ return accountEntity;
1079
+ }
1080
+
829
1081
  /**
830
1082
  * returns a boolean if the given credential is removed
831
1083
  * @param credential
@@ -875,61 +1127,6 @@ export abstract class CacheManager implements ICacheManager {
875
1127
  return true;
876
1128
  }
877
1129
 
878
- /**
879
- * Retrieve the cached credentials into a cacherecord
880
- * @param account {AccountInfo}
881
- * @param request {BaseAuthRequest}
882
- * @param environment {string}
883
- * @param performanceClient {?IPerformanceClient}
884
- * @param correlationId {?string}
885
- */
886
- readCacheRecord(
887
- account: AccountInfo,
888
- request: BaseAuthRequest,
889
- environment: string,
890
- performanceClient?: IPerformanceClient,
891
- correlationId?: string
892
- ): CacheRecord {
893
- const tokenKeys = this.getTokenKeys();
894
- const cachedAccount = this.readAccountFromCache(account);
895
- const cachedIdToken = this.getIdToken(
896
- account,
897
- tokenKeys,
898
- performanceClient,
899
- correlationId
900
- );
901
- const cachedAccessToken = this.getAccessToken(
902
- account,
903
- request,
904
- tokenKeys,
905
- performanceClient,
906
- correlationId
907
- );
908
- const cachedRefreshToken = this.getRefreshToken(
909
- account,
910
- false,
911
- tokenKeys,
912
- performanceClient,
913
- correlationId
914
- );
915
- const cachedAppMetadata = this.readAppMetadataFromCache(environment);
916
-
917
- if (cachedAccount && cachedIdToken) {
918
- cachedAccount.idTokenClaims = extractTokenClaims(
919
- cachedIdToken.secret,
920
- this.cryptoImpl.base64Decode
921
- );
922
- }
923
-
924
- return {
925
- account: cachedAccount,
926
- idToken: cachedIdToken,
927
- accessToken: cachedAccessToken,
928
- refreshToken: cachedRefreshToken,
929
- appMetadata: cachedAppMetadata,
930
- };
931
- }
932
-
933
1130
  /**
934
1131
  * Retrieve AccountEntity from cache
935
1132
  * @param account
@@ -937,19 +1134,21 @@ export abstract class CacheManager implements ICacheManager {
937
1134
  readAccountFromCache(account: AccountInfo): AccountEntity | null {
938
1135
  const accountKey: string =
939
1136
  AccountEntity.generateAccountCacheKey(account);
940
- return this.getAccount(accountKey);
1137
+ return this.getAccount(accountKey, this.commonLogger);
941
1138
  }
942
1139
 
943
1140
  /**
944
1141
  * Retrieve IdTokenEntity from cache
945
1142
  * @param account {AccountInfo}
946
1143
  * @param tokenKeys {?TokenKeys}
1144
+ * @param targetRealm {?string}
947
1145
  * @param performanceClient {?IPerformanceClient}
948
1146
  * @param correlationId {?string}
949
1147
  */
950
1148
  getIdToken(
951
1149
  account: AccountInfo,
952
1150
  tokenKeys?: TokenKeys,
1151
+ targetRealm?: string,
953
1152
  performanceClient?: IPerformanceClient,
954
1153
  correlationId?: string
955
1154
  ): IdTokenEntity | null {
@@ -959,36 +1158,66 @@ export abstract class CacheManager implements ICacheManager {
959
1158
  environment: account.environment,
960
1159
  credentialType: CredentialType.ID_TOKEN,
961
1160
  clientId: this.clientId,
962
- realm: account.tenantId,
1161
+ realm: targetRealm,
963
1162
  };
964
1163
 
965
- const idTokens: IdTokenEntity[] = this.getIdTokensByFilter(
1164
+ const idTokenMap: Map<string, IdTokenEntity> = this.getIdTokensByFilter(
966
1165
  idTokenFilter,
967
1166
  tokenKeys
968
1167
  );
969
- const numIdTokens = idTokens.length;
1168
+
1169
+ const numIdTokens = idTokenMap.size;
970
1170
 
971
1171
  if (numIdTokens < 1) {
972
1172
  this.commonLogger.info("CacheManager:getIdToken - No token found");
973
1173
  return null;
974
1174
  } else if (numIdTokens > 1) {
1175
+ let tokensToBeRemoved: Map<string, IdTokenEntity> = idTokenMap;
1176
+ // Multiple tenant profiles and no tenant specified, pick home account
1177
+ if (!targetRealm) {
1178
+ const homeIdTokenMap: Map<string, IdTokenEntity> = new Map<
1179
+ string,
1180
+ IdTokenEntity
1181
+ >();
1182
+ idTokenMap.forEach((idToken, key) => {
1183
+ if (idToken.realm === account.tenantId) {
1184
+ homeIdTokenMap.set(key, idToken);
1185
+ }
1186
+ });
1187
+ const numHomeIdTokens = homeIdTokenMap.size;
1188
+ if (numHomeIdTokens < 1) {
1189
+ this.commonLogger.info(
1190
+ "CacheManager:getIdToken - Multiple ID tokens found for account but none match account entity tenant id, returning first result"
1191
+ );
1192
+ return idTokenMap.values().next().value;
1193
+ } else if (numHomeIdTokens === 1) {
1194
+ this.commonLogger.info(
1195
+ "CacheManager:getIdToken - Multiple ID tokens found for account, defaulting to home tenant profile"
1196
+ );
1197
+ return homeIdTokenMap.values().next().value;
1198
+ } else {
1199
+ // Multiple ID tokens for home tenant profile, remove all and return null
1200
+ tokensToBeRemoved = homeIdTokenMap;
1201
+ }
1202
+ }
1203
+ // Multiple tokens for a single tenant profile, remove all and return null
975
1204
  this.commonLogger.info(
976
- "CacheManager:getIdToken - Multiple id tokens found, clearing them"
1205
+ "CacheManager:getIdToken - Multiple matching ID tokens found, clearing them"
977
1206
  );
978
- idTokens.forEach((idToken) => {
979
- this.removeIdToken(generateCredentialKey(idToken));
1207
+ tokensToBeRemoved.forEach((idToken, key) => {
1208
+ this.removeIdToken(key);
980
1209
  });
981
1210
  if (performanceClient && correlationId) {
982
1211
  performanceClient.addFields(
983
- { multiMatchedID: idTokens.length },
1212
+ { multiMatchedID: idTokenMap.size },
984
1213
  correlationId
985
1214
  );
986
1215
  }
987
1216
  return null;
988
1217
  }
989
1218
 
990
- this.commonLogger.info("CacheManager:getIdToken - Returning id token");
991
- return idTokens[0];
1219
+ this.commonLogger.info("CacheManager:getIdToken - Returning ID token");
1220
+ return idTokenMap.values().next().value;
992
1221
  }
993
1222
 
994
1223
  /**
@@ -999,11 +1228,14 @@ export abstract class CacheManager implements ICacheManager {
999
1228
  getIdTokensByFilter(
1000
1229
  filter: CredentialFilter,
1001
1230
  tokenKeys?: TokenKeys
1002
- ): IdTokenEntity[] {
1231
+ ): Map<string, IdTokenEntity> {
1003
1232
  const idTokenKeys =
1004
1233
  (tokenKeys && tokenKeys.idToken) || this.getTokenKeys().idToken;
1005
1234
 
1006
- const idTokens: IdTokenEntity[] = [];
1235
+ const idTokens: Map<string, IdTokenEntity> = new Map<
1236
+ string,
1237
+ IdTokenEntity
1238
+ >();
1007
1239
  idTokenKeys.forEach((key) => {
1008
1240
  if (
1009
1241
  !this.idTokenKeyMatchesFilter(key, {
@@ -1015,7 +1247,7 @@ export abstract class CacheManager implements ICacheManager {
1015
1247
  }
1016
1248
  const idToken = this.getIdTokenCredential(key);
1017
1249
  if (idToken && this.credentialMatchesFilter(idToken, filter)) {
1018
- idTokens.push(idToken);
1250
+ idTokens.set(key, idToken);
1019
1251
  }
1020
1252
  });
1021
1253
 
@@ -1078,6 +1310,7 @@ export abstract class CacheManager implements ICacheManager {
1078
1310
  account: AccountInfo,
1079
1311
  request: BaseAuthRequest,
1080
1312
  tokenKeys?: TokenKeys,
1313
+ targetRealm?: string,
1081
1314
  performanceClient?: IPerformanceClient,
1082
1315
  correlationId?: string
1083
1316
  ): AccessTokenEntity | null {
@@ -1101,7 +1334,7 @@ export abstract class CacheManager implements ICacheManager {
1101
1334
  environment: account.environment,
1102
1335
  credentialType: credentialType,
1103
1336
  clientId: this.clientId,
1104
- realm: account.tenantId,
1337
+ realm: targetRealm || account.tenantId,
1105
1338
  target: scopes,
1106
1339
  tokenType: authScheme,
1107
1340
  keyId: request.sshKid,
@@ -1407,27 +1640,19 @@ export abstract class CacheManager implements ICacheManager {
1407
1640
  * @param localAccountId
1408
1641
  * @returns
1409
1642
  */
1410
- private matchLocalAccountId(
1411
- entity: AccountEntity,
1643
+ private matchLocalAccountIdFromTokenClaims(
1644
+ tokenClaims: TokenClaims,
1412
1645
  localAccountId: string
1413
1646
  ): boolean {
1414
- return !!(
1415
- typeof entity.localAccountId === "string" &&
1416
- localAccountId === entity.localAccountId
1417
- );
1647
+ const idTokenLocalAccountId = tokenClaims.oid || tokenClaims.sub;
1648
+ return localAccountId === idTokenLocalAccountId;
1418
1649
  }
1419
1650
 
1420
- /**
1421
- * helper to match usernames
1422
- * @param entity
1423
- * @param username
1424
- * @returns
1425
- */
1426
- private matchUsername(entity: AccountEntity, username: string): boolean {
1427
- return !!(
1428
- typeof entity.username === "string" &&
1429
- username.toLowerCase() === entity.username.toLowerCase()
1430
- );
1651
+ private matchLocalAccountIdFromTenantProfile(
1652
+ tenantProfile: TenantProfile,
1653
+ localAccountId: string
1654
+ ): boolean {
1655
+ return tenantProfile.localAccountId === localAccountId;
1431
1656
  }
1432
1657
 
1433
1658
  /**
@@ -1436,8 +1661,25 @@ export abstract class CacheManager implements ICacheManager {
1436
1661
  * @param name
1437
1662
  * @returns true if the downcased name properties are present and match in the filter and the entity
1438
1663
  */
1439
- private matchName(entity: AccountEntity, name: string): boolean {
1440
- return !!(name.toLowerCase() === entity.name?.toLowerCase());
1664
+ private matchName(claims: TokenClaims, name: string): boolean {
1665
+ return !!(name.toLowerCase() === claims.name?.toLowerCase());
1666
+ }
1667
+
1668
+ /**
1669
+ * helper to match usernames
1670
+ * @param entity
1671
+ * @param username
1672
+ * @returns
1673
+ */
1674
+ private matchUsername(
1675
+ cachedUsername?: string,
1676
+ filterUsername?: string
1677
+ ): boolean {
1678
+ return !!(
1679
+ cachedUsername &&
1680
+ typeof cachedUsername === "string" &&
1681
+ filterUsername?.toLowerCase() === cachedUsername.toLowerCase()
1682
+ );
1441
1683
  }
1442
1684
 
1443
1685
  /**
@@ -1537,7 +1779,7 @@ export abstract class CacheManager implements ICacheManager {
1537
1779
  entity: AccountEntity | CredentialEntity,
1538
1780
  realm: string
1539
1781
  ): boolean {
1540
- return !!(entity.realm && realm === entity.realm);
1782
+ return !!(entity.realm?.toLowerCase() === realm.toLowerCase());
1541
1783
  }
1542
1784
 
1543
1785
  /**
@@ -1564,19 +1806,19 @@ export abstract class CacheManager implements ICacheManager {
1564
1806
  * @param loginHint
1565
1807
  * @returns
1566
1808
  */
1567
- private matchLoginHint(
1568
- idTokenClaims: TokenClaims,
1809
+ private matchLoginHintFromTokenClaims(
1810
+ tokenClaims: TokenClaims,
1569
1811
  loginHint: string
1570
1812
  ): boolean {
1571
- if (idTokenClaims?.login_hint === loginHint) {
1813
+ if (tokenClaims.login_hint === loginHint) {
1572
1814
  return true;
1573
1815
  }
1574
1816
 
1575
- if (idTokenClaims.preferred_username === loginHint) {
1817
+ if (tokenClaims.preferred_username === loginHint) {
1576
1818
  return true;
1577
1819
  }
1578
1820
 
1579
- if (idTokenClaims?.upn === loginHint) {
1821
+ if (tokenClaims.upn === loginHint) {
1580
1822
  return true;
1581
1823
  }
1582
1824
 
@@ -1585,12 +1827,12 @@ export abstract class CacheManager implements ICacheManager {
1585
1827
 
1586
1828
  /**
1587
1829
  * Helper to match sid
1588
- * @param idTokenClaims
1830
+ * @param entity
1589
1831
  * @param sid
1590
1832
  * @returns true if the sid claim is present and matches the filter
1591
1833
  */
1592
1834
  private matchSid(idTokenClaims: TokenClaims, sid: string): boolean {
1593
- return !!(idTokenClaims?.sid && idTokenClaims.sid === sid);
1835
+ return idTokenClaims.sid === sid;
1594
1836
  }
1595
1837
 
1596
1838
  private matchAuthorityType(
@@ -1688,6 +1930,9 @@ export class DefaultStorageClass extends CacheManager {
1688
1930
  getAccount(): AccountEntity {
1689
1931
  throw createClientAuthError(ClientAuthErrorCodes.methodNotImplemented);
1690
1932
  }
1933
+ getCachedAccountEntity(): AccountEntity | null {
1934
+ throw createClientAuthError(ClientAuthErrorCodes.methodNotImplemented);
1935
+ }
1691
1936
  setIdTokenCredential(): void {
1692
1937
  throw createClientAuthError(ClientAuthErrorCodes.methodNotImplemented);
1693
1938
  }
@@ -1754,4 +1999,7 @@ export class DefaultStorageClass extends CacheManager {
1754
1999
  updateCredentialCacheKey(): string {
1755
2000
  throw createClientAuthError(ClientAuthErrorCodes.methodNotImplemented);
1756
2001
  }
2002
+ removeOutdatedAccount(): void {
2003
+ throw createClientAuthError(ClientAuthErrorCodes.methodNotImplemented);
2004
+ }
1757
2005
  }