@auth0/auth0-server-js 1.9.0 → 1.11.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 +40 -0
- package/dist/index.cjs +329 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +273 -3
- package/dist/index.d.ts +273 -3
- package/dist/index.js +330 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -256,7 +256,47 @@ fastify.get('/auth/logout', async (request, reply) => {
|
|
|
256
256
|
> [!IMPORTANT]
|
|
257
257
|
> You will need to register the `RETURN_TO` in your Auth0 Application as an **Allowed Logout URLs** via the [Auth0 Dashboard](https://manage.auth0.com):
|
|
258
258
|
|
|
259
|
+
### 6. Database Connections (Sign-up & Change Password)
|
|
259
260
|
|
|
261
|
+
The `ServerClient` exposes a `database` sub-client with `signUp` and `changePassword` for self-service registration and password-reset requests against an Auth0 [database connection](https://auth0.com/docs/authenticate/database-connections). These are **pure passthrough** operations to the underlying Authentication API — they do **not** read or write the session/state store, so no store options are required for the operation itself.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { ServerClient, SignUpError, ChangePasswordError } from '@auth0/auth0-server-js';
|
|
265
|
+
|
|
266
|
+
// Register a new user
|
|
267
|
+
try {
|
|
268
|
+
const user = await auth0Client.database.signUp({
|
|
269
|
+
email: 'user@example.com',
|
|
270
|
+
password: 'a-Str0ng-Password!',
|
|
271
|
+
connection: 'Username-Password-Authentication',
|
|
272
|
+
// Optional profile fields: username, givenName, familyName, name, nickname, picture, userMetadata
|
|
273
|
+
});
|
|
274
|
+
console.log(user.id); // normalized identifier; may be undefined if the server omits one
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (error instanceof SignUpError) {
|
|
277
|
+
console.error(error.code, error.message, error.cause);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Request a password-change email
|
|
282
|
+
try {
|
|
283
|
+
const message = await auth0Client.database.changePassword({
|
|
284
|
+
email: 'user@example.com', // or `username: 'jane'` for username-only connections
|
|
285
|
+
connection: 'Username-Password-Authentication',
|
|
286
|
+
// Optional: organization
|
|
287
|
+
});
|
|
288
|
+
console.log(message); // plain-text confirmation from the server
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error instanceof ChangePasswordError) {
|
|
291
|
+
console.error(error.code, error.message);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
> [!IMPORTANT]
|
|
297
|
+
> These call the public `/dbconnections/*` endpoints, which send only `clientId` (never a client secret). `changePassword` resolves to a **plain-text** confirmation string. Neither method reads or writes the session/state store, so you can call them outside an authenticated request context. Domain selection is separate from session state: in static mode the constructor-configured `domain` is used, and in resolver (multi-tenant) mode the `domain` resolver still runs per call — pass `storeOptions` so it can select the intended tenant.
|
|
298
|
+
|
|
299
|
+
For full options and error handling, see the [Database Connections section in the auth0-auth-js EXAMPLES.md](https://github.com/auth0/auth0-auth-js/blob/main/packages/auth0-auth-js/EXAMPLES.md#using-database-connections-sign-up--change-password) (the underlying database client is identical) and the runnable [`examples/database-conns`](https://github.com/auth0/auth0-auth-js/tree/main/examples/database-conns) sample.
|
|
260
300
|
|
|
261
301
|
## Feedback
|
|
262
302
|
|
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
AbstractStateStore: () => AbstractStateStore,
|
|
24
24
|
AbstractTransactionStore: () => AbstractTransactionStore,
|
|
25
25
|
BackchannelLogoutError: () => BackchannelLogoutError,
|
|
26
|
+
ChangePasswordError: () => import_auth0_auth_js4.ChangePasswordError,
|
|
26
27
|
CookieTransactionStore: () => CookieTransactionStore,
|
|
27
28
|
InvalidConfigurationError: () => InvalidConfigurationError,
|
|
28
29
|
IssuerValidationError: () => IssuerValidationError,
|
|
@@ -30,27 +31,38 @@ __export(index_exports, {
|
|
|
30
31
|
MfaEnrollmentError: () => import_auth0_auth_js2.MfaEnrollmentError,
|
|
31
32
|
MfaListAuthenticatorsError: () => import_auth0_auth_js2.MfaListAuthenticatorsError,
|
|
32
33
|
MfaVerifyError: () => import_auth0_auth_js2.MfaVerifyError,
|
|
33
|
-
MissingClientAuthError: () =>
|
|
34
|
+
MissingClientAuthError: () => import_auth0_auth_js5.MissingClientAuthError,
|
|
34
35
|
MissingRequiredArgumentError: () => MissingRequiredArgumentError,
|
|
35
36
|
MissingSessionError: () => MissingSessionError,
|
|
36
37
|
MissingTransactionError: () => MissingTransactionError,
|
|
37
|
-
OrganizationValidationError: () =>
|
|
38
|
+
OrganizationValidationError: () => import_auth0_auth_js5.OrganizationValidationError,
|
|
38
39
|
PasskeyChallengeError: () => import_auth0_auth_js3.PasskeyChallengeError,
|
|
39
40
|
PasskeyGetTokenError: () => import_auth0_auth_js3.PasskeyGetTokenError,
|
|
40
41
|
PasskeyRegisterError: () => import_auth0_auth_js3.PasskeyRegisterError,
|
|
42
|
+
PasswordlessStartError: () => import_auth0_auth_js5.PasswordlessStartError,
|
|
43
|
+
PasswordlessVerifyError: () => import_auth0_auth_js5.PasswordlessVerifyError,
|
|
41
44
|
ServerClient: () => ServerClient,
|
|
45
|
+
ServerDatabaseClient: () => ServerDatabaseClient,
|
|
42
46
|
ServerMfaClient: () => ServerMfaClient,
|
|
43
47
|
ServerPasskeyClient: () => ServerPasskeyClient,
|
|
44
48
|
SessionExpiredError: () => SessionExpiredError,
|
|
49
|
+
SignUpError: () => import_auth0_auth_js4.SignUpError,
|
|
45
50
|
StartLinkUserError: () => StartLinkUserError,
|
|
46
51
|
StatefulStateStore: () => StatefulStateStore,
|
|
47
52
|
StatelessStateStore: () => StatelessStateStore,
|
|
48
|
-
TokenExchangeError: () =>
|
|
49
|
-
|
|
53
|
+
TokenExchangeError: () => import_auth0_auth_js5.TokenExchangeError,
|
|
54
|
+
TokenExchangeErrorCode: () => TokenExchangeErrorCode,
|
|
55
|
+
TokenRevocationError: () => import_auth0_auth_js5.TokenRevocationError,
|
|
56
|
+
isMfaRequiredError: () => import_auth0_auth_js5.isMfaRequiredError
|
|
50
57
|
});
|
|
51
58
|
module.exports = __toCommonJS(index_exports);
|
|
52
59
|
|
|
53
60
|
// src/errors.ts
|
|
61
|
+
var TokenExchangeErrorCode = {
|
|
62
|
+
ACTOR_UNAVAILABLE: "actor_unavailable",
|
|
63
|
+
SETACTOR_REQUIRED: "setactor_required",
|
|
64
|
+
SESSION_TRANSFER_DISABLED: "session_transfer_disabled"
|
|
65
|
+
};
|
|
54
66
|
var MissingTransactionError = class extends Error {
|
|
55
67
|
code = "missing_transaction_error";
|
|
56
68
|
constructor(message) {
|
|
@@ -250,7 +262,7 @@ function getTelemetryConfig(config) {
|
|
|
250
262
|
return {
|
|
251
263
|
enabled: true,
|
|
252
264
|
name: config?.name ?? "@auth0/auth0-server-js",
|
|
253
|
-
version: config?.version ?? "1.
|
|
265
|
+
version: config?.version ?? "1.11.0"
|
|
254
266
|
};
|
|
255
267
|
}
|
|
256
268
|
|
|
@@ -429,6 +441,53 @@ var ServerPasskeyClient = class {
|
|
|
429
441
|
}
|
|
430
442
|
};
|
|
431
443
|
|
|
444
|
+
// src/database/server-database-client.ts
|
|
445
|
+
var ServerDatabaseClient = class {
|
|
446
|
+
#options;
|
|
447
|
+
/**
|
|
448
|
+
* @internal
|
|
449
|
+
*/
|
|
450
|
+
constructor(options) {
|
|
451
|
+
this.#options = options;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Registers a new user in a database connection.
|
|
455
|
+
*
|
|
456
|
+
* Delegates to the underlying `AuthClient.database.signUp` without any session
|
|
457
|
+
* state modification. The caller is responsible for handling the returned user
|
|
458
|
+
* data as needed.
|
|
459
|
+
*
|
|
460
|
+
* @param options The signup options (email, password, connection, etc.).
|
|
461
|
+
* @param storeOptions Optional options used to resolve the domain (resolver mode).
|
|
462
|
+
*
|
|
463
|
+
* @throws {SignUpError} If there was an issue signing the user up.
|
|
464
|
+
*
|
|
465
|
+
* @returns A promise resolving to the created user result with a normalized `id` field.
|
|
466
|
+
*/
|
|
467
|
+
async signUp(options, storeOptions) {
|
|
468
|
+
const domain = await this.#options.resolveDomain(storeOptions);
|
|
469
|
+
return this.#options.getAuthClient(domain).database.signUp(options);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Requests a password-change email for a database connection user.
|
|
473
|
+
*
|
|
474
|
+
* Delegates to the underlying `AuthClient.database.changePassword` without any
|
|
475
|
+
* session state modification. The caller is responsible for informing the user
|
|
476
|
+
* of the sent email as needed.
|
|
477
|
+
*
|
|
478
|
+
* @param options The password change options (email, connection, organization, etc.).
|
|
479
|
+
* @param storeOptions Optional options used to resolve the domain (resolver mode).
|
|
480
|
+
*
|
|
481
|
+
* @throws {ChangePasswordError} If there was an issue requesting the password change.
|
|
482
|
+
*
|
|
483
|
+
* @returns A promise resolving to the server's plain-text confirmation message.
|
|
484
|
+
*/
|
|
485
|
+
async changePassword(options, storeOptions) {
|
|
486
|
+
const domain = await this.#options.resolveDomain(storeOptions);
|
|
487
|
+
return this.#options.getAuthClient(domain).database.changePassword(options);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
432
491
|
// src/server-client.ts
|
|
433
492
|
var normalizeDomain = (value) => {
|
|
434
493
|
const trimmed = value.trim();
|
|
@@ -443,6 +502,24 @@ var decodeIssuer = (token) => {
|
|
|
443
502
|
return void 0;
|
|
444
503
|
}
|
|
445
504
|
};
|
|
505
|
+
var ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token";
|
|
506
|
+
var ID_TOKEN_EXPIRY_SKEW_SECONDS = 30;
|
|
507
|
+
var actorUnavailableError = (message) => {
|
|
508
|
+
const error = new import_auth0_auth_js.TokenExchangeError(message);
|
|
509
|
+
error.code = TokenExchangeErrorCode.ACTOR_UNAVAILABLE;
|
|
510
|
+
return error;
|
|
511
|
+
};
|
|
512
|
+
var isTokenExpired = (token) => {
|
|
513
|
+
try {
|
|
514
|
+
const { exp } = (0, import_jose.decodeJwt)(token);
|
|
515
|
+
if (typeof exp !== "number") {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
return exp <= Date.now() / 1e3 + ID_TOKEN_EXPIRY_SKEW_SECONDS;
|
|
519
|
+
} catch {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
446
523
|
var ServerClient = class {
|
|
447
524
|
#options;
|
|
448
525
|
#transactionStore;
|
|
@@ -454,6 +531,7 @@ var ServerClient = class {
|
|
|
454
531
|
#authClient;
|
|
455
532
|
#mfaClient;
|
|
456
533
|
#passkeyClient;
|
|
534
|
+
#databaseClient;
|
|
457
535
|
/**
|
|
458
536
|
* The underlying `authClient` instance that can be used to interact with the Auth0 Authentication API.
|
|
459
537
|
* Generally, you should prefer to use the higher-level methods exposed on the `ServerClient` instance.
|
|
@@ -501,6 +579,21 @@ var ServerClient = class {
|
|
|
501
579
|
get passkey() {
|
|
502
580
|
return this.#passkeyClient;
|
|
503
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* The database client for self-service sign-up and password-change requests
|
|
584
|
+
* against an Auth0 database connection.
|
|
585
|
+
*
|
|
586
|
+
* Provides `signUp()` to register a user and `changePassword()` to request a
|
|
587
|
+
* password-reset email. Both are pure passthrough operations to the Auth0
|
|
588
|
+
* Authentication API — they never read or write the session/state store.
|
|
589
|
+
*
|
|
590
|
+
* Like `passkey`, this property is available in both static and resolver
|
|
591
|
+
* (multi-tenant) domain modes. In resolver mode, pass `storeOptions` so the
|
|
592
|
+
* request resolves the intended tenant.
|
|
593
|
+
*/
|
|
594
|
+
get database() {
|
|
595
|
+
return this.#databaseClient;
|
|
596
|
+
}
|
|
504
597
|
constructor(options) {
|
|
505
598
|
this.#options = options;
|
|
506
599
|
this.#stateStoreIdentifier = this.#options.stateIdentifier || "__a0_session";
|
|
@@ -550,6 +643,10 @@ var ServerClient = class {
|
|
|
550
643
|
defaultScope: this.#options.authorizationParams?.scope,
|
|
551
644
|
defaultAudience: this.#options.authorizationParams?.audience
|
|
552
645
|
});
|
|
646
|
+
this.#databaseClient = new ServerDatabaseClient({
|
|
647
|
+
resolveDomain: (storeOptions) => this.#resolveDomain(storeOptions),
|
|
648
|
+
getAuthClient: (domain) => this.#getAuthClient(domain)
|
|
649
|
+
});
|
|
553
650
|
}
|
|
554
651
|
async #resolveDomain(storeOptions) {
|
|
555
652
|
if (typeof this.#options.domain === "function") {
|
|
@@ -1215,6 +1312,47 @@ var ServerClient = class {
|
|
|
1215
1312
|
loginHint: options.loginHint
|
|
1216
1313
|
};
|
|
1217
1314
|
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Revokes the refresh token stored in the current session, or an explicitly supplied token.
|
|
1317
|
+
*
|
|
1318
|
+
* In resolver mode, revocation only occurs when the session domain matches the domain resolved
|
|
1319
|
+
* for the current request. If the domains differ (or the session has no stored domain), the call
|
|
1320
|
+
* returns without revoking to avoid sending a token to the wrong tenant. This guard applies even
|
|
1321
|
+
* when a token is passed explicitly via `options.token`.
|
|
1322
|
+
*
|
|
1323
|
+
* @param options Optionally supply a token to revoke instead of reading from the session.
|
|
1324
|
+
* @param storeOptions Optional options passed to the StateStore.
|
|
1325
|
+
*
|
|
1326
|
+
* @throws {MissingRequiredArgumentError} If `options.token` is an empty string.
|
|
1327
|
+
* @throws {MissingSessionError} If no refresh token is found in the session and none was provided.
|
|
1328
|
+
* @throws {TokenRevocationError} If the revocation request fails.
|
|
1329
|
+
*/
|
|
1330
|
+
async revokeRefreshToken(options = {}, storeOptions) {
|
|
1331
|
+
if (options.token !== void 0 && options.token.length === 0) {
|
|
1332
|
+
throw new MissingRequiredArgumentError("options.token must not be an empty string.");
|
|
1333
|
+
}
|
|
1334
|
+
let refreshToken = options.token;
|
|
1335
|
+
const needsStateData = !refreshToken || this.#isResolverMode();
|
|
1336
|
+
const stateData = needsStateData ? await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions) : void 0;
|
|
1337
|
+
if (!refreshToken) {
|
|
1338
|
+
refreshToken = stateData?.refreshToken;
|
|
1339
|
+
}
|
|
1340
|
+
if (!refreshToken) {
|
|
1341
|
+
throw new MissingSessionError("Unable to revoke refresh token: no refresh token found in session.");
|
|
1342
|
+
}
|
|
1343
|
+
let authClient;
|
|
1344
|
+
if (this.#isResolverMode()) {
|
|
1345
|
+
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
1346
|
+
const sessionDomain = stateData ? this.#getSessionDomain(stateData) : void 0;
|
|
1347
|
+
if (stateData && sessionDomain !== resolvedDomain) {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
authClient = this.#getAuthClient(sessionDomain ?? resolvedDomain);
|
|
1351
|
+
} else {
|
|
1352
|
+
authClient = this.authClient;
|
|
1353
|
+
}
|
|
1354
|
+
await authClient.revokeToken({ token: refreshToken, tokenTypeHint: "refresh_token" });
|
|
1355
|
+
}
|
|
1218
1356
|
/**
|
|
1219
1357
|
* Logs the user out and returns a URL to redirect the user-agent to after they log out.
|
|
1220
1358
|
* @param options Options used to configure the logout process.
|
|
@@ -1223,17 +1361,26 @@ var ServerClient = class {
|
|
|
1223
1361
|
*/
|
|
1224
1362
|
async logout(options, storeOptions) {
|
|
1225
1363
|
if (!this.#isResolverMode()) {
|
|
1364
|
+
try {
|
|
1365
|
+
await this.revokeRefreshToken({}, storeOptions);
|
|
1366
|
+
} catch {
|
|
1367
|
+
}
|
|
1226
1368
|
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
1227
1369
|
return this.authClient.buildLogoutUrl(options);
|
|
1228
1370
|
}
|
|
1229
1371
|
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
1230
1372
|
const authClient = this.#getAuthClient(resolvedDomain);
|
|
1231
1373
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1232
|
-
const sessionDomain = stateData ? this.#getSessionDomain(stateData) : void 0;
|
|
1233
1374
|
if (!stateData) {
|
|
1234
1375
|
return authClient.buildLogoutUrl(options);
|
|
1235
1376
|
}
|
|
1236
|
-
|
|
1377
|
+
const sessionDomain = this.#getSessionDomain(stateData);
|
|
1378
|
+
const domainMatches = sessionDomain === resolvedDomain;
|
|
1379
|
+
if (domainMatches) {
|
|
1380
|
+
try {
|
|
1381
|
+
await this.revokeRefreshToken({}, storeOptions);
|
|
1382
|
+
} catch {
|
|
1383
|
+
}
|
|
1237
1384
|
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
1238
1385
|
}
|
|
1239
1386
|
return authClient.buildLogoutUrl(options);
|
|
@@ -1297,6 +1444,170 @@ var ServerClient = class {
|
|
|
1297
1444
|
const authClient = this.#getAuthClient(domain);
|
|
1298
1445
|
return authClient.exchangeToken(options);
|
|
1299
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Requests a Session Transfer Token (STT) for impersonation via session transfer (RFC 8693).
|
|
1449
|
+
*
|
|
1450
|
+
* Performs a Custom Token Exchange against the `urn:{domain}:session_transfer` audience and
|
|
1451
|
+
* returns the resulting STT. The audience is built from the SDK's resolved request domain, so
|
|
1452
|
+
* it is correct under multiple custom domains. The returned STT is opaque and single-use — hand
|
|
1453
|
+
* it to {@link ServerClient.buildSessionTransferRedirect} and do not decode, cache, or persist
|
|
1454
|
+
* it. This method writes nothing to the state store for the STT itself; the `act` claim is not
|
|
1455
|
+
* on the result — it only appears on the target session's tokens once the STT is redeemed.
|
|
1456
|
+
*
|
|
1457
|
+
* An actor is mandatory for an STT (this is what makes it auditable impersonation). It is
|
|
1458
|
+
* resolved in this order: an explicit `options.actor` wins; otherwise the current agent
|
|
1459
|
+
* session's ID token is used, refreshed when it has expired; if neither is available the method
|
|
1460
|
+
* throws before any network call.
|
|
1461
|
+
*
|
|
1462
|
+
* @param options Options including the developer-supplied `subjectToken`/`subjectTokenType` and an optional explicit `actor`.
|
|
1463
|
+
* @param storeOptions Optional options used to read the agent session (for the actor) and resolve the request domain.
|
|
1464
|
+
*
|
|
1465
|
+
* @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`.
|
|
1466
|
+
* @throws {MissingClientAuthError} When client credentials are not configured (STT requires a confidential client).
|
|
1467
|
+
* @throws {MissingRequiredArgumentError} When `subjectToken` or `subjectTokenType` is missing or blank (raised before any session read or network call).
|
|
1468
|
+
*
|
|
1469
|
+
* @returns A promise resolving to a {@link SessionTransferTokenResult} containing the STT and its metadata.
|
|
1470
|
+
*/
|
|
1471
|
+
async requestSessionTransferToken(options, storeOptions) {
|
|
1472
|
+
if (!options.subjectToken || !options.subjectToken.trim()) {
|
|
1473
|
+
throw new MissingRequiredArgumentError("subjectToken");
|
|
1474
|
+
}
|
|
1475
|
+
if (!options.subjectTokenType || !options.subjectTokenType.trim()) {
|
|
1476
|
+
throw new MissingRequiredArgumentError("subjectTokenType");
|
|
1477
|
+
}
|
|
1478
|
+
const domain = await this.#resolveDomain(storeOptions);
|
|
1479
|
+
const actor = await this.#resolveSessionTransferActor(options.actor, domain, storeOptions);
|
|
1480
|
+
const authClient = this.#getAuthClient(domain);
|
|
1481
|
+
const response = await authClient.exchangeToken({
|
|
1482
|
+
subjectToken: options.subjectToken,
|
|
1483
|
+
subjectTokenType: options.subjectTokenType,
|
|
1484
|
+
audience: `urn:${domain}:session_transfer`,
|
|
1485
|
+
scope: options.scope,
|
|
1486
|
+
actorToken: actor.token,
|
|
1487
|
+
actorTokenType: actor.type,
|
|
1488
|
+
extra: options.extra
|
|
1489
|
+
});
|
|
1490
|
+
return {
|
|
1491
|
+
sessionTransferToken: response.accessToken,
|
|
1492
|
+
// Surface exactly what the server returned — never fabricate the URN, so a non-STT
|
|
1493
|
+
// response is not mislabelled as an STT.
|
|
1494
|
+
issuedTokenType: response.issuedTokenType ?? "",
|
|
1495
|
+
// `expiresAt` is NaN when the server omitted `expires_in`; fall back to 0 rather than
|
|
1496
|
+
// surfacing NaN to callers.
|
|
1497
|
+
expiresIn: Number.isFinite(response.expiresAt) ? Math.max(0, Math.floor(response.expiresAt - Date.now() / 1e3)) : 0,
|
|
1498
|
+
tokenType: response.tokenType,
|
|
1499
|
+
scope: response.scope
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Builds the redirect URL that hands a Session Transfer Token (STT) to the target app's login URL.
|
|
1504
|
+
*
|
|
1505
|
+
* Returns `targetLoginUrl` with `session_transfer_token` (and `organization`, when provided)
|
|
1506
|
+
* appended as query parameters, URL-encoded. This performs no network call and writes nothing
|
|
1507
|
+
* to the session — it only builds a string. The developer hands the returned URL to their
|
|
1508
|
+
* framework's redirect.
|
|
1509
|
+
*
|
|
1510
|
+
* `targetLoginUrl` attaches a single-use credential, so it must be a trusted, app-controlled
|
|
1511
|
+
* value — never derived from untrusted input (e.g. a `returnTo`), or the token could leak to an
|
|
1512
|
+
* attacker host. To harden against that, the URL must be absolute and use `https:` (an `http:`
|
|
1513
|
+
* URL is accepted only for `localhost` / loopback, to support local development).
|
|
1514
|
+
*
|
|
1515
|
+
* @param targetLoginUrl The target app's login URL (absolute, https).
|
|
1516
|
+
* @param result The {@link SessionTransferTokenResult} from {@link ServerClient.requestSessionTransferToken}.
|
|
1517
|
+
* @param options Optional options, e.g. the `organization` to forward when the STT is org-scoped.
|
|
1518
|
+
*
|
|
1519
|
+
* @throws {MissingRequiredArgumentError} When `targetLoginUrl` is missing or blank.
|
|
1520
|
+
* @throws {InvalidConfigurationError} When `targetLoginUrl` is not an absolute URL, or does not use `https:` (except for loopback hosts).
|
|
1521
|
+
*
|
|
1522
|
+
* @returns A {@link URL} with the STT (and optional organization) as query parameters.
|
|
1523
|
+
*/
|
|
1524
|
+
buildSessionTransferRedirect(targetLoginUrl, result, options) {
|
|
1525
|
+
if (!targetLoginUrl || !targetLoginUrl.trim()) {
|
|
1526
|
+
throw new MissingRequiredArgumentError("targetLoginUrl");
|
|
1527
|
+
}
|
|
1528
|
+
let url;
|
|
1529
|
+
try {
|
|
1530
|
+
url = new URL(targetLoginUrl);
|
|
1531
|
+
} catch {
|
|
1532
|
+
throw new InvalidConfigurationError(
|
|
1533
|
+
"targetLoginUrl must be an absolute URL (e.g. https://app.example.com/auth/login)."
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
const isLoopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
1537
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) {
|
|
1538
|
+
throw new InvalidConfigurationError(
|
|
1539
|
+
"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."
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
url.searchParams.set("session_transfer_token", result.sessionTransferToken);
|
|
1543
|
+
if (options?.organization !== void 0) {
|
|
1544
|
+
if (!options.organization.trim()) {
|
|
1545
|
+
throw new import_auth0_auth_js.OrganizationValidationError("organization must not be blank");
|
|
1546
|
+
}
|
|
1547
|
+
url.searchParams.set("organization", options.organization);
|
|
1548
|
+
}
|
|
1549
|
+
return url;
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Resolves the actor token for a Session Transfer Token request.
|
|
1553
|
+
*
|
|
1554
|
+
* An explicit actor wins. Otherwise the agent session's ID token is used, refreshed when it has
|
|
1555
|
+
* expired (and the refreshed session is persisted so the agent session stays coherent). If no
|
|
1556
|
+
* usable ID token can be obtained, throws a `TokenExchangeError` with code `actor_unavailable`
|
|
1557
|
+
* before any exchange is attempted.
|
|
1558
|
+
*/
|
|
1559
|
+
async #resolveSessionTransferActor(actor, domain, storeOptions) {
|
|
1560
|
+
if (actor !== void 0) {
|
|
1561
|
+
if (!actor.token || !actor.token.trim()) {
|
|
1562
|
+
throw actorUnavailableError(
|
|
1563
|
+
"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."
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
return { token: actor.token, type: actor.type ?? ID_TOKEN_TYPE };
|
|
1567
|
+
}
|
|
1568
|
+
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1569
|
+
if (!stateData || !stateData.idToken) {
|
|
1570
|
+
throw actorUnavailableError(
|
|
1571
|
+
"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."
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
if (this.#isResolverMode() && !await this.#isSessionForCurrentDomain(stateData, storeOptions)) {
|
|
1575
|
+
throw actorUnavailableError(
|
|
1576
|
+
"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."
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
if (!isTokenExpired(stateData.idToken)) {
|
|
1580
|
+
return { token: stateData.idToken, type: ID_TOKEN_TYPE };
|
|
1581
|
+
}
|
|
1582
|
+
if (!stateData.refreshToken) {
|
|
1583
|
+
throw actorUnavailableError(
|
|
1584
|
+
"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."
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
const sessionDomain = this.#getSessionDomain(stateData) ?? domain;
|
|
1588
|
+
let tokenEndpointResponse;
|
|
1589
|
+
try {
|
|
1590
|
+
tokenEndpointResponse = await this.#getAuthClient(sessionDomain).getTokenByRefreshToken({
|
|
1591
|
+
refreshToken: stateData.refreshToken
|
|
1592
|
+
});
|
|
1593
|
+
} catch {
|
|
1594
|
+
throw actorUnavailableError(
|
|
1595
|
+
"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."
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
if (!tokenEndpointResponse.idToken) {
|
|
1599
|
+
throw actorUnavailableError(
|
|
1600
|
+
"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."
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
const audience = this.#options.authorizationParams?.audience ?? "default";
|
|
1604
|
+
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1605
|
+
const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse, {
|
|
1606
|
+
domain: sessionDomain
|
|
1607
|
+
});
|
|
1608
|
+
await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
|
|
1609
|
+
return { token: tokenEndpointResponse.idToken, type: ID_TOKEN_TYPE };
|
|
1610
|
+
}
|
|
1300
1611
|
/**
|
|
1301
1612
|
* Handles the backchannel logout process by verifying the logout token and deleting the session from the store if the logout token was considered valid.
|
|
1302
1613
|
* @param logoutToken The logout token to verify and use to delete the session from the store.
|
|
@@ -1451,7 +1762,7 @@ var AbstractTransactionStore = class extends AbstractStore {
|
|
|
1451
1762
|
};
|
|
1452
1763
|
|
|
1453
1764
|
// src/index.ts
|
|
1454
|
-
var
|
|
1765
|
+
var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
|
|
1455
1766
|
|
|
1456
1767
|
// src/store/cookie-transaction-store.ts
|
|
1457
1768
|
var CookieTransactionStore = class extends AbstractTransactionStore {
|
|
@@ -1652,11 +1963,15 @@ var import_auth0_auth_js2 = require("@auth0/auth0-auth-js");
|
|
|
1652
1963
|
|
|
1653
1964
|
// src/passkey/index.ts
|
|
1654
1965
|
var import_auth0_auth_js3 = require("@auth0/auth0-auth-js");
|
|
1966
|
+
|
|
1967
|
+
// src/database/index.ts
|
|
1968
|
+
var import_auth0_auth_js4 = require("@auth0/auth0-auth-js");
|
|
1655
1969
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1656
1970
|
0 && (module.exports = {
|
|
1657
1971
|
AbstractStateStore,
|
|
1658
1972
|
AbstractTransactionStore,
|
|
1659
1973
|
BackchannelLogoutError,
|
|
1974
|
+
ChangePasswordError,
|
|
1660
1975
|
CookieTransactionStore,
|
|
1661
1976
|
InvalidConfigurationError,
|
|
1662
1977
|
IssuerValidationError,
|
|
@@ -1672,14 +1987,20 @@ var import_auth0_auth_js3 = require("@auth0/auth0-auth-js");
|
|
|
1672
1987
|
PasskeyChallengeError,
|
|
1673
1988
|
PasskeyGetTokenError,
|
|
1674
1989
|
PasskeyRegisterError,
|
|
1990
|
+
PasswordlessStartError,
|
|
1991
|
+
PasswordlessVerifyError,
|
|
1675
1992
|
ServerClient,
|
|
1993
|
+
ServerDatabaseClient,
|
|
1676
1994
|
ServerMfaClient,
|
|
1677
1995
|
ServerPasskeyClient,
|
|
1678
1996
|
SessionExpiredError,
|
|
1997
|
+
SignUpError,
|
|
1679
1998
|
StartLinkUserError,
|
|
1680
1999
|
StatefulStateStore,
|
|
1681
2000
|
StatelessStateStore,
|
|
1682
2001
|
TokenExchangeError,
|
|
2002
|
+
TokenExchangeErrorCode,
|
|
2003
|
+
TokenRevocationError,
|
|
1683
2004
|
isMfaRequiredError
|
|
1684
2005
|
});
|
|
1685
2006
|
//# sourceMappingURL=index.cjs.map
|