@auth0/auth0-server-js 1.10.0 → 1.12.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.js CHANGED
@@ -1,4 +1,9 @@
1
1
  // src/errors.ts
2
+ var TokenExchangeErrorCode = {
3
+ ACTOR_UNAVAILABLE: "actor_unavailable",
4
+ SETACTOR_REQUIRED: "setactor_required",
5
+ SESSION_TRANSFER_DISABLED: "session_transfer_disabled"
6
+ };
2
7
  var MissingTransactionError = class extends Error {
3
8
  code = "missing_transaction_error";
4
9
  constructor(message) {
@@ -166,7 +171,8 @@ import {
166
171
  OrganizationValidationError,
167
172
  PasswordlessStartError,
168
173
  PasswordlessVerifyError,
169
- TokenByRefreshTokenError
174
+ TokenByRefreshTokenError,
175
+ TokenExchangeError
170
176
  } from "@auth0/auth0-auth-js";
171
177
 
172
178
  // src/utils.ts
@@ -205,7 +211,7 @@ function getTelemetryConfig(config) {
205
211
  return {
206
212
  enabled: true,
207
213
  name: config?.name ?? "@auth0/auth0-server-js",
208
- version: config?.version ?? "1.10.0"
214
+ version: config?.version ?? "1.12.0"
209
215
  };
210
216
  }
211
217
 
@@ -445,6 +451,24 @@ var decodeIssuer = (token) => {
445
451
  return void 0;
446
452
  }
447
453
  };
454
+ var ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token";
455
+ var ID_TOKEN_EXPIRY_SKEW_SECONDS = 30;
456
+ var actorUnavailableError = (message) => {
457
+ const error = new TokenExchangeError(message);
458
+ error.code = TokenExchangeErrorCode.ACTOR_UNAVAILABLE;
459
+ return error;
460
+ };
461
+ var isTokenExpired = (token) => {
462
+ try {
463
+ const { exp } = decodeJwt(token);
464
+ if (typeof exp !== "number") {
465
+ return true;
466
+ }
467
+ return exp <= Date.now() / 1e3 + ID_TOKEN_EXPIRY_SKEW_SECONDS;
468
+ } catch {
469
+ return true;
470
+ }
471
+ };
448
472
  var ServerClient = class {
449
473
  #options;
450
474
  #transactionStore;
@@ -1369,6 +1393,186 @@ var ServerClient = class {
1369
1393
  const authClient = this.#getAuthClient(domain);
1370
1394
  return authClient.exchangeToken(options);
1371
1395
  }
1396
+ /**
1397
+ * Requests a Session Transfer Token (STT) for impersonation via session transfer (RFC 8693).
1398
+ *
1399
+ * Performs a Custom Token Exchange against the `urn:{domain}:session_transfer` audience and
1400
+ * returns the resulting STT. The audience is built from the SDK's resolved request domain, so
1401
+ * it is correct under multiple custom domains. The returned STT is opaque and single-use — hand
1402
+ * it to {@link ServerClient.buildSessionTransferRedirect} and do not decode, cache, or persist
1403
+ * it. This method writes nothing to the state store for the STT itself; the `act` claim is not
1404
+ * on the result — it only appears on the target session's tokens once the STT is redeemed.
1405
+ *
1406
+ * An actor is mandatory for an STT (this is what makes it auditable impersonation). It is
1407
+ * resolved in this order: an explicit `options.actor` wins, in which case the session is not
1408
+ * read at all; otherwise the current agent session's ID token is used, refreshed when it has
1409
+ * expired; if neither is available the method throws before any network call. An explicit actor
1410
+ * token carrying the default ID token type must satisfy Auth0's own validation — see
1411
+ * {@link SessionTransferActor.token}.
1412
+ *
1413
+ * When `organization` is provided it is sent on the exchange, so the tenant validates it while
1414
+ * minting. This is a separate parameter from the `organization` passed to
1415
+ * {@link ServerClient.buildSessionTransferRedirect}, which is forwarded to the target's
1416
+ * `/authorize` on the redirect.
1417
+ *
1418
+ * @param options Options including the developer-supplied `subjectToken`/`subjectTokenType` and an optional explicit `actor`.
1419
+ * @param storeOptions Optional options used to read the agent session (for the actor) and resolve the request domain.
1420
+ *
1421
+ * @throws {TokenExchangeError} With code `actor_unavailable` when no explicit actor is given and no usable session ID token can be resolved — no logged-in agent, a session that belongs to a different domain in resolver mode, or an expired ID token that cannot be refreshed (raised client-side, before any network call). With the default code when the exchange itself fails; a server-side `setactor_required` or `session_transfer_disabled` condition is surfaced via `cause.error` / `cause.error_description`. An organization the tenant rejects also surfaces here.
1422
+ * @throws {MissingClientAuthError} When client credentials are not configured (STT requires a confidential client).
1423
+ * @throws {MissingRequiredArgumentError} When `subjectToken` or `subjectTokenType` is missing or blank (raised before any session read or network call).
1424
+ * @throws {OrganizationValidationError} When `organization` is provided but blank (raised before any session read or network call).
1425
+ *
1426
+ * @returns A promise resolving to a {@link SessionTransferTokenResult} containing the STT and its metadata.
1427
+ */
1428
+ async requestSessionTransferToken(options, storeOptions) {
1429
+ if (!options.subjectToken || !options.subjectToken.trim()) {
1430
+ throw new MissingRequiredArgumentError("subjectToken");
1431
+ }
1432
+ if (!options.subjectTokenType || !options.subjectTokenType.trim()) {
1433
+ throw new MissingRequiredArgumentError("subjectTokenType");
1434
+ }
1435
+ if (options.organization !== void 0 && !options.organization.trim()) {
1436
+ throw new OrganizationValidationError("organization must not be blank");
1437
+ }
1438
+ const domain = await this.#resolveDomain(storeOptions);
1439
+ const actor = await this.#resolveSessionTransferActor(options.actor, domain, storeOptions);
1440
+ const authClient = this.#getAuthClient(domain);
1441
+ const response = await authClient.exchangeToken({
1442
+ subjectToken: options.subjectToken,
1443
+ subjectTokenType: options.subjectTokenType,
1444
+ audience: `urn:${domain}:session_transfer`,
1445
+ scope: options.scope,
1446
+ actorToken: actor.token,
1447
+ actorTokenType: actor.type,
1448
+ // Forwarded so the tenant validates the organization against the client's organization
1449
+ // settings while minting, rather than the STT being issued without it. Separate from the
1450
+ // `organization` on `buildSessionTransferRedirect`, which goes to the target's
1451
+ // `/authorize`; neither implies the other.
1452
+ organization: options.organization,
1453
+ extra: options.extra
1454
+ });
1455
+ return {
1456
+ sessionTransferToken: response.accessToken,
1457
+ // Surface exactly what the server returned — never fabricate the URN, so a non-STT
1458
+ // response is not mislabelled as an STT.
1459
+ issuedTokenType: response.issuedTokenType ?? "",
1460
+ // `expiresAt` is NaN when the server omitted `expires_in`; fall back to 0 rather than
1461
+ // surfacing NaN to callers.
1462
+ expiresIn: Number.isFinite(response.expiresAt) ? Math.max(0, Math.floor(response.expiresAt - Date.now() / 1e3)) : 0,
1463
+ tokenType: response.tokenType,
1464
+ scope: response.scope
1465
+ };
1466
+ }
1467
+ /**
1468
+ * Builds the redirect URL that hands a Session Transfer Token (STT) to the target app's login URL.
1469
+ *
1470
+ * Returns `targetLoginUrl` with `session_transfer_token` (and `organization`, when provided)
1471
+ * appended as query parameters, URL-encoded. This performs no network call and writes nothing
1472
+ * to the session — it only builds a string. The developer hands the returned URL to their
1473
+ * framework's redirect.
1474
+ *
1475
+ * `targetLoginUrl` attaches a single-use credential, so it must be a trusted, app-controlled
1476
+ * value — never derived from untrusted input (e.g. a `returnTo`), or the token could leak to an
1477
+ * attacker host. To harden against that, the URL must be absolute and use `https:` (an `http:`
1478
+ * URL is accepted only for `localhost` / loopback, to support local development).
1479
+ *
1480
+ * @param targetLoginUrl The target app's login URL (absolute, https).
1481
+ * @param result The {@link SessionTransferTokenResult} from {@link ServerClient.requestSessionTransferToken}.
1482
+ * @param options Optional options, e.g. the `organization` to forward when the STT is org-scoped.
1483
+ *
1484
+ * @throws {MissingRequiredArgumentError} When `targetLoginUrl` is missing or blank.
1485
+ * @throws {InvalidConfigurationError} When `targetLoginUrl` is not an absolute URL, or does not use `https:` (except for loopback hosts).
1486
+ *
1487
+ * @returns A {@link URL} with the STT (and optional organization) as query parameters.
1488
+ */
1489
+ buildSessionTransferRedirect(targetLoginUrl, result, options) {
1490
+ if (!targetLoginUrl || !targetLoginUrl.trim()) {
1491
+ throw new MissingRequiredArgumentError("targetLoginUrl");
1492
+ }
1493
+ let url;
1494
+ try {
1495
+ url = new URL(targetLoginUrl);
1496
+ } catch {
1497
+ throw new InvalidConfigurationError(
1498
+ "targetLoginUrl must be an absolute URL (e.g. https://app.example.com/auth/login)."
1499
+ );
1500
+ }
1501
+ const isLoopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
1502
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) {
1503
+ throw new InvalidConfigurationError(
1504
+ "targetLoginUrl must use https (http is allowed only for localhost/loopback). The session transfer token is a single-use credential and must not be sent over an insecure or untrusted URL."
1505
+ );
1506
+ }
1507
+ url.searchParams.set("session_transfer_token", result.sessionTransferToken);
1508
+ if (options?.organization !== void 0) {
1509
+ if (!options.organization.trim()) {
1510
+ throw new OrganizationValidationError("organization must not be blank");
1511
+ }
1512
+ url.searchParams.set("organization", options.organization);
1513
+ }
1514
+ return url;
1515
+ }
1516
+ /**
1517
+ * Resolves the actor token for a Session Transfer Token request.
1518
+ *
1519
+ * An explicit actor wins. Otherwise the agent session's ID token is used, refreshed when it has
1520
+ * expired (and the refreshed session is persisted so the agent session stays coherent). If no
1521
+ * usable ID token can be obtained, throws a `TokenExchangeError` with code `actor_unavailable`
1522
+ * before any exchange is attempted.
1523
+ */
1524
+ async #resolveSessionTransferActor(actor, domain, storeOptions) {
1525
+ if (actor !== void 0) {
1526
+ if (!actor.token || !actor.token.trim()) {
1527
+ throw actorUnavailableError(
1528
+ "Unable to resolve an actor for the session transfer token: an explicit actor was provided but its token is blank. Pass a non-blank actor token, or omit `actor` to source it from the agent session."
1529
+ );
1530
+ }
1531
+ return { token: actor.token, type: actor.type ?? ID_TOKEN_TYPE };
1532
+ }
1533
+ const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
1534
+ if (!stateData || !stateData.idToken) {
1535
+ throw actorUnavailableError(
1536
+ "Unable to resolve an actor for the session transfer token: no actor was provided and there is no logged-in agent session. Pass an explicit actor or ensure the agent is logged in."
1537
+ );
1538
+ }
1539
+ if (this.#isResolverMode() && !await this.#isSessionForCurrentDomain(stateData, storeOptions)) {
1540
+ throw actorUnavailableError(
1541
+ "Unable to resolve an actor for the session transfer token: the agent session belongs to a different domain than the one resolved for this request. Pass an explicit actor or ensure the agent is logged in on this domain."
1542
+ );
1543
+ }
1544
+ if (!isTokenExpired(stateData.idToken)) {
1545
+ return { token: stateData.idToken, type: ID_TOKEN_TYPE };
1546
+ }
1547
+ if (!stateData.refreshToken) {
1548
+ throw actorUnavailableError(
1549
+ "Unable to resolve an actor for the session transfer token: the agent session ID token has expired and no refresh token is available to refresh it. Pass an explicit actor or re-authenticate the agent."
1550
+ );
1551
+ }
1552
+ const sessionDomain = this.#getSessionDomain(stateData) ?? domain;
1553
+ let tokenEndpointResponse;
1554
+ try {
1555
+ tokenEndpointResponse = await this.#getAuthClient(sessionDomain).getTokenByRefreshToken({
1556
+ refreshToken: stateData.refreshToken
1557
+ });
1558
+ } catch {
1559
+ throw actorUnavailableError(
1560
+ "Unable to resolve an actor for the session transfer token: refreshing the agent session ID token failed. Pass an explicit actor or re-authenticate the agent."
1561
+ );
1562
+ }
1563
+ if (!tokenEndpointResponse.idToken) {
1564
+ throw actorUnavailableError(
1565
+ "Unable to resolve an actor for the session transfer token: refreshing the agent session did not return an ID token. Pass an explicit actor or re-authenticate the agent."
1566
+ );
1567
+ }
1568
+ const audience = this.#options.authorizationParams?.audience ?? "default";
1569
+ const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
1570
+ const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse, {
1571
+ domain: sessionDomain
1572
+ });
1573
+ await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
1574
+ return { token: tokenEndpointResponse.idToken, type: ID_TOKEN_TYPE };
1575
+ }
1372
1576
  /**
1373
1577
  * Handles the backchannel logout process by verifying the logout token and deleting the session from the store if the logout token was considered valid.
1374
1578
  * @param logoutToken The logout token to verify and use to delete the session from the store.
@@ -1523,7 +1727,15 @@ var AbstractTransactionStore = class extends AbstractStore {
1523
1727
  };
1524
1728
 
1525
1729
  // src/index.ts
1526
- import { TokenExchangeError, TokenRevocationError, MissingClientAuthError, OrganizationValidationError as OrganizationValidationError3 } from "@auth0/auth0-auth-js";
1730
+ import {
1731
+ TokenExchangeError as TokenExchangeError2,
1732
+ TokenRevocationError,
1733
+ MissingClientAuthError,
1734
+ OrganizationValidationError as OrganizationValidationError3,
1735
+ PasswordlessStartError as PasswordlessStartError2,
1736
+ PasswordlessVerifyError as PasswordlessVerifyError2,
1737
+ isMfaRequiredError as isMfaRequiredError2
1738
+ } from "@auth0/auth0-auth-js";
1527
1739
 
1528
1740
  // src/store/cookie-transaction-store.ts
1529
1741
  var CookieTransactionStore = class extends AbstractTransactionStore {
@@ -1758,6 +1970,8 @@ export {
1758
1970
  PasskeyChallengeError,
1759
1971
  PasskeyGetTokenError,
1760
1972
  PasskeyRegisterError,
1973
+ PasswordlessStartError2 as PasswordlessStartError,
1974
+ PasswordlessVerifyError2 as PasswordlessVerifyError,
1761
1975
  ServerClient,
1762
1976
  ServerDatabaseClient,
1763
1977
  ServerMfaClient,
@@ -1767,8 +1981,9 @@ export {
1767
1981
  StartLinkUserError,
1768
1982
  StatefulStateStore,
1769
1983
  StatelessStateStore,
1770
- TokenExchangeError,
1984
+ TokenExchangeError2 as TokenExchangeError,
1985
+ TokenExchangeErrorCode,
1771
1986
  TokenRevocationError,
1772
- isMfaRequiredError
1987
+ isMfaRequiredError2 as isMfaRequiredError
1773
1988
  };
1774
1989
  //# sourceMappingURL=index.js.map