@auth0/auth0-server-js 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +95 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +94 -21
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
ServerClient: () => ServerClient,
|
|
42
42
|
ServerMfaClient: () => ServerMfaClient,
|
|
43
43
|
ServerPasskeyClient: () => ServerPasskeyClient,
|
|
44
|
+
SessionExpiredError: () => SessionExpiredError,
|
|
44
45
|
StartLinkUserError: () => StartLinkUserError,
|
|
45
46
|
StatefulStateStore: () => StatefulStateStore,
|
|
46
47
|
StatelessStateStore: () => StatelessStateStore,
|
|
@@ -99,14 +100,55 @@ var IssuerValidationError = class extends Error {
|
|
|
99
100
|
this.name = "IssuerValidationError";
|
|
100
101
|
}
|
|
101
102
|
};
|
|
103
|
+
var SessionExpiredError = class extends Error {
|
|
104
|
+
code = "session_expired";
|
|
105
|
+
constructor(message) {
|
|
106
|
+
super(
|
|
107
|
+
message ?? "The session has expired because the upstream identity provider session ceiling was reached. The user needs to re-authenticate."
|
|
108
|
+
);
|
|
109
|
+
this.name = "SessionExpiredError";
|
|
110
|
+
}
|
|
111
|
+
};
|
|
102
112
|
|
|
103
113
|
// src/state/utils.ts
|
|
114
|
+
var SESSION_EXPIRY_LEEWAY = 30;
|
|
115
|
+
var MAX_PLAUSIBLE_UNIX_SECONDS = 1e10;
|
|
116
|
+
function isPlausibleUnixSeconds(value) {
|
|
117
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value < MAX_PLAUSIBLE_UNIX_SECONDS;
|
|
118
|
+
}
|
|
119
|
+
function extractSessionExpiry(claims) {
|
|
120
|
+
const value = claims?.session_expiry;
|
|
121
|
+
return isPlausibleUnixSeconds(value) ? value : void 0;
|
|
122
|
+
}
|
|
123
|
+
function isSessionExpiryReached(sessionExpiresAt, nowSeconds) {
|
|
124
|
+
if (sessionExpiresAt === void 0) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const now = nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
128
|
+
return now >= sessionExpiresAt - SESSION_EXPIRY_LEEWAY;
|
|
129
|
+
}
|
|
104
130
|
var createUpdatedTokenSet = (audience, response) => ({
|
|
105
131
|
audience,
|
|
106
132
|
accessToken: response.accessToken,
|
|
107
133
|
scope: response.scope,
|
|
108
134
|
expiresAt: response.expiresAt
|
|
109
135
|
});
|
|
136
|
+
function isSessionExpiryInPast(sessionExpiresAt, issuedAt) {
|
|
137
|
+
if (sessionExpiresAt === void 0) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const reference = isPlausibleUnixSeconds(issuedAt) ? issuedAt : Math.floor(Date.now() / 1e3);
|
|
141
|
+
return sessionExpiresAt <= reference + SESSION_EXPIRY_LEEWAY;
|
|
142
|
+
}
|
|
143
|
+
function applySessionExpiryAtLogin(stateData, claims) {
|
|
144
|
+
const sessionExpiresAt = extractSessionExpiry(claims);
|
|
145
|
+
if (isSessionExpiryInPast(sessionExpiresAt, claims?.iat)) {
|
|
146
|
+
throw new SessionExpiredError(
|
|
147
|
+
"The upstream identity provider session_expiry is at or before the issued-at time; refusing to create an already-expired session."
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return { ...stateData, sessionExpiresAt };
|
|
151
|
+
}
|
|
110
152
|
function updateStateData(audience, stateData, tokenEndpointResponse, context) {
|
|
111
153
|
if (stateData && tokenEndpointResponse.claims) {
|
|
112
154
|
const newSub = tokenEndpointResponse.claims.sub;
|
|
@@ -208,7 +250,7 @@ function getTelemetryConfig(config) {
|
|
|
208
250
|
return {
|
|
209
251
|
enabled: true,
|
|
210
252
|
name: config?.name ?? "@auth0/auth0-server-js",
|
|
211
|
-
version: config?.version ?? "1.
|
|
253
|
+
version: config?.version ?? "1.8.0"
|
|
212
254
|
};
|
|
213
255
|
}
|
|
214
256
|
|
|
@@ -270,9 +312,12 @@ var ServerMfaClient = class {
|
|
|
270
312
|
this.#options.stateStoreIdentifier,
|
|
271
313
|
storeOptions
|
|
272
314
|
);
|
|
273
|
-
const updatedStateData =
|
|
274
|
-
|
|
275
|
-
|
|
315
|
+
const updatedStateData = applySessionExpiryAtLogin(
|
|
316
|
+
updateStateData(audience, existingStateData, tokenResponse, {
|
|
317
|
+
domain: this.#options.domain
|
|
318
|
+
}),
|
|
319
|
+
tokenResponse.claims
|
|
320
|
+
);
|
|
276
321
|
await this.#options.stateStore.set(
|
|
277
322
|
this.#options.stateStoreIdentifier,
|
|
278
323
|
updatedStateData,
|
|
@@ -598,6 +643,7 @@ var ServerClient = class {
|
|
|
598
643
|
*
|
|
599
644
|
* @throws {MissingTransactionError} When no transaction was found.
|
|
600
645
|
* @throws {TokenByCodeError} If there was an issue requesting the access token.
|
|
646
|
+
* @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
|
|
601
647
|
*
|
|
602
648
|
* @returns A promise resolving to an object, containing the original appState (if present) and the authorizationDetails (when RAR was used).
|
|
603
649
|
*/
|
|
@@ -612,12 +658,15 @@ var ServerClient = class {
|
|
|
612
658
|
// TransactionData.codeVerifier is optional only to accommodate magic-link transactions.
|
|
613
659
|
codeVerifier: transactionData.codeVerifier
|
|
614
660
|
});
|
|
661
|
+
await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
|
|
615
662
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
616
|
-
const stateData =
|
|
617
|
-
|
|
618
|
-
|
|
663
|
+
const stateData = applySessionExpiryAtLogin(
|
|
664
|
+
updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
|
|
665
|
+
domain
|
|
666
|
+
}),
|
|
667
|
+
tokenEndpointResponse.claims
|
|
668
|
+
);
|
|
619
669
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
620
|
-
await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
|
|
621
670
|
return { appState: transactionData.appState, authorizationDetails: tokenEndpointResponse.authorizationDetails };
|
|
622
671
|
}
|
|
623
672
|
/**
|
|
@@ -627,6 +676,7 @@ var ServerClient = class {
|
|
|
627
676
|
*
|
|
628
677
|
* @throws {MissingSessionError} If there is no active session.
|
|
629
678
|
* @throws {BuildLinkUserUrlError} If there was an issue when building the Authorization URL.
|
|
679
|
+
* @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
|
|
630
680
|
*
|
|
631
681
|
* @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
|
|
632
682
|
*/
|
|
@@ -643,6 +693,10 @@ var ServerClient = class {
|
|
|
643
693
|
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
644
694
|
}
|
|
645
695
|
}
|
|
696
|
+
if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
|
|
697
|
+
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
698
|
+
throw new SessionExpiredError();
|
|
699
|
+
}
|
|
646
700
|
const domain = this.#getSessionDomain(stateData);
|
|
647
701
|
const authClient = this.#getAuthClient(domain);
|
|
648
702
|
const { linkUserUrl, codeVerifier } = await authClient.buildLinkUserUrl({
|
|
@@ -686,6 +740,7 @@ var ServerClient = class {
|
|
|
686
740
|
*
|
|
687
741
|
* @throws {MissingSessionError} If there is no active session.
|
|
688
742
|
* @throws {BuildUnlinkUserUrlError} If there was an issue when building the User Unlinking URL.
|
|
743
|
+
* @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
|
|
689
744
|
*
|
|
690
745
|
* @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
|
|
691
746
|
*/
|
|
@@ -702,6 +757,10 @@ var ServerClient = class {
|
|
|
702
757
|
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
703
758
|
}
|
|
704
759
|
}
|
|
760
|
+
if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
|
|
761
|
+
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
762
|
+
throw new SessionExpiredError();
|
|
763
|
+
}
|
|
705
764
|
const domain = this.#getSessionDomain(stateData);
|
|
706
765
|
const authClient = this.#getAuthClient(domain);
|
|
707
766
|
const { unlinkUserUrl, codeVerifier } = await authClient.buildUnlinkUserUrl({
|
|
@@ -746,6 +805,7 @@ var ServerClient = class {
|
|
|
746
805
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
747
806
|
*
|
|
748
807
|
* @throws {BackchannelAuthenticationError} If there was an issue when doing backchannel authentication.
|
|
808
|
+
* @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
|
|
749
809
|
*
|
|
750
810
|
* @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
|
|
751
811
|
*/
|
|
@@ -762,11 +822,11 @@ var ServerClient = class {
|
|
|
762
822
|
}
|
|
763
823
|
});
|
|
764
824
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
765
|
-
const stateData =
|
|
766
|
-
this.#options.authorizationParams?.audience ?? "default",
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
825
|
+
const stateData = applySessionExpiryAtLogin(
|
|
826
|
+
updateStateData(this.#options.authorizationParams?.audience ?? "default", existingStateData, tokenEndpointResponse, {
|
|
827
|
+
domain
|
|
828
|
+
}),
|
|
829
|
+
tokenEndpointResponse.claims
|
|
770
830
|
);
|
|
771
831
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
772
832
|
return {
|
|
@@ -961,6 +1021,10 @@ var ServerClient = class {
|
|
|
961
1021
|
return;
|
|
962
1022
|
}
|
|
963
1023
|
}
|
|
1024
|
+
if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
|
|
1025
|
+
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
964
1028
|
return stateData.user;
|
|
965
1029
|
}
|
|
966
1030
|
/**
|
|
@@ -977,6 +1041,10 @@ var ServerClient = class {
|
|
|
977
1041
|
return;
|
|
978
1042
|
}
|
|
979
1043
|
}
|
|
1044
|
+
if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
|
|
1045
|
+
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
980
1048
|
const { internal, ...sessionData } = stateData;
|
|
981
1049
|
return sessionData;
|
|
982
1050
|
}
|
|
@@ -993,6 +1061,7 @@ var ServerClient = class {
|
|
|
993
1061
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
994
1062
|
*
|
|
995
1063
|
* @throws {TokenByRefreshTokenError} If the refresh token was not found or there was an issue requesting the access token. When the cause is `mfa_required`, use `isMfaRequiredError(error)` to narrow the error and read `cause.mfa_token`.
|
|
1064
|
+
* @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and no refresh is attempted — the user must re-authenticate.
|
|
996
1065
|
*
|
|
997
1066
|
* @returns The Token Set, containing the access token, as well as additional information.
|
|
998
1067
|
*/
|
|
@@ -1020,6 +1089,10 @@ var ServerClient = class {
|
|
|
1020
1089
|
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
1021
1090
|
}
|
|
1022
1091
|
}
|
|
1092
|
+
if (stateData && isSessionExpiryReached(stateData.sessionExpiresAt)) {
|
|
1093
|
+
await this.#stateStore.delete(this.#stateStoreIdentifier, resolvedStoreOptions);
|
|
1094
|
+
throw new SessionExpiredError();
|
|
1095
|
+
}
|
|
1023
1096
|
const tokenSet = stateData?.tokenSets.find(
|
|
1024
1097
|
(tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
|
|
1025
1098
|
);
|
|
@@ -1394,14 +1467,14 @@ var AbstractSessionStore = class extends AbstractStateStore {
|
|
|
1394
1467
|
}
|
|
1395
1468
|
/**
|
|
1396
1469
|
* calculateMaxAge calculates the max age of the session based on createdAt and the rolling and absolute durations.
|
|
1470
|
+
* When sessionExpiresAt is provided, caps the maxAge to not exceed the time until that ceiling.
|
|
1397
1471
|
*/
|
|
1398
|
-
calculateMaxAge(createdAt) {
|
|
1399
|
-
if (!this.#rolling) {
|
|
1400
|
-
return this.#absoluteDuration;
|
|
1401
|
-
}
|
|
1472
|
+
calculateMaxAge(createdAt, sessionExpiresAt) {
|
|
1402
1473
|
const now = Date.now() / 1e3 | 0;
|
|
1403
|
-
|
|
1404
|
-
|
|
1474
|
+
let maxAge = this.#rolling ? Math.min(now + this.#inactivityDuration, createdAt + this.#absoluteDuration) - now : this.#absoluteDuration;
|
|
1475
|
+
if (sessionExpiresAt !== void 0) {
|
|
1476
|
+
maxAge = Math.min(maxAge, sessionExpiresAt - now);
|
|
1477
|
+
}
|
|
1405
1478
|
return maxAge > 0 ? maxAge : 0;
|
|
1406
1479
|
}
|
|
1407
1480
|
};
|
|
@@ -1429,7 +1502,7 @@ var StatefulStateStore = class extends AbstractSessionStore {
|
|
|
1429
1502
|
sessionId = generateId();
|
|
1430
1503
|
}
|
|
1431
1504
|
sessionId ??= generateId();
|
|
1432
|
-
const maxAge = this.calculateMaxAge(stateData.internal.createdAt);
|
|
1505
|
+
const maxAge = this.calculateMaxAge(stateData.internal.createdAt, stateData.sessionExpiresAt);
|
|
1433
1506
|
const cookieOpts = this.#getCookieOptions({
|
|
1434
1507
|
maxAge
|
|
1435
1508
|
});
|
|
@@ -1494,7 +1567,7 @@ var StatelessStateStore = class extends AbstractSessionStore {
|
|
|
1494
1567
|
this.#cookieHandler = cookieHandler;
|
|
1495
1568
|
}
|
|
1496
1569
|
async set(identifier, stateData, removeIfExists, options) {
|
|
1497
|
-
const maxAge = this.calculateMaxAge(stateData.internal.createdAt);
|
|
1570
|
+
const maxAge = this.calculateMaxAge(stateData.internal.createdAt, stateData.sessionExpiresAt);
|
|
1498
1571
|
const cookieOpts = this.#getCookieOptions({
|
|
1499
1572
|
maxAge
|
|
1500
1573
|
});
|
|
@@ -1578,6 +1651,7 @@ var import_auth0_auth_js3 = require("@auth0/auth0-auth-js");
|
|
|
1578
1651
|
ServerClient,
|
|
1579
1652
|
ServerMfaClient,
|
|
1580
1653
|
ServerPasskeyClient,
|
|
1654
|
+
SessionExpiredError,
|
|
1581
1655
|
StartLinkUserError,
|
|
1582
1656
|
StatefulStateStore,
|
|
1583
1657
|
StatelessStateStore,
|