@auth0/auth0-spa-js 2.7.0 → 2.9.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.
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  ![Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE.](https://cdn.auth0.com/website/sdks/banners/spa-js-banner.png)
2
2
 
3
- ![Release](https://img.shields.io/npm/v/@auth0/auth0-spa-js)
3
+ [![npm version](https://img.shields.io/npm/v/@auth0/auth0-spa-js?style=flat-square&logo=npm&logoColor=CB3837)](https://www.npmjs.com/package/@auth0/auth0-spa-js)
4
4
  [![Codecov](https://img.shields.io/codecov/c/github/auth0/auth0-spa-js)](https://codecov.io/gh/auth0/auth0-spa-js)
5
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/auth0/auth0-spa-js)
5
6
  ![Downloads](https://img.shields.io/npm/dw/@auth0/auth0-spa-js)
6
7
  [![License](https://img.shields.io/:license-mit-blue.svg?style=flat)](https://opensource.org/licenses/MIT)
7
8
  ![CircleCI](https://img.shields.io/circleci/build/github/auth0/auth0-spa-js)
@@ -29,7 +30,7 @@ npm install @auth0/auth0-spa-js
29
30
  From the CDN:
30
31
 
31
32
  ```html
32
- <script src="https://cdn.auth0.com/js/auth0-spa-js/2.7/auth0-spa-js.production.js"></script>
33
+ <script src="https://cdn.auth0.com/js/auth0-spa-js/2.9/auth0-spa-js.production.js"></script>
33
34
  ```
34
35
 
35
36
  ### Configure Auth0
@@ -156,4 +157,4 @@ Please do not report security vulnerabilities on the public GitHub issue tracker
156
157
  </p>
157
158
  <p align="center">
158
159
  This project is licensed under the MIT license. See the <a href="https://github.com/auth0/auth0-spa-js/blob/main/LICENSE"> LICENSE</a> file for more info.
159
- </p>
160
+ </p>
@@ -540,7 +540,7 @@
540
540
  exports.default = SuperTokensLock;
541
541
  }));
542
542
  var Lock = unwrapExports(browserTabsLock);
543
- var version = "2.7.0";
543
+ var version = "2.9.0";
544
544
  const DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS = 60;
545
545
  const DEFAULT_POPUP_CONFIG_OPTIONS = {
546
546
  timeoutInSeconds: DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS
@@ -558,6 +558,7 @@
558
558
  version: version
559
559
  };
560
560
  const DEFAULT_NOW_PROVIDER = () => Date.now();
561
+ const DEFAULT_AUDIENCE = "default";
561
562
  class GenericError extends Error {
562
563
  constructor(error, error_description) {
563
564
  super(error_description);
@@ -733,6 +734,23 @@
733
734
  const stripUndefined = params => Object.keys(params).filter((k => typeof params[k] !== "undefined")).reduce(((acc, key) => Object.assign(Object.assign({}, acc), {
734
735
  [key]: params[key]
735
736
  })), {});
737
+ const ALLOWED_AUTH0CLIENT_PROPERTIES = [ {
738
+ key: "name",
739
+ type: [ "string" ]
740
+ }, {
741
+ key: "version",
742
+ type: [ "string", "number" ]
743
+ }, {
744
+ key: "env",
745
+ type: [ "object" ]
746
+ } ];
747
+ const stripAuth0Client = auth0Client => Object.keys(auth0Client).reduce(((acc, key) => {
748
+ const allowedProperty = ALLOWED_AUTH0CLIENT_PROPERTIES.find((p => p.key === key));
749
+ if (allowedProperty && allowedProperty.type.includes(typeof auth0Client[key])) {
750
+ acc[key] = auth0Client[key];
751
+ }
752
+ return acc;
753
+ }), {});
736
754
  const createQueryParams = _a => {
737
755
  var {clientId: client_id} = _a, params = __rest(_a, [ "clientId" ]);
738
756
  return new URLSearchParams(stripUndefined(Object.assign({
@@ -1215,17 +1233,42 @@
1215
1233
  });
1216
1234
  const body = useFormData ? createQueryParams(allParams) : JSON.stringify(allParams);
1217
1235
  const isDpopSupported = isGrantTypeSupported(options.grant_type);
1218
- return await getJSON(`${baseUrl}/oauth/token`, timeout, audience || "default", scope, {
1236
+ return await getJSON(`${baseUrl}/oauth/token`, timeout, audience || DEFAULT_AUDIENCE, scope, {
1219
1237
  method: "POST",
1220
1238
  body: body,
1221
1239
  headers: {
1222
1240
  "Content-Type": useFormData ? "application/x-www-form-urlencoded" : "application/json",
1223
- "Auth0-Client": btoa(JSON.stringify(auth0Client || DEFAULT_AUTH0_CLIENT))
1241
+ "Auth0-Client": btoa(JSON.stringify(stripAuth0Client(auth0Client || DEFAULT_AUTH0_CLIENT)))
1224
1242
  }
1225
1243
  }, worker, useFormData, useMrrt, isDpopSupported ? dpop : undefined);
1226
1244
  }
1227
1245
  const dedupe = arr => Array.from(new Set(arr));
1228
1246
  const getUniqueScopes = (...scopes) => dedupe(scopes.filter(Boolean).join(" ").trim().split(/\s+/)).join(" ");
1247
+ const injectDefaultScopes = (authScopes, openIdScope, ...extraScopes) => {
1248
+ if (typeof authScopes !== "object") {
1249
+ return {
1250
+ [DEFAULT_AUDIENCE]: getUniqueScopes(openIdScope, authScopes, ...extraScopes)
1251
+ };
1252
+ }
1253
+ let requestedScopes = {
1254
+ [DEFAULT_AUDIENCE]: getUniqueScopes(openIdScope, ...extraScopes)
1255
+ };
1256
+ Object.keys(authScopes).forEach((key => {
1257
+ const audienceScopes = authScopes[key];
1258
+ requestedScopes[key] = getUniqueScopes(openIdScope, audienceScopes, ...extraScopes);
1259
+ }));
1260
+ return requestedScopes;
1261
+ };
1262
+ const scopesToRequest = (authScopes, methodScopes, audience) => {
1263
+ let scope;
1264
+ if (audience) {
1265
+ scope = authScopes[audience];
1266
+ }
1267
+ if (!scope) {
1268
+ scope = authScopes[DEFAULT_AUDIENCE];
1269
+ }
1270
+ return getUniqueScopes(scope, methodScopes);
1271
+ };
1229
1272
  const CACHE_KEY_PREFIX = "@@auth0spajs@@";
1230
1273
  const CACHE_KEY_ID_TOKEN_SUFFIX = "@@user@@";
1231
1274
  class CacheKey {
@@ -1394,6 +1437,14 @@
1394
1437
  await this.cache.set(cacheKey.toKey(), wrappedEntry);
1395
1438
  await ((_a = this.keyManifest) === null || _a === void 0 ? void 0 : _a.add(cacheKey.toKey()));
1396
1439
  }
1440
+ async remove(client_id, audience, scope) {
1441
+ const cacheKey = new CacheKey({
1442
+ clientId: client_id,
1443
+ scope: scope,
1444
+ audience: audience
1445
+ });
1446
+ await this.cache.remove(cacheKey.toKey());
1447
+ }
1397
1448
  async clear(clientId) {
1398
1449
  var _a;
1399
1450
  const keys = await this.getCacheKeys();
@@ -1868,6 +1919,7 @@
1868
1919
  }
1869
1920
  }
1870
1921
  const GET_TOKEN_SILENTLY_LOCK_KEY = "auth0.lock.getTokenSilently";
1922
+ const buildGetTokenSilentlyLockKey = (clientId, audience) => `${GET_TOKEN_SILENTLY_LOCK_KEY}.${clientId}.${audience}`;
1871
1923
  const buildOrganizationHintCookieName = clientId => `auth0.${clientId}.organization_hint`;
1872
1924
  const OLD_IS_AUTHENTICATED_COOKIE_NAME = "auth0.is.authenticated";
1873
1925
  const buildIsAuthenticatedCookieName = clientId => `auth0.${clientId}.is.authenticated`;
@@ -1879,7 +1931,7 @@
1879
1931
  const getAuthorizeParams = (clientOptions, scope, authorizationParams, state, nonce, code_challenge, redirect_uri, response_mode, thumbprint) => Object.assign(Object.assign(Object.assign({
1880
1932
  client_id: clientOptions.clientId
1881
1933
  }, clientOptions.authorizationParams), authorizationParams), {
1882
- scope: getUniqueScopes(scope, authorizationParams.scope),
1934
+ scope: scopesToRequest(scope, authorizationParams.scope, authorizationParams.audience),
1883
1935
  response_type: "code",
1884
1936
  response_mode: response_mode || "query",
1885
1937
  state: state,
@@ -2215,6 +2267,7 @@
2215
2267
  class Auth0Client {
2216
2268
  constructor(options) {
2217
2269
  this.userCache = (new InMemoryCache).enclosedCache;
2270
+ this.activeLockKeys = new Set;
2218
2271
  this.defaultOptions = {
2219
2272
  authorizationParams: {
2220
2273
  scope: DEFAULT_SCOPE
@@ -2223,7 +2276,11 @@
2223
2276
  useFormData: true
2224
2277
  };
2225
2278
  this._releaseLockOnPageHide = async () => {
2226
- await lock.releaseLock(GET_TOKEN_SILENTLY_LOCK_KEY);
2279
+ const lockKeysToRelease = Array.from(this.activeLockKeys);
2280
+ for (const lockKey of lockKeysToRelease) {
2281
+ await lock.releaseLock(lockKey);
2282
+ }
2283
+ this.activeLockKeys.clear();
2227
2284
  window.removeEventListener("pagehide", this._releaseLockOnPageHide);
2228
2285
  };
2229
2286
  this.options = Object.assign(Object.assign(Object.assign({}, this.defaultOptions), options), {
@@ -2250,7 +2307,7 @@
2250
2307
  this.isAuthenticatedCookieName = buildIsAuthenticatedCookieName(this.options.clientId);
2251
2308
  this.sessionCheckExpiryDays = options.sessionCheckExpiryDays || DEFAULT_SESSION_CHECK_EXPIRY_DAYS;
2252
2309
  const transactionStorage = options.useCookiesForTransactions ? this.cookieStorage : SessionStorage;
2253
- this.scope = getUniqueScopes("openid", this.options.authorizationParams.scope, this.options.useRefreshTokens ? "offline_access" : "");
2310
+ this.scope = injectDefaultScopes(this.options.authorizationParams.scope, "openid", this.options.useRefreshTokens ? "offline_access" : "");
2254
2311
  this.transactionManager = new TransactionManager(transactionStorage, this.options.clientId, this.options.cookieDomain);
2255
2312
  this.nowProvider = this.options.nowProvider || DEFAULT_NOW_PROVIDER;
2256
2313
  this.cacheManager = new CacheManager(cache, !cache.allKeys ? new CacheKeyManifest(cache, this.options.clientId) : undefined, this.nowProvider);
@@ -2324,7 +2381,7 @@
2324
2381
  nonce: nonce,
2325
2382
  code_verifier: code_verifier,
2326
2383
  scope: params.scope,
2327
- audience: params.audience || "default",
2384
+ audience: params.audience || DEFAULT_AUDIENCE,
2328
2385
  redirect_uri: params.redirect_uri,
2329
2386
  state: state,
2330
2387
  url: url
@@ -2474,12 +2531,12 @@
2474
2531
  } catch (_) {}
2475
2532
  }
2476
2533
  async getTokenSilently(options = {}) {
2477
- var _a;
2534
+ var _a, _b;
2478
2535
  const localOptions = Object.assign(Object.assign({
2479
2536
  cacheMode: "on"
2480
2537
  }, options), {
2481
2538
  authorizationParams: Object.assign(Object.assign(Object.assign({}, this.options.authorizationParams), options.authorizationParams), {
2482
- scope: getUniqueScopes(this.scope, (_a = options.authorizationParams) === null || _a === void 0 ? void 0 : _a.scope)
2539
+ scope: scopesToRequest(this.scope, (_a = options.authorizationParams) === null || _a === void 0 ? void 0 : _a.scope, ((_b = options.authorizationParams) === null || _b === void 0 ? void 0 : _b.audience) || this.options.authorizationParams.audience)
2483
2540
  })
2484
2541
  });
2485
2542
  const result = await singlePromise((() => this._getTokenSilently(localOptions)), `${this.options.clientId}::${localOptions.authorizationParams.audience}::${localOptions.authorizationParams.scope}`);
@@ -2490,7 +2547,7 @@
2490
2547
  if (cacheMode !== "off") {
2491
2548
  const entry = await this._getEntryFromCache({
2492
2549
  scope: getTokenOptions.authorizationParams.scope,
2493
- audience: getTokenOptions.authorizationParams.audience || "default",
2550
+ audience: getTokenOptions.authorizationParams.audience || DEFAULT_AUDIENCE,
2494
2551
  clientId: this.options.clientId,
2495
2552
  cacheMode: cacheMode
2496
2553
  });
@@ -2501,13 +2558,17 @@
2501
2558
  if (cacheMode === "cache-only") {
2502
2559
  return;
2503
2560
  }
2504
- if (await retryPromise((() => lock.acquireLock(GET_TOKEN_SILENTLY_LOCK_KEY, 5e3)), 10)) {
2505
- try {
2561
+ const lockKey = buildGetTokenSilentlyLockKey(this.options.clientId, getTokenOptions.authorizationParams.audience || "default");
2562
+ if (await retryPromise((() => lock.acquireLock(lockKey, 5e3)), 10)) {
2563
+ this.activeLockKeys.add(lockKey);
2564
+ if (this.activeLockKeys.size === 1) {
2506
2565
  window.addEventListener("pagehide", this._releaseLockOnPageHide);
2566
+ }
2567
+ try {
2507
2568
  if (cacheMode !== "off") {
2508
2569
  const entry = await this._getEntryFromCache({
2509
2570
  scope: getTokenOptions.authorizationParams.scope,
2510
- audience: getTokenOptions.authorizationParams.audience || "default",
2571
+ audience: getTokenOptions.authorizationParams.audience || DEFAULT_AUDIENCE,
2511
2572
  clientId: this.options.clientId
2512
2573
  });
2513
2574
  if (entry) {
@@ -2526,25 +2587,28 @@
2526
2587
  expires_in: expires_in
2527
2588
  });
2528
2589
  } finally {
2529
- await lock.releaseLock(GET_TOKEN_SILENTLY_LOCK_KEY);
2530
- window.removeEventListener("pagehide", this._releaseLockOnPageHide);
2590
+ await lock.releaseLock(lockKey);
2591
+ this.activeLockKeys.delete(lockKey);
2592
+ if (this.activeLockKeys.size === 0) {
2593
+ window.removeEventListener("pagehide", this._releaseLockOnPageHide);
2594
+ }
2531
2595
  }
2532
2596
  } else {
2533
2597
  throw new TimeoutError;
2534
2598
  }
2535
2599
  }
2536
2600
  async getTokenWithPopup(options = {}, config = {}) {
2537
- var _a;
2601
+ var _a, _b;
2538
2602
  const localOptions = Object.assign(Object.assign({}, options), {
2539
2603
  authorizationParams: Object.assign(Object.assign(Object.assign({}, this.options.authorizationParams), options.authorizationParams), {
2540
- scope: getUniqueScopes(this.scope, (_a = options.authorizationParams) === null || _a === void 0 ? void 0 : _a.scope)
2604
+ scope: scopesToRequest(this.scope, (_a = options.authorizationParams) === null || _a === void 0 ? void 0 : _a.scope, ((_b = options.authorizationParams) === null || _b === void 0 ? void 0 : _b.audience) || this.options.authorizationParams.audience)
2541
2605
  })
2542
2606
  });
2543
2607
  config = Object.assign(Object.assign({}, DEFAULT_POPUP_CONFIG_OPTIONS), config);
2544
2608
  await this.loginWithPopup(localOptions, config);
2545
2609
  const cache = await this.cacheManager.get(new CacheKey({
2546
2610
  scope: localOptions.authorizationParams.scope,
2547
- audience: localOptions.authorizationParams.audience || "default",
2611
+ audience: localOptions.authorizationParams.audience || DEFAULT_AUDIENCE,
2548
2612
  clientId: this.options.clientId
2549
2613
  }), undefined, this.options.useMrrt);
2550
2614
  return cache.access_token;
@@ -2642,14 +2706,14 @@
2642
2706
  async _getTokenUsingRefreshToken(options) {
2643
2707
  const cache = await this.cacheManager.get(new CacheKey({
2644
2708
  scope: options.authorizationParams.scope,
2645
- audience: options.authorizationParams.audience || "default",
2709
+ audience: options.authorizationParams.audience || DEFAULT_AUDIENCE,
2646
2710
  clientId: this.options.clientId
2647
2711
  }), undefined, this.options.useMrrt);
2648
2712
  if ((!cache || !cache.refresh_token) && !this.worker) {
2649
2713
  if (this.options.useRefreshTokensFallback) {
2650
2714
  return await this._getTokenFromIFrame(options);
2651
2715
  }
2652
- throw new MissingRefreshTokenError(options.authorizationParams.audience || "default", options.authorizationParams.scope);
2716
+ throw new MissingRefreshTokenError(options.authorizationParams.audience || DEFAULT_AUDIENCE, options.authorizationParams.scope);
2653
2717
  }
2654
2718
  const redirect_uri = options.authorizationParams.redirect_uri || this.options.authorizationParams.redirect_uri || window.location.origin;
2655
2719
  const timeout = typeof options.timeoutInSeconds === "number" ? options.timeoutInSeconds * 1e3 : null;
@@ -2675,6 +2739,7 @@
2675
2739
  if (this.options.useRefreshTokensFallback) {
2676
2740
  return await this._getTokenFromIFrame(options);
2677
2741
  }
2742
+ await this.cacheManager.remove(this.options.clientId, options.authorizationParams.audience, options.authorizationParams.scope);
2678
2743
  const missingScopes = getMissingScopes(scopesToRequest, tokenResult.scope);
2679
2744
  throw new MissingScopesError(options.authorizationParams.audience || "default", missingScopes);
2680
2745
  }
@@ -2683,7 +2748,7 @@
2683
2748
  return Object.assign(Object.assign({}, tokenResult), {
2684
2749
  scope: options.authorizationParams.scope,
2685
2750
  oauthTokenScope: tokenResult.scope,
2686
- audience: options.authorizationParams.audience || "default"
2751
+ audience: options.authorizationParams.audience || DEFAULT_AUDIENCE
2687
2752
  });
2688
2753
  } catch (e) {
2689
2754
  if ((e.message.indexOf(MISSING_REFRESH_TOKEN_ERROR_MESSAGE) > -1 || e.message && e.message.indexOf(INVALID_REFRESH_TOKEN_ERROR_MESSAGE) > -1) && this.options.useRefreshTokensFallback) {
@@ -2702,11 +2767,12 @@
2702
2767
  await this.cacheManager.set(entryWithoutIdToken);
2703
2768
  }
2704
2769
  async _getIdTokenFromCache() {
2705
- const audience = this.options.authorizationParams.audience || "default";
2770
+ const audience = this.options.authorizationParams.audience || DEFAULT_AUDIENCE;
2771
+ const scope = this.scope[audience];
2706
2772
  const cache = await this.cacheManager.getIdToken(new CacheKey({
2707
2773
  clientId: this.options.clientId,
2708
2774
  audience: audience,
2709
- scope: this.scope
2775
+ scope: scope
2710
2776
  }));
2711
2777
  const currentCache = this.userCache.get(CACHE_KEY_ID_TOKEN_SUFFIX);
2712
2778
  if (cache && cache.id_token === (currentCache === null || currentCache === void 0 ? void 0 : currentCache.id_token)) {
@@ -2752,7 +2818,7 @@
2752
2818
  await this._saveEntryInCache(Object.assign(Object.assign(Object.assign(Object.assign({}, authResult), {
2753
2819
  decodedToken: decodedToken,
2754
2820
  scope: options.scope,
2755
- audience: options.audience || "default"
2821
+ audience: options.audience || DEFAULT_AUDIENCE
2756
2822
  }), authResult.scope ? {
2757
2823
  oauthTokenScope: authResult.scope
2758
2824
  } : null), {
@@ -2772,7 +2838,7 @@
2772
2838
  grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
2773
2839
  subject_token: options.subject_token,
2774
2840
  subject_token_type: options.subject_token_type,
2775
- scope: getUniqueScopes(options.scope, this.scope),
2841
+ scope: scopesToRequest(this.scope, options.scope, options.audience || this.options.authorizationParams.audience),
2776
2842
  audience: options.audience || this.options.authorizationParams.audience
2777
2843
  });
2778
2844
  }
@@ -2812,7 +2878,7 @@
2812
2878
  });
2813
2879
  }
2814
2880
  async connectAccountWithRedirect(options) {
2815
- const {openUrl: openUrl, appState: appState, connection: connection, authorization_params: authorization_params, redirectUri: redirectUri = this.options.authorizationParams.redirect_uri || window.location.origin} = options;
2881
+ const {openUrl: openUrl, appState: appState, connection: connection, scopes: scopes, authorization_params: authorization_params, redirectUri: redirectUri = this.options.authorizationParams.redirect_uri || window.location.origin} = options;
2816
2882
  if (!connection) {
2817
2883
  throw new Error("connection is required");
2818
2884
  }
@@ -2822,6 +2888,7 @@
2822
2888
  const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
2823
2889
  const {connect_uri: connect_uri, connect_params: connect_params, auth_session: auth_session} = await this.myAccountApi.connectAccount({
2824
2890
  connection: connection,
2891
+ scopes: scopes,
2825
2892
  redirect_uri: redirectUri,
2826
2893
  state: state,
2827
2894
  code_challenge: code_challenge,