@auth0/auth0-server-js 1.1.0 → 1.3.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/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  AbstractTransactionStore: () => AbstractTransactionStore,
25
25
  BackchannelLogoutError: () => BackchannelLogoutError,
26
26
  CookieTransactionStore: () => CookieTransactionStore,
27
+ InvalidConfigurationError: () => InvalidConfigurationError,
27
28
  MissingRequiredArgumentError: () => MissingRequiredArgumentError,
28
29
  MissingSessionError: () => MissingSessionError,
29
30
  MissingTransactionError: () => MissingTransactionError,
@@ -70,26 +71,33 @@ var MissingSessionError = class extends Error {
70
71
  this.name = "MissingSessionError";
71
72
  }
72
73
  };
74
+ var InvalidConfigurationError = class extends Error {
75
+ code = "invalid_configuration_error";
76
+ constructor(message) {
77
+ super(message);
78
+ this.name = "InvalidConfigurationError";
79
+ }
80
+ };
73
81
 
74
82
  // src/state/utils.ts
75
- function updateStateData(audience, stateDate, tokenEndpointResponse) {
76
- if (stateDate) {
77
- const isNewTokenSet = !stateDate.tokenSets.some(
83
+ var createUpdatedTokenSet = (audience, response) => ({
84
+ audience,
85
+ accessToken: response.accessToken,
86
+ scope: response.scope,
87
+ expiresAt: response.expiresAt
88
+ });
89
+ function updateStateData(audience, stateData, tokenEndpointResponse) {
90
+ if (stateData) {
91
+ const isNewTokenSet = !stateData.tokenSets.some(
78
92
  (tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope
79
93
  );
80
- const createUpdatedTokenSet = (response) => ({
81
- audience,
82
- accessToken: response.accessToken,
83
- scope: response.scope,
84
- expiresAt: Math.floor(Date.now() / 1e3) + Number(response.expiresAt)
85
- });
86
- const tokenSets = isNewTokenSet ? [...stateDate.tokenSets, createUpdatedTokenSet(tokenEndpointResponse)] : stateDate.tokenSets.map(
87
- (tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope ? createUpdatedTokenSet(tokenEndpointResponse) : tokenSet
94
+ const tokenSets = isNewTokenSet ? [...stateData.tokenSets, createUpdatedTokenSet(audience, tokenEndpointResponse)] : stateData.tokenSets.map(
95
+ (tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope ? createUpdatedTokenSet(audience, tokenEndpointResponse) : tokenSet
88
96
  );
89
97
  return {
90
- ...stateDate,
91
- idToken: tokenEndpointResponse.idToken,
92
- refreshToken: tokenEndpointResponse.refreshToken ?? stateDate.refreshToken,
98
+ ...stateData,
99
+ idToken: tokenEndpointResponse.idToken ?? stateData.idToken,
100
+ refreshToken: tokenEndpointResponse.refreshToken ?? stateData.refreshToken,
93
101
  tokenSets
94
102
  };
95
103
  } else {
@@ -99,12 +107,7 @@ function updateStateData(audience, stateDate, tokenEndpointResponse) {
99
107
  idToken: tokenEndpointResponse.idToken,
100
108
  refreshToken: tokenEndpointResponse.refreshToken,
101
109
  tokenSets: [
102
- {
103
- audience,
104
- accessToken: tokenEndpointResponse.accessToken,
105
- scope: tokenEndpointResponse.scope,
106
- expiresAt: tokenEndpointResponse.expiresAt
107
- }
110
+ createUpdatedTokenSet(audience, tokenEndpointResponse)
108
111
  ],
109
112
  internal: {
110
113
  sid: user?.sid,
@@ -113,9 +116,9 @@ function updateStateData(audience, stateDate, tokenEndpointResponse) {
113
116
  };
114
117
  }
115
118
  }
116
- function updateStateDataForConnectionTokenSet(options, stateDate, tokenEndpointResponse) {
117
- stateDate.connectionTokenSets = stateDate.connectionTokenSets || [];
118
- const isNewTokenSet = !stateDate.connectionTokenSets.some(
119
+ function updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointResponse) {
120
+ stateData.connectionTokenSets = stateData.connectionTokenSets || [];
121
+ const isNewTokenSet = !stateData.connectionTokenSets.some(
119
122
  (tokenSet) => tokenSet.connection === options.connection && (!options.loginHint || tokenSet.loginHint === options.loginHint)
120
123
  );
121
124
  const connectionTokenSet = {
@@ -125,11 +128,11 @@ function updateStateDataForConnectionTokenSet(options, stateDate, tokenEndpointR
125
128
  scope: tokenEndpointResponse.scope,
126
129
  expiresAt: tokenEndpointResponse.expiresAt
127
130
  };
128
- const connectionTokenSets = isNewTokenSet ? [...stateDate.connectionTokenSets, connectionTokenSet] : stateDate.connectionTokenSets.map(
131
+ const connectionTokenSets = isNewTokenSet ? [...stateData.connectionTokenSets, connectionTokenSet] : stateData.connectionTokenSets.map(
129
132
  (tokenSet) => tokenSet.connection === options.connection && (!options.loginHint || tokenSet.loginHint === options.loginHint) ? connectionTokenSet : tokenSet
130
133
  );
131
134
  return {
132
- ...stateDate,
135
+ ...stateData,
133
136
  connectionTokenSets
134
137
  };
135
138
  }
@@ -150,7 +153,30 @@ var compareScopes = (scopes, requiredScopes) => {
150
153
  return requiredScopesArray.every((scope) => scopesSet.has(scope));
151
154
  };
152
155
 
156
+ // src/telemetry.ts
157
+ function getTelemetryConfig(config) {
158
+ if (config?.enabled === false) {
159
+ return config;
160
+ }
161
+ return {
162
+ enabled: true,
163
+ name: config?.name ?? "@auth0/auth0-server-js",
164
+ version: config?.version ?? "1.3.0"
165
+ };
166
+ }
167
+
153
168
  // src/server-client.ts
169
+ var DEFAULT_SCOPES = "openid profile email offline_access";
170
+ function ensureOpenIdScope(scope) {
171
+ if (!scope) {
172
+ return DEFAULT_SCOPES;
173
+ }
174
+ const scopes = scope.split(" ");
175
+ if (!scopes.includes("openid")) {
176
+ scopes.unshift("openid");
177
+ }
178
+ return scopes.join(" ");
179
+ }
154
180
  var ServerClient = class {
155
181
  #options;
156
182
  #transactionStore;
@@ -184,7 +210,8 @@ var ServerClient = class {
184
210
  clientAssertionSigningAlg: this.#options.clientAssertionSigningAlg,
185
211
  authorizationParams: this.#options.authorizationParams,
186
212
  customFetch: this.#options.customFetch,
187
- useMtls: this.#options.useMtls
213
+ useMtls: this.#options.useMtls,
214
+ telemetry: getTelemetryConfig(this.#options.telemetry)
188
215
  });
189
216
  }
190
217
  /**
@@ -201,11 +228,13 @@ var ServerClient = class {
201
228
  if (!redirectUri) {
202
229
  throw new MissingRequiredArgumentError("authorizationParams.redirect_uri");
203
230
  }
231
+ const scope = ensureOpenIdScope(options?.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
204
232
  const { codeVerifier, authorizationUrl } = await this.authClient.buildAuthorizationUrl({
205
233
  pushedAuthorizationRequests: options?.pushedAuthorizationRequests,
206
234
  authorizationParams: {
207
235
  ...options?.authorizationParams,
208
- redirect_uri: redirectUri
236
+ redirect_uri: redirectUri,
237
+ scope
209
238
  }
210
239
  });
211
240
  const transactionState = {
@@ -355,10 +384,14 @@ var ServerClient = class {
355
384
  * @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
356
385
  */
357
386
  async loginBackchannel(options, storeOptions) {
387
+ const scope = ensureOpenIdScope(options.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
358
388
  const tokenEndpointResponse = await this.authClient.backchannelAuthentication({
359
389
  bindingMessage: options.bindingMessage,
360
390
  loginHint: options.loginHint,
361
- authorizationParams: options.authorizationParams
391
+ authorizationParams: {
392
+ ...options.authorizationParams,
393
+ scope
394
+ }
362
395
  });
363
396
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
364
397
  const stateData = updateStateData(
@@ -395,16 +428,24 @@ var ServerClient = class {
395
428
  /**
396
429
  * Retrieves the access token from the store, or calls Auth0 when the access token is expired and a refresh token is available in the store.
397
430
  * Also updates the store when a new token was retrieved from Auth0.
431
+ *
432
+ * @param options Optional options for requesting specific audience/scope.
398
433
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
399
434
  *
400
435
  * @throws {TokenByRefreshTokenError} If the refresh token was not found or there was an issue requesting the access token.
401
436
  *
402
437
  * @returns The Token Set, containing the access token, as well as additional information.
403
438
  */
404
- async getAccessToken(storeOptions) {
405
- const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
406
- const audience = this.#options.authorizationParams?.audience ?? "default";
407
- const scope = this.#options.authorizationParams?.scope;
439
+ async getAccessToken(tokenOptionsOrStoreOptions, storeOptions) {
440
+ const hasTokenOptions = (
441
+ // If second arg exists, first arg must be GetAccessTokenOptions
442
+ storeOptions !== void 0 || // OR if first arg has audience/scope properties
443
+ tokenOptionsOrStoreOptions && typeof tokenOptionsOrStoreOptions === "object" && ("audience" in tokenOptionsOrStoreOptions || "scope" in tokenOptionsOrStoreOptions)
444
+ );
445
+ const [resolvedOptions, resolvedStoreOptions] = hasTokenOptions ? [tokenOptionsOrStoreOptions, storeOptions] : [void 0, tokenOptionsOrStoreOptions];
446
+ const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
447
+ const audience = resolvedOptions?.audience ?? this.#options.authorizationParams?.audience ?? "default";
448
+ const scope = resolvedOptions?.scope ?? this.#options.authorizationParams?.scope;
408
449
  const tokenSet = stateData?.tokenSets.find(
409
450
  (tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
410
451
  );
@@ -416,12 +457,14 @@ var ServerClient = class {
416
457
  "The access token has expired and a refresh token was not provided. The user needs to re-authenticate."
417
458
  );
418
459
  }
419
- const tokenEndpointResponse = await this.authClient.getTokenByRefreshToken({
420
- refreshToken: stateData.refreshToken
421
- });
422
- const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
460
+ const tokenByRefreshTokenOptions = {
461
+ refreshToken: stateData.refreshToken,
462
+ ...hasTokenOptions && { audience, scope }
463
+ };
464
+ const tokenEndpointResponse = await this.authClient.getTokenByRefreshToken(tokenByRefreshTokenOptions);
465
+ const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
423
466
  const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse);
424
- await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
467
+ await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, resolvedStoreOptions);
425
468
  return {
426
469
  accessToken: tokenEndpointResponse.accessToken,
427
470
  scope: tokenEndpointResponse.scope,
@@ -507,6 +550,9 @@ var DIGEST = "SHA-256";
507
550
  var BIT_LENGTH = 512;
508
551
  var HKDF_INFO = "derived cookie encryption secret";
509
552
  var encoder;
553
+ function isDecryptionError(e) {
554
+ return e instanceof import_jose.errors.JWEDecryptionFailed || e instanceof import_jose.errors.JWEInvalid;
555
+ }
510
556
  async function deriveEncryptionSecret(secret, salt, kid) {
511
557
  encoder ||= new TextEncoder();
512
558
  const key = await crypto.subtle.importKey("raw", encoder.encode(secret), "HKDF", false, ["deriveBits"]);
@@ -523,12 +569,23 @@ async function deriveEncryptionSecret(secret, salt, kid) {
523
569
  )
524
570
  );
525
571
  }
526
- async function encrypt(payload, secret, salt, expiration) {
572
+ async function _encrypt(payload, secret, salt, expiration) {
527
573
  const kid = crypto.randomUUID();
528
574
  const encryptionSecret = await deriveEncryptionSecret(secret, salt, kid);
529
575
  return await new import_jose.EncryptJWT(payload).setProtectedHeader({ enc: ENC, alg: ALG, kid }).setExpirationTime(expiration).encrypt(encryptionSecret);
530
576
  }
531
- async function decrypt(value, secret, salt) {
577
+ async function encrypt(payload, secret, salt, expiration) {
578
+ if (typeof secret === "string") {
579
+ return await _encrypt(payload, secret, salt, expiration);
580
+ } else {
581
+ const [newSecret] = secret;
582
+ if (!newSecret) {
583
+ throw new InvalidConfigurationError("At least one secret must be provided");
584
+ }
585
+ return await _encrypt(payload, newSecret, salt, expiration);
586
+ }
587
+ }
588
+ async function _decrypt(value, secret, salt) {
532
589
  const res = await (0, import_jose.jwtDecrypt)(
533
590
  value,
534
591
  async (protectedHeader) => {
@@ -541,6 +598,35 @@ async function decrypt(value, secret, salt) {
541
598
  );
542
599
  return res.payload;
543
600
  }
601
+ async function decrypt(value, secret, salt) {
602
+ if (typeof secret === "string") {
603
+ return await _decrypt(value, secret, salt);
604
+ } else {
605
+ const [newSecret, ...oldSecrets] = secret;
606
+ if (!newSecret) {
607
+ throw new InvalidConfigurationError("At least one secret must be provided");
608
+ }
609
+ let firstError;
610
+ try {
611
+ return await _decrypt(value, newSecret, salt);
612
+ } catch (err) {
613
+ firstError = err;
614
+ if (!isDecryptionError(err)) {
615
+ throw firstError;
616
+ }
617
+ for (const oldSecret of oldSecrets) {
618
+ try {
619
+ return await _decrypt(value, oldSecret, salt);
620
+ } catch (fallbackErr) {
621
+ if (!isDecryptionError(fallbackErr)) {
622
+ throw fallbackErr;
623
+ }
624
+ }
625
+ }
626
+ throw firstError;
627
+ }
628
+ }
629
+ }
544
630
 
545
631
  // src/store/abstract-store.ts
546
632
  var AbstractStore = class {
@@ -552,7 +638,14 @@ var AbstractStore = class {
552
638
  return await encrypt(stateData, this.options.secret, identifier, expiration);
553
639
  }
554
640
  async decrypt(identifier, encryptedStateData) {
555
- return await decrypt(encryptedStateData, this.options.secret, identifier);
641
+ try {
642
+ return await decrypt(encryptedStateData, this.options.secret, identifier);
643
+ } catch (e) {
644
+ if (isDecryptionError(e)) {
645
+ return;
646
+ }
647
+ throw e;
648
+ }
556
649
  }
557
650
  };
558
651
 
@@ -644,13 +737,9 @@ var StatefulStateStore = class extends AbstractSessionStore {
644
737
  }
645
738
  sessionId ??= generateId();
646
739
  const maxAge = this.calculateMaxAge(stateData.internal.createdAt);
647
- const cookieOpts = {
648
- httpOnly: true,
649
- sameSite: this.#cookieOptions?.sameSite ?? "lax",
650
- path: "/",
651
- secure: this.#cookieOptions?.secure,
740
+ const cookieOpts = this.#getCookieOptions({
652
741
  maxAge
653
- };
742
+ });
654
743
  const expiration = Date.now() / 1e3 + maxAge;
655
744
  const encryptedStateData = await this.encrypt(
656
745
  identifier,
@@ -667,7 +756,7 @@ var StatefulStateStore = class extends AbstractSessionStore {
667
756
  if (sessionId) {
668
757
  const stateData = await this.#store.get(sessionId);
669
758
  if (!stateData) {
670
- this.#cookieHandler.deleteCookie(identifier, options);
759
+ this.#cookieHandler.deleteCookie(identifier, options, this.#getCookieOptions());
671
760
  }
672
761
  return stateData;
673
762
  }
@@ -677,18 +766,29 @@ var StatefulStateStore = class extends AbstractSessionStore {
677
766
  if (sessionId) {
678
767
  await this.#store.delete(sessionId);
679
768
  }
680
- this.#cookieHandler.deleteCookie(identifier, options);
769
+ this.#cookieHandler.deleteCookie(identifier, options, this.#getCookieOptions());
681
770
  }
682
771
  async getSessionId(identifier, options) {
683
772
  const cookieValue = this.#cookieHandler.getCookie(identifier, options);
684
773
  if (cookieValue) {
685
774
  const sessionCookie = await this.decrypt(identifier, cookieValue);
686
- return sessionCookie.id;
775
+ if (sessionCookie) {
776
+ return sessionCookie.id;
777
+ }
687
778
  }
688
779
  }
689
780
  deleteByLogoutToken(claims, options) {
690
781
  return this.#store.deleteByLogoutToken(claims, options);
691
782
  }
783
+ #getCookieOptions(partial) {
784
+ return {
785
+ httpOnly: true,
786
+ sameSite: this.#cookieOptions?.sameSite ?? "lax",
787
+ path: this.#cookieOptions?.path ?? "/",
788
+ secure: this.#cookieOptions?.secure ?? true,
789
+ ...partial
790
+ };
791
+ }
692
792
  };
693
793
 
694
794
  // src/store/stateless-state-store.ts
@@ -702,13 +802,9 @@ var StatelessStateStore = class extends AbstractSessionStore {
702
802
  }
703
803
  async set(identifier, stateData, removeIfExists, options) {
704
804
  const maxAge = this.calculateMaxAge(stateData.internal.createdAt);
705
- const cookieOpts = {
706
- httpOnly: true,
707
- sameSite: this.#cookieOptions?.sameSite ?? "lax",
708
- path: "/",
709
- secure: this.#cookieOptions?.secure ?? true,
805
+ const cookieOpts = this.#getCookieOptions({
710
806
  maxAge
711
- };
807
+ });
712
808
  const expiration = Math.floor(Date.now() / 1e3 + maxAge);
713
809
  const encryptedStateData = await this.encrypt(identifier, stateData, expiration);
714
810
  const chunkSize = 3072;
@@ -723,7 +819,7 @@ var StatelessStateStore = class extends AbstractSessionStore {
723
819
  const existingCookieKeys = this.getCookieKeys(identifier, options);
724
820
  const cookieKeysToRemove = existingCookieKeys.filter((key) => !chunks.some((chunk) => chunk.name === key));
725
821
  cookieKeysToRemove.forEach((key) => {
726
- this.#cookieHandler.deleteCookie(key, options);
822
+ this.#cookieHandler.deleteCookie(key, options, cookieOpts);
727
823
  });
728
824
  }
729
825
  async get(identifier, options) {
@@ -739,7 +835,7 @@ var StatelessStateStore = class extends AbstractSessionStore {
739
835
  async delete(identifier, options) {
740
836
  const cookieKeys = this.getCookieKeys(identifier, options);
741
837
  for (const key of cookieKeys) {
742
- this.#cookieHandler.deleteCookie(key, options);
838
+ this.#cookieHandler.deleteCookie(key, options, this.#getCookieOptions());
743
839
  }
744
840
  }
745
841
  deleteByLogoutToken() {
@@ -750,6 +846,15 @@ var StatelessStateStore = class extends AbstractSessionStore {
750
846
  getCookieKeys(identifier, options) {
751
847
  return Object.keys(this.#cookieHandler.getCookies(options)).filter((key) => key.startsWith(identifier));
752
848
  }
849
+ #getCookieOptions(partial) {
850
+ return {
851
+ httpOnly: true,
852
+ sameSite: this.#cookieOptions?.sameSite ?? "lax",
853
+ path: this.#cookieOptions?.path ?? "/",
854
+ secure: this.#cookieOptions?.secure ?? true,
855
+ ...partial
856
+ };
857
+ }
753
858
  };
754
859
  // Annotate the CommonJS export names for ESM import in node:
755
860
  0 && (module.exports = {
@@ -757,6 +862,7 @@ var StatelessStateStore = class extends AbstractSessionStore {
757
862
  AbstractTransactionStore,
758
863
  BackchannelLogoutError,
759
864
  CookieTransactionStore,
865
+ InvalidConfigurationError,
760
866
  MissingRequiredArgumentError,
761
867
  MissingSessionError,
762
868
  MissingTransactionError,