@auth0/auth0-server-js 1.15.0 → 1.16.1
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 +38 -0
- package/dist/index.cjs +539 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +519 -59
- package/dist/index.d.ts +519 -59
- package/dist/index.js +488 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,43 +20,50 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
AbstractAnonymousStore: () => AbstractAnonymousStore,
|
|
23
24
|
AbstractStateStore: () => AbstractStateStore,
|
|
24
25
|
AbstractTransactionStore: () => AbstractTransactionStore,
|
|
26
|
+
AnonymousSessionError: () => import_auth0_auth_js7.AnonymousSessionError,
|
|
27
|
+
AnonymousSessionExpiredError: () => AnonymousSessionExpiredError,
|
|
25
28
|
BackchannelLogoutError: () => BackchannelLogoutError,
|
|
26
|
-
ChangePasswordError: () =>
|
|
29
|
+
ChangePasswordError: () => import_auth0_auth_js6.ChangePasswordError,
|
|
27
30
|
CookieTransactionStore: () => CookieTransactionStore,
|
|
28
|
-
|
|
31
|
+
DEFAULT_ANONYMOUS_SESSION_LIFETIME: () => DEFAULT_ANONYMOUS_SESSION_LIFETIME,
|
|
32
|
+
EnterpriseConnectNotSupportedError: () => import_auth0_auth_js8.EnterpriseConnectNotSupportedError,
|
|
29
33
|
InvalidConfigurationError: () => InvalidConfigurationError,
|
|
30
34
|
IssuerValidationError: () => IssuerValidationError,
|
|
31
|
-
MfaChallengeError: () =>
|
|
32
|
-
MfaEnrollmentError: () =>
|
|
33
|
-
MfaListAuthenticatorsError: () =>
|
|
34
|
-
MfaVerifyError: () =>
|
|
35
|
-
|
|
35
|
+
MfaChallengeError: () => import_auth0_auth_js4.MfaChallengeError,
|
|
36
|
+
MfaEnrollmentError: () => import_auth0_auth_js4.MfaEnrollmentError,
|
|
37
|
+
MfaListAuthenticatorsError: () => import_auth0_auth_js4.MfaListAuthenticatorsError,
|
|
38
|
+
MfaVerifyError: () => import_auth0_auth_js4.MfaVerifyError,
|
|
39
|
+
MissingAnonymousSessionError: () => MissingAnonymousSessionError,
|
|
40
|
+
MissingClientAuthError: () => import_auth0_auth_js8.MissingClientAuthError,
|
|
36
41
|
MissingRequiredArgumentError: () => MissingRequiredArgumentError,
|
|
37
42
|
MissingSessionError: () => MissingSessionError,
|
|
38
43
|
MissingTransactionError: () => MissingTransactionError,
|
|
39
|
-
OrganizationValidationError: () =>
|
|
40
|
-
PasskeyChallengeError: () =>
|
|
41
|
-
PasskeyGetTokenError: () =>
|
|
42
|
-
PasskeyRegisterError: () =>
|
|
43
|
-
PasswordlessStartError: () =>
|
|
44
|
-
PasswordlessVerifyError: () =>
|
|
44
|
+
OrganizationValidationError: () => import_auth0_auth_js8.OrganizationValidationError,
|
|
45
|
+
PasskeyChallengeError: () => import_auth0_auth_js5.PasskeyChallengeError,
|
|
46
|
+
PasskeyGetTokenError: () => import_auth0_auth_js5.PasskeyGetTokenError,
|
|
47
|
+
PasskeyRegisterError: () => import_auth0_auth_js5.PasskeyRegisterError,
|
|
48
|
+
PasswordlessStartError: () => import_auth0_auth_js8.PasswordlessStartError,
|
|
49
|
+
PasswordlessVerifyError: () => import_auth0_auth_js8.PasswordlessVerifyError,
|
|
50
|
+
ServerAnonymousClient: () => ServerAnonymousClient,
|
|
45
51
|
ServerClient: () => ServerClient,
|
|
46
52
|
ServerDatabaseClient: () => ServerDatabaseClient,
|
|
47
53
|
ServerMfaClient: () => ServerMfaClient,
|
|
48
54
|
ServerPasskeyClient: () => ServerPasskeyClient,
|
|
49
55
|
SessionExpiredError: () => SessionExpiredError,
|
|
50
|
-
SignUpError: () =>
|
|
56
|
+
SignUpError: () => import_auth0_auth_js6.SignUpError,
|
|
51
57
|
StartLinkUserError: () => StartLinkUserError,
|
|
52
58
|
StatefulStateStore: () => StatefulStateStore,
|
|
59
|
+
StatelessAnonymousStore: () => StatelessAnonymousStore,
|
|
53
60
|
StatelessStateStore: () => StatelessStateStore,
|
|
54
|
-
TokenExchangeError: () =>
|
|
61
|
+
TokenExchangeError: () => import_auth0_auth_js8.TokenExchangeError,
|
|
55
62
|
TokenExchangeErrorCode: () => TokenExchangeErrorCode,
|
|
56
|
-
TokenRevocationError: () =>
|
|
57
|
-
UserInfoError: () =>
|
|
58
|
-
isFederatedDomain: () =>
|
|
59
|
-
isMfaRequiredError: () =>
|
|
63
|
+
TokenRevocationError: () => import_auth0_auth_js8.TokenRevocationError,
|
|
64
|
+
UserInfoError: () => import_auth0_auth_js8.UserInfoError,
|
|
65
|
+
isFederatedDomain: () => import_auth0_auth_js8.isFederatedDomain,
|
|
66
|
+
isMfaRequiredError: () => import_auth0_auth_js8.isMfaRequiredError
|
|
60
67
|
});
|
|
61
68
|
module.exports = __toCommonJS(index_exports);
|
|
62
69
|
|
|
@@ -115,6 +122,22 @@ var IssuerValidationError = class extends Error {
|
|
|
115
122
|
this.name = "IssuerValidationError";
|
|
116
123
|
}
|
|
117
124
|
};
|
|
125
|
+
var MissingAnonymousSessionError = class extends Error {
|
|
126
|
+
code = "missing_anonymous_session_error";
|
|
127
|
+
constructor(message) {
|
|
128
|
+
super(message ?? "There is no anonymous session. Call `anonymous.createSession()` first.");
|
|
129
|
+
this.name = "MissingAnonymousSessionError";
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
var AnonymousSessionExpiredError = class extends Error {
|
|
133
|
+
code = "anonymous_session_expired";
|
|
134
|
+
constructor(message) {
|
|
135
|
+
super(
|
|
136
|
+
message ?? "The anonymous session has expired or is no longer valid. Call `anonymous.createSession()` to start a new one."
|
|
137
|
+
);
|
|
138
|
+
this.name = "AnonymousSessionExpiredError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
118
141
|
var SessionExpiredError = class extends Error {
|
|
119
142
|
code = "session_expired";
|
|
120
143
|
constructor(message) {
|
|
@@ -227,7 +250,7 @@ function updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointR
|
|
|
227
250
|
}
|
|
228
251
|
|
|
229
252
|
// src/server-client.ts
|
|
230
|
-
var
|
|
253
|
+
var import_auth0_auth_js3 = require("@auth0/auth0-auth-js");
|
|
231
254
|
|
|
232
255
|
// src/utils.ts
|
|
233
256
|
var DEFAULT_SCOPES = "openid profile email offline_access";
|
|
@@ -255,7 +278,7 @@ var compareScopes = (scopes, requiredScopes) => {
|
|
|
255
278
|
};
|
|
256
279
|
|
|
257
280
|
// src/server-client.ts
|
|
258
|
-
var
|
|
281
|
+
var import_jose2 = require("jose");
|
|
259
282
|
|
|
260
283
|
// src/telemetry.ts
|
|
261
284
|
function getTelemetryConfig(config) {
|
|
@@ -265,7 +288,7 @@ function getTelemetryConfig(config) {
|
|
|
265
288
|
return {
|
|
266
289
|
enabled: true,
|
|
267
290
|
name: config?.name ?? "@auth0/auth0-server-js",
|
|
268
|
-
version: config?.version ?? "1.
|
|
291
|
+
version: config?.version ?? "1.16.1"
|
|
269
292
|
};
|
|
270
293
|
}
|
|
271
294
|
|
|
@@ -339,6 +362,7 @@ var ServerMfaClient = class {
|
|
|
339
362
|
true,
|
|
340
363
|
storeOptions
|
|
341
364
|
);
|
|
365
|
+
await this.#options.onUserSessionEstablished?.(storeOptions);
|
|
342
366
|
const result = {
|
|
343
367
|
accessToken: tokenResponse.accessToken,
|
|
344
368
|
tokenType: tokenResponse.tokenType ?? "bearer",
|
|
@@ -447,6 +471,7 @@ var ServerPasskeyClient = class {
|
|
|
447
471
|
const existingStateData = await this.#options.stateStore.get(this.#options.stateStoreIdentifier, storeOptions);
|
|
448
472
|
const stateData = updateStateData(audience ?? "default", existingStateData, tokenEndpointResponse, { domain });
|
|
449
473
|
await this.#options.stateStore.set(this.#options.stateStoreIdentifier, stateData, true, storeOptions);
|
|
474
|
+
await this.#options.onUserSessionEstablished?.(storeOptions);
|
|
450
475
|
return {
|
|
451
476
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
452
477
|
};
|
|
@@ -541,6 +566,234 @@ function applyEnterpriseConnectRestrictions(instance) {
|
|
|
541
566
|
}
|
|
542
567
|
}
|
|
543
568
|
|
|
569
|
+
// src/anonymous/server-anonymous-client.ts
|
|
570
|
+
var import_jose = require("jose");
|
|
571
|
+
var import_auth0_auth_js2 = require("@auth0/auth0-auth-js");
|
|
572
|
+
var DEFAULT_AUDIENCE_CACHE_KEY = "default";
|
|
573
|
+
var readAnonymousSub = (accessToken) => {
|
|
574
|
+
try {
|
|
575
|
+
const { sub } = (0, import_jose.decodeJwt)(accessToken);
|
|
576
|
+
return typeof sub === "string" ? sub : void 0;
|
|
577
|
+
} catch {
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
var cacheKeyScope = (tokenSet) => tokenSet.requestedScope ?? tokenSet.scope;
|
|
582
|
+
var upsertTokenSet = (tokenSets, tokenSet) => {
|
|
583
|
+
const matches = (candidate) => candidate.audience === tokenSet.audience && cacheKeyScope(candidate) === cacheKeyScope(tokenSet);
|
|
584
|
+
return tokenSets.some(matches) ? tokenSets.map((candidate) => matches(candidate) ? tokenSet : candidate) : [...tokenSets, tokenSet];
|
|
585
|
+
};
|
|
586
|
+
var ServerAnonymousClient = class {
|
|
587
|
+
#options;
|
|
588
|
+
/**
|
|
589
|
+
* @internal
|
|
590
|
+
*/
|
|
591
|
+
constructor(options) {
|
|
592
|
+
this.#options = options;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Creates an anonymous session and stores it, returning the first anonymous access token.
|
|
596
|
+
*
|
|
597
|
+
* Replaces any anonymous session already stored for this visitor. Metadata can only be
|
|
598
|
+
* attached here, because Auth0 rejects a request that carries both metadata and an
|
|
599
|
+
* existing session.
|
|
600
|
+
*
|
|
601
|
+
* The anonymous identity (`sub`) is read off the first access token and stored alongside
|
|
602
|
+
* the metadata, so {@link ServerAnonymousClient.getSession} can hand both back without the
|
|
603
|
+
* application decoding a token. It stays `undefined` for an audience with token encryption
|
|
604
|
+
* (`token_encryption`) enabled, whose access token is an encrypted JWE only the API can
|
|
605
|
+
* read.
|
|
606
|
+
*
|
|
607
|
+
* @param options Optional audience, scope and metadata for the new session.
|
|
608
|
+
* @param storeOptions Optional options used to pass to the anonymous store (and to resolve the domain in resolver mode).
|
|
609
|
+
*
|
|
610
|
+
* @throws {AnonymousSessionError} If Auth0 rejected the request. Common codes are
|
|
611
|
+
* `feature_not_enabled` (the tenant flag is off), `unauthorized_client` (the client is
|
|
612
|
+
* not enabled for anonymous sessions), `invalid_target` (the resource server does not
|
|
613
|
+
* allow anonymous access), `invalid_request` (metadata over 1024 bytes, or not all
|
|
614
|
+
* strings) and `access_denied`. `code` is `server_error` when Auth0 answers with a body
|
|
615
|
+
* that is not JSON.
|
|
616
|
+
*
|
|
617
|
+
* Only call this once you have established the visitor has no anonymous session (check
|
|
618
|
+
* {@link ServerAnonymousClient.getSession} first), rather than on every request.
|
|
619
|
+
*
|
|
620
|
+
* @returns The anonymous access token for the requested audience.
|
|
621
|
+
*/
|
|
622
|
+
async createSession(options, storeOptions) {
|
|
623
|
+
const domain = await this.#options.resolveDomain(storeOptions);
|
|
624
|
+
const requestedAudience = options?.audience ?? this.#options.defaultAudience;
|
|
625
|
+
const audience = requestedAudience ?? DEFAULT_AUDIENCE_CACHE_KEY;
|
|
626
|
+
const scope = options?.scope;
|
|
627
|
+
const session = await this.#options.getAuthClient(domain).anonymous.createSession({
|
|
628
|
+
...requestedAudience && { audience: requestedAudience },
|
|
629
|
+
...scope && { scope },
|
|
630
|
+
...options?.metadata && { metadata: options.metadata }
|
|
631
|
+
});
|
|
632
|
+
const tokenSet = {
|
|
633
|
+
audience,
|
|
634
|
+
accessToken: session.accessToken,
|
|
635
|
+
scope: session.scope,
|
|
636
|
+
...scope && scope !== session.scope && { requestedScope: scope },
|
|
637
|
+
expiresAt: session.expiresAt
|
|
638
|
+
};
|
|
639
|
+
const stateData = {
|
|
640
|
+
sessionToken: session.sessionToken,
|
|
641
|
+
sessionTokenExpiresAt: session.sessionTokenExpiresAt,
|
|
642
|
+
sub: readAnonymousSub(session.accessToken),
|
|
643
|
+
...options?.metadata && { metadata: options.metadata },
|
|
644
|
+
createdAt: Math.floor(Date.now() / 1e3),
|
|
645
|
+
tokenSets: [tokenSet],
|
|
646
|
+
domain
|
|
647
|
+
};
|
|
648
|
+
await this.#options.anonymousStore.set(
|
|
649
|
+
this.#options.anonymousStoreIdentifier,
|
|
650
|
+
stateData,
|
|
651
|
+
true,
|
|
652
|
+
storeOptions
|
|
653
|
+
);
|
|
654
|
+
return tokenSet;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Returns an anonymous access token for the stored anonymous session, fetching a fresh
|
|
658
|
+
* one from Auth0 when the cached one has expired.
|
|
659
|
+
*
|
|
660
|
+
* Tokens are cached per audience and scope, so requesting a second audience returns a
|
|
661
|
+
* second token for the same anonymous identity without replacing the first.
|
|
662
|
+
*
|
|
663
|
+
* Auth0 may grant fewer scopes than requested — a scope the anonymous identity is not
|
|
664
|
+
* entitled to is silently dropped and the response is still a success. Always check
|
|
665
|
+
* `tokenSet.scope` before calling your API; do not assume the token carries every scope
|
|
666
|
+
* you asked for.
|
|
667
|
+
*
|
|
668
|
+
* This never creates a session. If the anonymous session has expired, the stored session
|
|
669
|
+
* is deleted and `AnonymousSessionExpiredError` is thrown, so a visitor is never moved
|
|
670
|
+
* onto a fresh anonymous identity behind your back.
|
|
671
|
+
*
|
|
672
|
+
* @param options Optional audience and scope for the requested token.
|
|
673
|
+
* @param storeOptions Optional options used to pass to the anonymous store (and to resolve the domain in resolver mode).
|
|
674
|
+
*
|
|
675
|
+
* @throws {MissingAnonymousSessionError} When there is no anonymous session stored, or the stored one belongs to another Auth0 domain (resolver mode).
|
|
676
|
+
* @throws {AnonymousSessionExpiredError} When the anonymous session has expired or Auth0 rejected the session token. The stored session is deleted first.
|
|
677
|
+
*
|
|
678
|
+
* @returns The anonymous access token for the requested audience.
|
|
679
|
+
*/
|
|
680
|
+
async getAccessToken(options, storeOptions) {
|
|
681
|
+
const stateData = await this.#options.anonymousStore.get(
|
|
682
|
+
this.#options.anonymousStoreIdentifier,
|
|
683
|
+
storeOptions
|
|
684
|
+
);
|
|
685
|
+
if (!stateData?.sessionToken) {
|
|
686
|
+
throw new MissingAnonymousSessionError();
|
|
687
|
+
}
|
|
688
|
+
const domain = await this.#options.resolveDomain(storeOptions);
|
|
689
|
+
if (this.#options.isResolverMode() && stateData.domain !== domain) {
|
|
690
|
+
throw new MissingAnonymousSessionError(
|
|
691
|
+
"The stored anonymous session was created for a different Auth0 domain."
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
const requestedAudience = options?.audience ?? this.#options.defaultAudience;
|
|
695
|
+
const audience = requestedAudience ?? DEFAULT_AUDIENCE_CACHE_KEY;
|
|
696
|
+
const scope = options?.scope;
|
|
697
|
+
const cachedTokenSet = stateData.tokenSets.find(
|
|
698
|
+
(tokenSet2) => tokenSet2.audience === audience && (!scope || cacheKeyScope(tokenSet2) === scope || compareScopes(tokenSet2.scope, scope))
|
|
699
|
+
);
|
|
700
|
+
if (cachedTokenSet && cachedTokenSet.expiresAt > Date.now() / 1e3) {
|
|
701
|
+
return cachedTokenSet;
|
|
702
|
+
}
|
|
703
|
+
let renewed;
|
|
704
|
+
try {
|
|
705
|
+
renewed = await this.#options.getAuthClient(domain).anonymous.getAccessToken({
|
|
706
|
+
sessionToken: stateData.sessionToken,
|
|
707
|
+
...requestedAudience && { audience: requestedAudience },
|
|
708
|
+
...scope && { scope }
|
|
709
|
+
});
|
|
710
|
+
} catch (e) {
|
|
711
|
+
if (e instanceof import_auth0_auth_js2.AnonymousSessionError && e.code === "session_expired") {
|
|
712
|
+
await this.#options.anonymousStore.delete(this.#options.anonymousStoreIdentifier, storeOptions);
|
|
713
|
+
throw new AnonymousSessionExpiredError();
|
|
714
|
+
}
|
|
715
|
+
if (e instanceof import_auth0_auth_js2.AnonymousSessionError && e.code === "invalid_session_token") {
|
|
716
|
+
await this.#options.anonymousStore.delete(this.#options.anonymousStoreIdentifier, storeOptions);
|
|
717
|
+
}
|
|
718
|
+
throw e;
|
|
719
|
+
}
|
|
720
|
+
const tokenSet = {
|
|
721
|
+
audience,
|
|
722
|
+
accessToken: renewed.accessToken,
|
|
723
|
+
scope: renewed.scope,
|
|
724
|
+
...scope && scope !== renewed.scope && { requestedScope: scope },
|
|
725
|
+
expiresAt: renewed.expiresAt
|
|
726
|
+
};
|
|
727
|
+
const existingStateData = await this.#options.anonymousStore.get(
|
|
728
|
+
this.#options.anonymousStoreIdentifier,
|
|
729
|
+
storeOptions
|
|
730
|
+
);
|
|
731
|
+
if (existingStateData?.sessionToken === stateData.sessionToken) {
|
|
732
|
+
await this.#options.anonymousStore.set(
|
|
733
|
+
this.#options.anonymousStoreIdentifier,
|
|
734
|
+
{
|
|
735
|
+
...existingStateData,
|
|
736
|
+
...existingStateData.sub === void 0 && { sub: readAnonymousSub(renewed.accessToken) },
|
|
737
|
+
tokenSets: upsertTokenSet(existingStateData.tokenSets, tokenSet)
|
|
738
|
+
},
|
|
739
|
+
false,
|
|
740
|
+
storeOptions
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
return tokenSet;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Returns the stored anonymous session, or `undefined` when there is none.
|
|
747
|
+
*
|
|
748
|
+
* Two common uses:
|
|
749
|
+
* - **Gate `createSession`** — call this first; only create a session when the result is `undefined`.
|
|
750
|
+
* - **Read identity for a merge** — `sub` and `metadata` are available here without decoding a token.
|
|
751
|
+
*
|
|
752
|
+
* This is a local read of the store with no request to Auth0. It cannot tell you whether
|
|
753
|
+
* Auth0 still considers the session valid — only {@link ServerAnonymousClient.getAccessToken} can.
|
|
754
|
+
* The session token is never included in the result.
|
|
755
|
+
*
|
|
756
|
+
* @param storeOptions Optional options used to pass to the anonymous store (and to resolve the domain in resolver mode).
|
|
757
|
+
*
|
|
758
|
+
* @returns The anonymous session, or `undefined` when there is none for this visitor (or it belongs to another Auth0 domain in resolver mode).
|
|
759
|
+
*/
|
|
760
|
+
async getSession(storeOptions) {
|
|
761
|
+
const stateData = await this.#options.anonymousStore.get(
|
|
762
|
+
this.#options.anonymousStoreIdentifier,
|
|
763
|
+
storeOptions
|
|
764
|
+
);
|
|
765
|
+
if (!stateData) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (this.#options.isResolverMode()) {
|
|
769
|
+
const domain = await this.#options.resolveDomain(storeOptions);
|
|
770
|
+
if (stateData.domain !== domain) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const { sessionToken, ...sessionData } = stateData;
|
|
775
|
+
return sessionData;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Clears the anonymous session from the store.
|
|
779
|
+
*
|
|
780
|
+
* Does not call `POST /anonymous/logout` — that endpoint only clears the `auth0_anon`
|
|
781
|
+
* browser cookie, which your server never holds. Any access tokens already issued remain
|
|
782
|
+
* valid until they expire (~2 hours by default).
|
|
783
|
+
*
|
|
784
|
+
* You rarely need to call this directly: every method that establishes a user session
|
|
785
|
+
* clears the anonymous session automatically (unless `clearAnonymousSessionOnLogin: false`
|
|
786
|
+
* is set), and `serverClient.logout()` clears it too. Call this to explicitly reset a
|
|
787
|
+
* visitor's anonymous identity, or to clean up after your own post-login merge logic when
|
|
788
|
+
* automatic clearing is disabled.
|
|
789
|
+
*
|
|
790
|
+
* @param storeOptions Optional options used to pass to the anonymous store.
|
|
791
|
+
*/
|
|
792
|
+
async logout(storeOptions) {
|
|
793
|
+
await this.#options.anonymousStore.delete(this.#options.anonymousStoreIdentifier, storeOptions);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
544
797
|
// src/server-client.ts
|
|
545
798
|
var normalizeDomain = (value) => {
|
|
546
799
|
const trimmed = value.trim();
|
|
@@ -549,7 +802,7 @@ var normalizeDomain = (value) => {
|
|
|
549
802
|
};
|
|
550
803
|
var decodeIssuer = (token) => {
|
|
551
804
|
try {
|
|
552
|
-
const { iss } = (0,
|
|
805
|
+
const { iss } = (0, import_jose2.decodeJwt)(token);
|
|
553
806
|
return typeof iss === "string" ? iss : void 0;
|
|
554
807
|
} catch {
|
|
555
808
|
return void 0;
|
|
@@ -558,13 +811,13 @@ var decodeIssuer = (token) => {
|
|
|
558
811
|
var ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token";
|
|
559
812
|
var ID_TOKEN_EXPIRY_SKEW_SECONDS = 30;
|
|
560
813
|
var actorUnavailableError = (message) => {
|
|
561
|
-
const error = new
|
|
814
|
+
const error = new import_auth0_auth_js3.TokenExchangeError(message);
|
|
562
815
|
error.code = TokenExchangeErrorCode.ACTOR_UNAVAILABLE;
|
|
563
816
|
return error;
|
|
564
817
|
};
|
|
565
818
|
var isTokenExpired = (token) => {
|
|
566
819
|
try {
|
|
567
|
-
const { exp } = (0,
|
|
820
|
+
const { exp } = (0, import_jose2.decodeJwt)(token);
|
|
568
821
|
if (typeof exp !== "number") {
|
|
569
822
|
return true;
|
|
570
823
|
}
|
|
@@ -586,6 +839,7 @@ var ServerClient = class {
|
|
|
586
839
|
#mfaClient;
|
|
587
840
|
#passkeyClient;
|
|
588
841
|
#databaseClient;
|
|
842
|
+
#anonymousClient;
|
|
589
843
|
/**
|
|
590
844
|
* The underlying `authClient` instance that can be used to interact with the Auth0 Authentication API.
|
|
591
845
|
* Generally, you should prefer to use the higher-level methods exposed on the `ServerClient` instance.
|
|
@@ -648,6 +902,42 @@ var ServerClient = class {
|
|
|
648
902
|
get database() {
|
|
649
903
|
return this.#databaseClient;
|
|
650
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* The anonymous session client, for giving a visitor who has not logged in a stable
|
|
907
|
+
* identity and an access token for your API.
|
|
908
|
+
*
|
|
909
|
+
* Provides `createSession()` to establish the anonymous identity, `getAccessToken()` to
|
|
910
|
+
* obtain and renew anonymous access tokens, `getSession()` to check whether a visitor
|
|
911
|
+
* already has one, and `logout()` to discard it.
|
|
912
|
+
*
|
|
913
|
+
* Requires `anonymousStore` on the `ServerClient`, and a tenant and client configured for
|
|
914
|
+
* anonymous sessions. Anonymous sessions are kept in that store, never in the state
|
|
915
|
+
* store, so `getSession()` and `getUser()` still report no user while one is active.
|
|
916
|
+
*
|
|
917
|
+
* The anonymous session ends when the visitor logs in: every login method clears it once
|
|
918
|
+
* the user session is written, unless you set `clearAnonymousSessionOnLogin: false`.
|
|
919
|
+
* {@link ServerClient.logout} clears it too.
|
|
920
|
+
*
|
|
921
|
+
* For `/authorize` flows, `startInteractiveLogin()` links the anonymous session to
|
|
922
|
+
* the user automatically via a Session Transfer Ticket — no extra configuration needed.
|
|
923
|
+
* For logins that bypass `/authorize` (passkey, passwordless, backchannel, custom token
|
|
924
|
+
* exchange), the anonymous session is not linked. Read `anonymous.getSession()` before
|
|
925
|
+
* the login to get the anonymous `sub` for a manual merge, or set
|
|
926
|
+
* `clearAnonymousSessionOnLogin: false` and clear it yourself afterwards.
|
|
927
|
+
*
|
|
928
|
+
* Like `passkey` and `database`, this works in both static and resolver (multi-tenant)
|
|
929
|
+
* domain modes.
|
|
930
|
+
*
|
|
931
|
+
* @throws {InvalidConfigurationError} When no `anonymousStore` is configured.
|
|
932
|
+
*/
|
|
933
|
+
get anonymous() {
|
|
934
|
+
if (!this.#anonymousClient) {
|
|
935
|
+
throw new InvalidConfigurationError(
|
|
936
|
+
"anonymous is only available when an `anonymousStore` is configured on the ServerClient."
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
return this.#anonymousClient;
|
|
940
|
+
}
|
|
651
941
|
constructor(options) {
|
|
652
942
|
this.#options = options;
|
|
653
943
|
this.#enterpriseConnect = !!options.enterpriseConnect;
|
|
@@ -696,7 +986,7 @@ var ServerClient = class {
|
|
|
696
986
|
if (typeof this.#options.domain === "string") {
|
|
697
987
|
const domain = normalizeDomain(this.#options.domain);
|
|
698
988
|
this.#staticDomain = domain;
|
|
699
|
-
this.#authClient = new
|
|
989
|
+
this.#authClient = new import_auth0_auth_js3.AuthClient({
|
|
700
990
|
domain,
|
|
701
991
|
...this.#authClientOptions,
|
|
702
992
|
telemetry: getTelemetryConfig(this.#options.telemetry)
|
|
@@ -706,7 +996,8 @@ var ServerClient = class {
|
|
|
706
996
|
domain,
|
|
707
997
|
stateStore: this.#stateStore,
|
|
708
998
|
stateStoreIdentifier: this.#stateStoreIdentifier,
|
|
709
|
-
defaultAudience: this.#options.authorizationParams?.audience ?? "default"
|
|
999
|
+
defaultAudience: this.#options.authorizationParams?.audience ?? "default",
|
|
1000
|
+
onUserSessionEstablished: (storeOptions) => this.#clearAnonymousSessionAfterLogin(storeOptions)
|
|
710
1001
|
});
|
|
711
1002
|
}
|
|
712
1003
|
this.#passkeyClient = new ServerPasskeyClient({
|
|
@@ -715,12 +1006,26 @@ var ServerClient = class {
|
|
|
715
1006
|
stateStore: this.#stateStore,
|
|
716
1007
|
stateStoreIdentifier: this.#stateStoreIdentifier,
|
|
717
1008
|
defaultScope: this.#options.authorizationParams?.scope,
|
|
718
|
-
defaultAudience: this.#options.authorizationParams?.audience
|
|
1009
|
+
defaultAudience: this.#options.authorizationParams?.audience,
|
|
1010
|
+
onUserSessionEstablished: (storeOptions) => this.#clearAnonymousSessionAfterLogin(storeOptions)
|
|
719
1011
|
});
|
|
720
1012
|
this.#databaseClient = new ServerDatabaseClient({
|
|
721
1013
|
resolveDomain: (storeOptions) => this.#resolveDomain(storeOptions),
|
|
722
1014
|
getAuthClient: (domain) => this.#getAuthClient(domain)
|
|
723
1015
|
});
|
|
1016
|
+
if (this.#options.anonymousStore) {
|
|
1017
|
+
this.#anonymousClient = new ServerAnonymousClient({
|
|
1018
|
+
resolveDomain: (storeOptions) => this.#resolveDomain(storeOptions),
|
|
1019
|
+
getAuthClient: (domain) => this.#getAuthClient(domain),
|
|
1020
|
+
isResolverMode: () => this.#isResolverMode(),
|
|
1021
|
+
anonymousStore: this.#options.anonymousStore,
|
|
1022
|
+
anonymousStoreIdentifier: this.#options.anonymousSessionIdentifier || "__a0_anon",
|
|
1023
|
+
// Only the audience is inherited from the client-level authorizationParams. The
|
|
1024
|
+
// scope there is written for a user session (`openid profile email offline_access`)
|
|
1025
|
+
// and none of it applies to an anonymous identity.
|
|
1026
|
+
defaultAudience: this.#options.authorizationParams?.audience
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
724
1029
|
}
|
|
725
1030
|
async #resolveDomain(storeOptions) {
|
|
726
1031
|
if (typeof this.#options.domain === "function") {
|
|
@@ -733,7 +1038,7 @@ var ServerClient = class {
|
|
|
733
1038
|
return normalizeDomain(this.#options.domain);
|
|
734
1039
|
}
|
|
735
1040
|
#createAuthClient(domain) {
|
|
736
|
-
return new
|
|
1041
|
+
return new import_auth0_auth_js3.AuthClient({
|
|
737
1042
|
domain,
|
|
738
1043
|
...this.#authClientOptions,
|
|
739
1044
|
telemetry: getTelemetryConfig(this.#options.telemetry)
|
|
@@ -784,7 +1089,7 @@ var ServerClient = class {
|
|
|
784
1089
|
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
785
1090
|
const emailDomain = parts[1].toLowerCase();
|
|
786
1091
|
const domain = await this.#resolveDomain(storeOptions);
|
|
787
|
-
const isFederated = await (0,
|
|
1092
|
+
const isFederated = await (0, import_auth0_auth_js3.isFederatedDomain)(domain, emailDomain, {
|
|
788
1093
|
customFetch: this.#options.customFetch,
|
|
789
1094
|
telemetry: getTelemetryConfig(this.#options.telemetry)
|
|
790
1095
|
});
|
|
@@ -797,6 +1102,54 @@ var ServerClient = class {
|
|
|
797
1102
|
storeOptions
|
|
798
1103
|
);
|
|
799
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Discards the anonymous session once a login has established a user session.
|
|
1107
|
+
*
|
|
1108
|
+
* Called by every method that writes a user session, always AFTER the state store has been
|
|
1109
|
+
* written. An anonymous session outliving the login would leave a 30-day bearer credential
|
|
1110
|
+
* in a cookie on every request, and `anonymous.getAccessToken()` would keep minting
|
|
1111
|
+
* anonymous tokens for a visitor who is no longer anonymous.
|
|
1112
|
+
*
|
|
1113
|
+
* Two deliberate properties:
|
|
1114
|
+
* - **Best-effort.** The user is authenticated and their session is already persisted by
|
|
1115
|
+
* this point, so a failing anonymous store must not turn a successful login into an
|
|
1116
|
+
* error. The visitor would be unable to log in at all because of leftover state they
|
|
1117
|
+
* cannot see or clear.
|
|
1118
|
+
* - **Unconditional across domains.** In resolver (multi-tenant) mode the anonymous session
|
|
1119
|
+
* is dropped even when it was created against a different Auth0 domain than the one just
|
|
1120
|
+
* logged into. The anonymous store holds a single session per visitor, and "no anonymous
|
|
1121
|
+
* session survives a login" is a rule that can be reasoned about; a domain-dependent one
|
|
1122
|
+
* cannot.
|
|
1123
|
+
*
|
|
1124
|
+
* Skipped entirely when no `anonymousStore` is configured, or when the application opted
|
|
1125
|
+
* out with `clearAnonymousSessionOnLogin: false`.
|
|
1126
|
+
*/
|
|
1127
|
+
async #clearAnonymousSessionAfterLogin(storeOptions) {
|
|
1128
|
+
if (this.#options.clearAnonymousSessionOnLogin === false) {
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
await this.#discardAnonymousSession(storeOptions);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Drops the anonymous session, swallowing store failures.
|
|
1135
|
+
*
|
|
1136
|
+
* Shared by the login sites and {@link ServerClient.logout}. Both run after the user
|
|
1137
|
+
* session has already been written or deleted, so the outcome the caller cares about is
|
|
1138
|
+
* settled: a failing anonymous store must not fail a login the user completed, nor block a
|
|
1139
|
+
* logout redirect and leave the visitor unable to log out. When the store is the
|
|
1140
|
+
* cookie-backed default this cannot fail, since deleting is a `Set-Cookie` on the response.
|
|
1141
|
+
*
|
|
1142
|
+
* No-op when no `anonymousStore` is configured.
|
|
1143
|
+
*/
|
|
1144
|
+
async #discardAnonymousSession(storeOptions) {
|
|
1145
|
+
if (!this.#anonymousClient) {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
await this.#anonymousClient.logout(storeOptions);
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
800
1153
|
/**
|
|
801
1154
|
* Starts the interactive login process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
|
|
802
1155
|
*
|
|
@@ -824,7 +1177,7 @@ var ServerClient = class {
|
|
|
824
1177
|
const clientAuthParamsOrganization = typeof this.#options.authorizationParams?.organization === "string" ? this.#options.authorizationParams.organization : void 0;
|
|
825
1178
|
const resolvedOrganization = options?.organization ?? perLoginAuthParamsOrganization ?? this.#options.organization ?? clientAuthParamsOrganization;
|
|
826
1179
|
if (resolvedOrganization !== void 0 && !resolvedOrganization.trim()) {
|
|
827
|
-
throw new
|
|
1180
|
+
throw new import_auth0_auth_js3.OrganizationValidationError("organization must not be blank");
|
|
828
1181
|
}
|
|
829
1182
|
const hasInvitation = !!(options?.invitation || options?.authorizationParams?.invitation);
|
|
830
1183
|
if (hasInvitation && !resolvedOrganization) {
|
|
@@ -832,6 +1185,17 @@ var ServerClient = class {
|
|
|
832
1185
|
}
|
|
833
1186
|
const domain = await this.#resolveDomain(storeOptions);
|
|
834
1187
|
const authClient = this.#getAuthClient(domain);
|
|
1188
|
+
let anonTransferToken;
|
|
1189
|
+
if (this.#options.anonymousStore) {
|
|
1190
|
+
try {
|
|
1191
|
+
const identifier = this.#options.anonymousSessionIdentifier || "__a0_anon";
|
|
1192
|
+
const stateData = await this.#options.anonymousStore.get(identifier, storeOptions);
|
|
1193
|
+
if (stateData?.sessionToken) {
|
|
1194
|
+
anonTransferToken = await authClient.anonymous.mintTransferToken(stateData.sessionToken) ?? void 0;
|
|
1195
|
+
}
|
|
1196
|
+
} catch {
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
835
1199
|
const { codeVerifier, authorizationUrl } = await authClient.buildAuthorizationUrl({
|
|
836
1200
|
pushedAuthorizationRequests: options?.pushedAuthorizationRequests,
|
|
837
1201
|
authorizationParams: {
|
|
@@ -839,7 +1203,8 @@ var ServerClient = class {
|
|
|
839
1203
|
redirect_uri: redirectUri,
|
|
840
1204
|
scope,
|
|
841
1205
|
...resolvedOrganization ? { organization: resolvedOrganization } : {},
|
|
842
|
-
...options?.invitation ? { invitation: options.invitation } : {}
|
|
1206
|
+
...options?.invitation ? { invitation: options.invitation } : {},
|
|
1207
|
+
...anonTransferToken ? { anon_transfer_token: anonTransferToken } : {}
|
|
843
1208
|
}
|
|
844
1209
|
});
|
|
845
1210
|
const transactionState = {
|
|
@@ -909,6 +1274,7 @@ var ServerClient = class {
|
|
|
909
1274
|
tokenEndpointResponse.claims
|
|
910
1275
|
);
|
|
911
1276
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
1277
|
+
await this.#clearAnonymousSessionAfterLogin(storeOptions);
|
|
912
1278
|
return {
|
|
913
1279
|
appState: transactionData.appState,
|
|
914
1280
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
@@ -1076,12 +1442,13 @@ var ServerClient = class {
|
|
|
1076
1442
|
tokenEndpointResponse.claims
|
|
1077
1443
|
);
|
|
1078
1444
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
1445
|
+
await this.#clearAnonymousSessionAfterLogin(storeOptions);
|
|
1079
1446
|
const result = {
|
|
1080
1447
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
1081
1448
|
};
|
|
1082
1449
|
if (options.fullResponse) {
|
|
1083
1450
|
if (!response) {
|
|
1084
|
-
throw new
|
|
1451
|
+
throw new import_auth0_auth_js3.MissingCapturedResponseError();
|
|
1085
1452
|
}
|
|
1086
1453
|
return { data: result, response };
|
|
1087
1454
|
}
|
|
@@ -1150,7 +1517,7 @@ var ServerClient = class {
|
|
|
1150
1517
|
return;
|
|
1151
1518
|
}
|
|
1152
1519
|
if (!options.redirectUri || typeof options.redirectUri !== "string") {
|
|
1153
|
-
throw new
|
|
1520
|
+
throw new import_auth0_auth_js3.PasswordlessStartError("redirectUri is required to start a passwordless magic-link login.");
|
|
1154
1521
|
}
|
|
1155
1522
|
const state = crypto.randomUUID();
|
|
1156
1523
|
const scope = ensureOpenIdScope(options.scope ?? this.#options.authorizationParams?.scope);
|
|
@@ -1222,12 +1589,13 @@ var ServerClient = class {
|
|
|
1222
1589
|
{ domain }
|
|
1223
1590
|
);
|
|
1224
1591
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
1592
|
+
await this.#clearAnonymousSessionAfterLogin(storeOptions);
|
|
1225
1593
|
const result = {
|
|
1226
1594
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
1227
1595
|
};
|
|
1228
1596
|
if (options.fullResponse) {
|
|
1229
1597
|
if (!response) {
|
|
1230
|
-
throw new
|
|
1598
|
+
throw new import_auth0_auth_js3.MissingCapturedResponseError();
|
|
1231
1599
|
}
|
|
1232
1600
|
return { data: result, response };
|
|
1233
1601
|
}
|
|
@@ -1262,7 +1630,7 @@ var ServerClient = class {
|
|
|
1262
1630
|
const expectedState = typeof transactionData.state === "string" ? transactionData.state : void 0;
|
|
1263
1631
|
const returnedState = url.searchParams.get("state");
|
|
1264
1632
|
if (!returnedState || !expectedState || returnedState !== expectedState) {
|
|
1265
|
-
throw new
|
|
1633
|
+
throw new import_auth0_auth_js3.PasswordlessVerifyError("State mismatch on magic-link callback");
|
|
1266
1634
|
}
|
|
1267
1635
|
const domain = transactionData.domain ?? await this.#resolveDomain(storeOptions);
|
|
1268
1636
|
const authClient = this.#getAuthClient(domain);
|
|
@@ -1272,6 +1640,7 @@ var ServerClient = class {
|
|
|
1272
1640
|
domain
|
|
1273
1641
|
});
|
|
1274
1642
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
1643
|
+
await this.#clearAnonymousSessionAfterLogin(storeOptions);
|
|
1275
1644
|
await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
|
|
1276
1645
|
return {
|
|
1277
1646
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
@@ -1419,7 +1788,7 @@ var ServerClient = class {
|
|
|
1419
1788
|
}
|
|
1420
1789
|
}
|
|
1421
1790
|
if (!stateData?.refreshToken) {
|
|
1422
|
-
throw new
|
|
1791
|
+
throw new import_auth0_auth_js3.TokenByRefreshTokenError(
|
|
1423
1792
|
"The access token has expired and a refresh token was not provided. The user needs to re-authenticate."
|
|
1424
1793
|
);
|
|
1425
1794
|
}
|
|
@@ -1458,7 +1827,7 @@ var ServerClient = class {
|
|
|
1458
1827
|
};
|
|
1459
1828
|
if (resolvedOptions?.fullResponse) {
|
|
1460
1829
|
if (!response) {
|
|
1461
|
-
throw new
|
|
1830
|
+
throw new import_auth0_auth_js3.MissingCapturedResponseError();
|
|
1462
1831
|
}
|
|
1463
1832
|
return { data: returnTokenSet, response };
|
|
1464
1833
|
}
|
|
@@ -1488,7 +1857,7 @@ var ServerClient = class {
|
|
|
1488
1857
|
}
|
|
1489
1858
|
}
|
|
1490
1859
|
if (!stateData?.refreshToken) {
|
|
1491
|
-
throw new
|
|
1860
|
+
throw new import_auth0_auth_js3.TokenForConnectionError(
|
|
1492
1861
|
"A refresh token was not found but is required to be able to retrieve an access token for a connection."
|
|
1493
1862
|
);
|
|
1494
1863
|
}
|
|
@@ -1529,7 +1898,7 @@ var ServerClient = class {
|
|
|
1529
1898
|
};
|
|
1530
1899
|
if (options.fullResponse) {
|
|
1531
1900
|
if (!response) {
|
|
1532
|
-
throw new
|
|
1901
|
+
throw new import_auth0_auth_js3.MissingCapturedResponseError();
|
|
1533
1902
|
}
|
|
1534
1903
|
return { data: returnConnectionTokenSet, response };
|
|
1535
1904
|
}
|
|
@@ -1579,6 +1948,29 @@ var ServerClient = class {
|
|
|
1579
1948
|
}
|
|
1580
1949
|
/**
|
|
1581
1950
|
* Logs the user out and returns a URL to redirect the user-agent to after they log out.
|
|
1951
|
+
*
|
|
1952
|
+
* Clears the anonymous session as well, whenever an `anonymousStore` is configured. Logging
|
|
1953
|
+
* out means the visitor is done, so leaving a 30-day anonymous credential behind in a
|
|
1954
|
+
* cookie would be surprising, and `anonymous.getAccessToken()` would keep working right
|
|
1955
|
+
* after the user logged out. This happens even when there is no user session to clear: a
|
|
1956
|
+
* visitor who only ever had an anonymous session can log out. Unlike the automatic clearing
|
|
1957
|
+
* at login, it is not affected by `clearAnonymousSessionOnLogin`, which exists so an
|
|
1958
|
+
* application can finish its post-login work.
|
|
1959
|
+
*
|
|
1960
|
+
* In resolver (multi-tenant) mode the one exception is a stored user session belonging to a
|
|
1961
|
+
* different Auth0 domain than the request resolves to. Nothing local is cleared in that
|
|
1962
|
+
* case, anonymous session included, exactly as today: that state belongs to another tenant.
|
|
1963
|
+
*
|
|
1964
|
+
* The anonymous session is only ever cleared locally. The SDK does not call
|
|
1965
|
+
* `POST /anonymous/logout`: that endpoint exists to clear the `auth0_anon` cookie in a
|
|
1966
|
+
* browser, and it revokes nothing server-side. Your server never holds that cookie, so
|
|
1967
|
+
* calling it would achieve nothing. If the visitor's browser created an anonymous session
|
|
1968
|
+
* of its own (through `@auth0/auth0-spa-js`, for example), that cookie is not covered by
|
|
1969
|
+
* this SDK and has to be cleared from the browser.
|
|
1970
|
+
*
|
|
1971
|
+
* Anonymous access tokens already handed out stay valid until they expire; there is no
|
|
1972
|
+
* anonymous session to revoke them against.
|
|
1973
|
+
*
|
|
1582
1974
|
* @param options Options used to configure the logout process.
|
|
1583
1975
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
1584
1976
|
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the token revocation ONLY. Building the logout URL is local string work and issues no request, so nothing here can affect it.
|
|
@@ -1599,12 +1991,14 @@ var ServerClient = class {
|
|
|
1599
1991
|
} catch {
|
|
1600
1992
|
}
|
|
1601
1993
|
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
1994
|
+
await this.#discardAnonymousSession(storeOptions);
|
|
1602
1995
|
return this.authClient.buildLogoutUrl(options);
|
|
1603
1996
|
}
|
|
1604
1997
|
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
1605
1998
|
const authClient = this.#getAuthClient(resolvedDomain);
|
|
1606
1999
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1607
2000
|
if (!stateData) {
|
|
2001
|
+
await this.#discardAnonymousSession(storeOptions);
|
|
1608
2002
|
return authClient.buildLogoutUrl(options);
|
|
1609
2003
|
}
|
|
1610
2004
|
const sessionDomain = this.#getSessionDomain(stateData);
|
|
@@ -1615,6 +2009,7 @@ var ServerClient = class {
|
|
|
1615
2009
|
} catch {
|
|
1616
2010
|
}
|
|
1617
2011
|
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
2012
|
+
await this.#discardAnonymousSession(storeOptions);
|
|
1618
2013
|
}
|
|
1619
2014
|
return authClient.buildLogoutUrl(options);
|
|
1620
2015
|
}
|
|
@@ -1646,12 +2041,13 @@ var ServerClient = class {
|
|
|
1646
2041
|
{ domain }
|
|
1647
2042
|
);
|
|
1648
2043
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
2044
|
+
await this.#clearAnonymousSessionAfterLogin(storeOptions);
|
|
1649
2045
|
const result = {
|
|
1650
2046
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
1651
2047
|
};
|
|
1652
2048
|
if (options.fullResponse) {
|
|
1653
2049
|
if (!response) {
|
|
1654
|
-
throw new
|
|
2050
|
+
throw new import_auth0_auth_js3.MissingCapturedResponseError();
|
|
1655
2051
|
}
|
|
1656
2052
|
return { data: result, response };
|
|
1657
2053
|
}
|
|
@@ -1715,7 +2111,7 @@ var ServerClient = class {
|
|
|
1715
2111
|
throw new MissingRequiredArgumentError("subjectTokenType");
|
|
1716
2112
|
}
|
|
1717
2113
|
if (options.organization !== void 0 && !options.organization.trim()) {
|
|
1718
|
-
throw new
|
|
2114
|
+
throw new import_auth0_auth_js3.OrganizationValidationError("organization must not be blank");
|
|
1719
2115
|
}
|
|
1720
2116
|
const domain = await this.#resolveDomain(storeOptions);
|
|
1721
2117
|
const actor = await this.#resolveSessionTransferActor(options.actor, domain, storeOptions);
|
|
@@ -1789,7 +2185,7 @@ var ServerClient = class {
|
|
|
1789
2185
|
url.searchParams.set("session_transfer_token", result.sessionTransferToken);
|
|
1790
2186
|
if (options?.organization !== void 0) {
|
|
1791
2187
|
if (!options.organization.trim()) {
|
|
1792
|
-
throw new
|
|
2188
|
+
throw new import_auth0_auth_js3.OrganizationValidationError("organization must not be blank");
|
|
1793
2189
|
}
|
|
1794
2190
|
url.searchParams.set("organization", options.organization);
|
|
1795
2191
|
}
|
|
@@ -1893,7 +2289,7 @@ var ServerClient = class {
|
|
|
1893
2289
|
};
|
|
1894
2290
|
|
|
1895
2291
|
// src/encryption/index.ts
|
|
1896
|
-
var
|
|
2292
|
+
var import_jose3 = require("jose");
|
|
1897
2293
|
var ENC = "A256CBC-HS512";
|
|
1898
2294
|
var ALG = "dir";
|
|
1899
2295
|
var DIGEST = "SHA-256";
|
|
@@ -1901,7 +2297,7 @@ var BIT_LENGTH = 512;
|
|
|
1901
2297
|
var HKDF_INFO = "derived cookie encryption secret";
|
|
1902
2298
|
var encoder;
|
|
1903
2299
|
function isDecryptionError(e) {
|
|
1904
|
-
return e instanceof
|
|
2300
|
+
return e instanceof import_jose3.errors.JWEDecryptionFailed || e instanceof import_jose3.errors.JWEInvalid;
|
|
1905
2301
|
}
|
|
1906
2302
|
async function deriveEncryptionSecret(secret, salt, kid) {
|
|
1907
2303
|
encoder ||= new TextEncoder();
|
|
@@ -1922,7 +2318,7 @@ async function deriveEncryptionSecret(secret, salt, kid) {
|
|
|
1922
2318
|
async function _encrypt(payload, secret, salt, expiration) {
|
|
1923
2319
|
const kid = crypto.randomUUID();
|
|
1924
2320
|
const encryptionSecret = await deriveEncryptionSecret(secret, salt, kid);
|
|
1925
|
-
return await new
|
|
2321
|
+
return await new import_jose3.EncryptJWT(payload).setProtectedHeader({ enc: ENC, alg: ALG, kid }).setExpirationTime(expiration).encrypt(encryptionSecret);
|
|
1926
2322
|
}
|
|
1927
2323
|
async function encrypt(payload, secret, salt, expiration) {
|
|
1928
2324
|
if (typeof secret === "string") {
|
|
@@ -1936,7 +2332,7 @@ async function encrypt(payload, secret, salt, expiration) {
|
|
|
1936
2332
|
}
|
|
1937
2333
|
}
|
|
1938
2334
|
async function _decrypt(value, secret, salt) {
|
|
1939
|
-
const res = await (0,
|
|
2335
|
+
const res = await (0, import_jose3.jwtDecrypt)(
|
|
1940
2336
|
value,
|
|
1941
2337
|
async (protectedHeader) => {
|
|
1942
2338
|
if (!protectedHeader.kid) {
|
|
@@ -2014,7 +2410,7 @@ var AbstractTransactionStore = class extends AbstractStore {
|
|
|
2014
2410
|
};
|
|
2015
2411
|
|
|
2016
2412
|
// src/index.ts
|
|
2017
|
-
var
|
|
2413
|
+
var import_auth0_auth_js8 = require("@auth0/auth0-auth-js");
|
|
2018
2414
|
|
|
2019
2415
|
// src/store/cookie-transaction-store.ts
|
|
2020
2416
|
var CookieTransactionStore = class extends AbstractTransactionStore {
|
|
@@ -2210,21 +2606,110 @@ var StatelessStateStore = class extends AbstractSessionStore {
|
|
|
2210
2606
|
}
|
|
2211
2607
|
};
|
|
2212
2608
|
|
|
2609
|
+
// src/store/abstract-anonymous-store.ts
|
|
2610
|
+
var AbstractAnonymousStore = class extends AbstractStore {
|
|
2611
|
+
constructor(options) {
|
|
2612
|
+
super(options);
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2616
|
+
// src/store/stateless-anonymous-store.ts
|
|
2617
|
+
var DEFAULT_ANONYMOUS_SESSION_LIFETIME = 60 * 60 * 24 * 30;
|
|
2618
|
+
var CHUNK_SIZE = 3072;
|
|
2619
|
+
var StatelessAnonymousStore = class extends AbstractAnonymousStore {
|
|
2620
|
+
#cookieOptions;
|
|
2621
|
+
#cookieHandler;
|
|
2622
|
+
#sessionTokenLifetime;
|
|
2623
|
+
constructor(options, cookieHandler) {
|
|
2624
|
+
super(options);
|
|
2625
|
+
this.#cookieOptions = options.cookie;
|
|
2626
|
+
this.#cookieHandler = cookieHandler;
|
|
2627
|
+
this.#sessionTokenLifetime = options.sessionTokenLifetime ?? DEFAULT_ANONYMOUS_SESSION_LIFETIME;
|
|
2628
|
+
}
|
|
2629
|
+
async set(identifier, anonymousStateData, removeIfExists, options) {
|
|
2630
|
+
const expiration = this.#getExpiration(anonymousStateData);
|
|
2631
|
+
const maxAge = expiration - Math.floor(Date.now() / 1e3);
|
|
2632
|
+
if (maxAge <= 0) {
|
|
2633
|
+
await this.delete(identifier, options);
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
const cookieOpts = this.#getCookieOptions({ maxAge });
|
|
2637
|
+
const encryptedStateData = await this.encrypt(identifier, anonymousStateData, expiration);
|
|
2638
|
+
const chunkCount = Math.ceil(encryptedStateData.length / CHUNK_SIZE);
|
|
2639
|
+
const chunks = [...Array(chunkCount).keys()].map((i) => ({
|
|
2640
|
+
value: encryptedStateData.substring(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE),
|
|
2641
|
+
name: `${identifier}.${i}`
|
|
2642
|
+
}));
|
|
2643
|
+
chunks.forEach((chunk) => {
|
|
2644
|
+
this.#cookieHandler.setCookie(chunk.name, chunk.value, cookieOpts, options);
|
|
2645
|
+
});
|
|
2646
|
+
const existingCookieKeys = this.#getCookieKeys(identifier, options);
|
|
2647
|
+
existingCookieKeys.filter((key) => !chunks.some((chunk) => chunk.name === key)).forEach((key) => {
|
|
2648
|
+
this.#cookieHandler.deleteCookie(key, options, this.#getCookieOptions());
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
async get(identifier, options) {
|
|
2652
|
+
const cookieKeys = this.#getCookieKeys(identifier, options);
|
|
2653
|
+
const encryptedStateData = cookieKeys.map((key) => ({
|
|
2654
|
+
index: parseInt(key.split(".")[1], 10),
|
|
2655
|
+
value: this.#cookieHandler.getCookie(key, options)
|
|
2656
|
+
})).sort((a, b) => a.index - b.index).map((item) => item.value).join("");
|
|
2657
|
+
if (encryptedStateData) {
|
|
2658
|
+
return await this.decrypt(identifier, encryptedStateData);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
async delete(identifier, options) {
|
|
2662
|
+
const cookieKeys = this.#getCookieKeys(identifier, options);
|
|
2663
|
+
for (const key of cookieKeys) {
|
|
2664
|
+
this.#cookieHandler.deleteCookie(key, options, this.#getCookieOptions());
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Absolute expiry of the anonymous session, in Unix seconds.
|
|
2669
|
+
*
|
|
2670
|
+
* Prefers the value Auth0 reports. `sessionTokenExpiresAt` is not populated yet, because
|
|
2671
|
+
* `auth0-auth-js` does not surface `session_expires_in` from the token response; until it
|
|
2672
|
+
* does, this falls back to the configured lifetime measured from creation.
|
|
2673
|
+
*/
|
|
2674
|
+
#getExpiration(anonymousStateData) {
|
|
2675
|
+
return anonymousStateData.sessionTokenExpiresAt ?? anonymousStateData.createdAt + this.#sessionTokenLifetime;
|
|
2676
|
+
}
|
|
2677
|
+
#getCookieKeys(identifier, options) {
|
|
2678
|
+
return Object.keys(this.#cookieHandler.getCookies(options)).filter((key) => key.startsWith(identifier));
|
|
2679
|
+
}
|
|
2680
|
+
#getCookieOptions(partial) {
|
|
2681
|
+
return {
|
|
2682
|
+
httpOnly: true,
|
|
2683
|
+
sameSite: this.#cookieOptions?.sameSite ?? "lax",
|
|
2684
|
+
path: this.#cookieOptions?.path ?? "/",
|
|
2685
|
+
secure: this.#cookieOptions?.secure ?? true,
|
|
2686
|
+
...partial
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
|
|
2213
2691
|
// src/mfa/index.ts
|
|
2214
|
-
var
|
|
2692
|
+
var import_auth0_auth_js4 = require("@auth0/auth0-auth-js");
|
|
2215
2693
|
|
|
2216
2694
|
// src/passkey/index.ts
|
|
2217
|
-
var
|
|
2695
|
+
var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
|
|
2218
2696
|
|
|
2219
2697
|
// src/database/index.ts
|
|
2220
|
-
var
|
|
2698
|
+
var import_auth0_auth_js6 = require("@auth0/auth0-auth-js");
|
|
2699
|
+
|
|
2700
|
+
// src/anonymous/index.ts
|
|
2701
|
+
var import_auth0_auth_js7 = require("@auth0/auth0-auth-js");
|
|
2221
2702
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2222
2703
|
0 && (module.exports = {
|
|
2704
|
+
AbstractAnonymousStore,
|
|
2223
2705
|
AbstractStateStore,
|
|
2224
2706
|
AbstractTransactionStore,
|
|
2707
|
+
AnonymousSessionError,
|
|
2708
|
+
AnonymousSessionExpiredError,
|
|
2225
2709
|
BackchannelLogoutError,
|
|
2226
2710
|
ChangePasswordError,
|
|
2227
2711
|
CookieTransactionStore,
|
|
2712
|
+
DEFAULT_ANONYMOUS_SESSION_LIFETIME,
|
|
2228
2713
|
EnterpriseConnectNotSupportedError,
|
|
2229
2714
|
InvalidConfigurationError,
|
|
2230
2715
|
IssuerValidationError,
|
|
@@ -2232,6 +2717,7 @@ var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
|
|
|
2232
2717
|
MfaEnrollmentError,
|
|
2233
2718
|
MfaListAuthenticatorsError,
|
|
2234
2719
|
MfaVerifyError,
|
|
2720
|
+
MissingAnonymousSessionError,
|
|
2235
2721
|
MissingClientAuthError,
|
|
2236
2722
|
MissingRequiredArgumentError,
|
|
2237
2723
|
MissingSessionError,
|
|
@@ -2242,6 +2728,7 @@ var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
|
|
|
2242
2728
|
PasskeyRegisterError,
|
|
2243
2729
|
PasswordlessStartError,
|
|
2244
2730
|
PasswordlessVerifyError,
|
|
2731
|
+
ServerAnonymousClient,
|
|
2245
2732
|
ServerClient,
|
|
2246
2733
|
ServerDatabaseClient,
|
|
2247
2734
|
ServerMfaClient,
|
|
@@ -2250,6 +2737,7 @@ var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
|
|
|
2250
2737
|
SignUpError,
|
|
2251
2738
|
StartLinkUserError,
|
|
2252
2739
|
StatefulStateStore,
|
|
2740
|
+
StatelessAnonymousStore,
|
|
2253
2741
|
StatelessStateStore,
|
|
2254
2742
|
TokenExchangeError,
|
|
2255
2743
|
TokenExchangeErrorCode,
|