@auth0/auth0-server-js 1.2.0 → 1.4.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 +6 -1
- package/dist/index.cjs +344 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -8
- package/dist/index.d.ts +89 -8
- package/dist/index.js +340 -47
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -49,7 +49,8 @@ const auth0 = new ServerClient<StoreOptions>({
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
The `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET` can be obtained from the [Auth0 Dashboard](https://manage.auth0.com) once you've created an application. **This application must be a `Regular Web Application`**.
|
|
52
|
-
The `AUTH0_REDIRECT_URI` is needed to tell Auth0 what URL to redirect back to after
|
|
52
|
+
The `AUTH0_REDIRECT_URI` is needed to tell Auth0 what URL to redirect back to after successful authentication, e.g. `http://localhost:3000/auth/callback`. Your application needs to handle this endpoint and call the SDK's `completeInteractiveLogin(url: string)` to finish the authentication process. See below for more information.
|
|
53
|
+
|
|
53
54
|
|
|
54
55
|
### 3. Configuring the Store
|
|
55
56
|
|
|
@@ -200,6 +201,10 @@ const auth0 = new ServerClient<StoreOptions>({
|
|
|
200
201
|
});
|
|
201
202
|
```
|
|
202
203
|
|
|
204
|
+
The `redirect_uri` must be an absolute URL.
|
|
205
|
+
|
|
206
|
+
If you need to compute an absolute redirect URI per request (for example, in multi-domain deployments), pass `authorizationParams.redirect_uri` to `startInteractiveLogin` to override the configured default.
|
|
207
|
+
|
|
203
208
|
> [!IMPORTANT]
|
|
204
209
|
> You will need to register the `AUTH0_REDIRECT_URI` in your Auth0 Application as an **Allowed Callback URLs** via the [Auth0 Dashboard](https://manage.auth0.com):
|
|
205
210
|
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,8 @@ __export(index_exports, {
|
|
|
24
24
|
AbstractTransactionStore: () => AbstractTransactionStore,
|
|
25
25
|
BackchannelLogoutError: () => BackchannelLogoutError,
|
|
26
26
|
CookieTransactionStore: () => CookieTransactionStore,
|
|
27
|
+
InvalidConfigurationError: () => InvalidConfigurationError,
|
|
28
|
+
IssuerValidationError: () => IssuerValidationError,
|
|
27
29
|
MissingRequiredArgumentError: () => MissingRequiredArgumentError,
|
|
28
30
|
MissingSessionError: () => MissingSessionError,
|
|
29
31
|
MissingTransactionError: () => MissingTransactionError,
|
|
@@ -70,6 +72,20 @@ var MissingSessionError = class extends Error {
|
|
|
70
72
|
this.name = "MissingSessionError";
|
|
71
73
|
}
|
|
72
74
|
};
|
|
75
|
+
var InvalidConfigurationError = class extends Error {
|
|
76
|
+
code = "invalid_configuration_error";
|
|
77
|
+
constructor(message) {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = "InvalidConfigurationError";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var IssuerValidationError = class extends Error {
|
|
83
|
+
code = "issuer_validation_error";
|
|
84
|
+
constructor(message) {
|
|
85
|
+
super(message);
|
|
86
|
+
this.name = "IssuerValidationError";
|
|
87
|
+
}
|
|
88
|
+
};
|
|
73
89
|
|
|
74
90
|
// src/state/utils.ts
|
|
75
91
|
var createUpdatedTokenSet = (audience, response) => ({
|
|
@@ -78,19 +94,20 @@ var createUpdatedTokenSet = (audience, response) => ({
|
|
|
78
94
|
scope: response.scope,
|
|
79
95
|
expiresAt: response.expiresAt
|
|
80
96
|
});
|
|
81
|
-
function updateStateData(audience,
|
|
82
|
-
if (
|
|
83
|
-
const isNewTokenSet = !
|
|
97
|
+
function updateStateData(audience, stateData, tokenEndpointResponse, context) {
|
|
98
|
+
if (stateData) {
|
|
99
|
+
const isNewTokenSet = !stateData.tokenSets.some(
|
|
84
100
|
(tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope
|
|
85
101
|
);
|
|
86
|
-
const tokenSets = isNewTokenSet ? [...
|
|
102
|
+
const tokenSets = isNewTokenSet ? [...stateData.tokenSets, createUpdatedTokenSet(audience, tokenEndpointResponse)] : stateData.tokenSets.map(
|
|
87
103
|
(tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope ? createUpdatedTokenSet(audience, tokenEndpointResponse) : tokenSet
|
|
88
104
|
);
|
|
89
105
|
return {
|
|
90
|
-
...
|
|
91
|
-
idToken: tokenEndpointResponse.idToken,
|
|
92
|
-
refreshToken: tokenEndpointResponse.refreshToken ??
|
|
93
|
-
tokenSets
|
|
106
|
+
...stateData,
|
|
107
|
+
idToken: tokenEndpointResponse.idToken ?? stateData.idToken,
|
|
108
|
+
refreshToken: tokenEndpointResponse.refreshToken ?? stateData.refreshToken,
|
|
109
|
+
tokenSets,
|
|
110
|
+
domain: context?.domain ?? stateData.domain
|
|
94
111
|
};
|
|
95
112
|
} else {
|
|
96
113
|
const user = tokenEndpointResponse.claims;
|
|
@@ -98,9 +115,8 @@ function updateStateData(audience, stateDate, tokenEndpointResponse) {
|
|
|
98
115
|
user,
|
|
99
116
|
idToken: tokenEndpointResponse.idToken,
|
|
100
117
|
refreshToken: tokenEndpointResponse.refreshToken,
|
|
101
|
-
tokenSets: [
|
|
102
|
-
|
|
103
|
-
],
|
|
118
|
+
tokenSets: [createUpdatedTokenSet(audience, tokenEndpointResponse)],
|
|
119
|
+
domain: context?.domain,
|
|
104
120
|
internal: {
|
|
105
121
|
sid: user?.sid,
|
|
106
122
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
@@ -108,9 +124,9 @@ function updateStateData(audience, stateDate, tokenEndpointResponse) {
|
|
|
108
124
|
};
|
|
109
125
|
}
|
|
110
126
|
}
|
|
111
|
-
function updateStateDataForConnectionTokenSet(options,
|
|
112
|
-
|
|
113
|
-
const isNewTokenSet = !
|
|
127
|
+
function updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointResponse) {
|
|
128
|
+
stateData.connectionTokenSets = stateData.connectionTokenSets || [];
|
|
129
|
+
const isNewTokenSet = !stateData.connectionTokenSets.some(
|
|
114
130
|
(tokenSet) => tokenSet.connection === options.connection && (!options.loginHint || tokenSet.loginHint === options.loginHint)
|
|
115
131
|
);
|
|
116
132
|
const connectionTokenSet = {
|
|
@@ -120,11 +136,11 @@ function updateStateDataForConnectionTokenSet(options, stateDate, tokenEndpointR
|
|
|
120
136
|
scope: tokenEndpointResponse.scope,
|
|
121
137
|
expiresAt: tokenEndpointResponse.expiresAt
|
|
122
138
|
};
|
|
123
|
-
const connectionTokenSets = isNewTokenSet ? [...
|
|
139
|
+
const connectionTokenSets = isNewTokenSet ? [...stateData.connectionTokenSets, connectionTokenSet] : stateData.connectionTokenSets.map(
|
|
124
140
|
(tokenSet) => tokenSet.connection === options.connection && (!options.loginHint || tokenSet.loginHint === options.loginHint) ? connectionTokenSet : tokenSet
|
|
125
141
|
);
|
|
126
142
|
return {
|
|
127
|
-
...
|
|
143
|
+
...stateData,
|
|
128
144
|
connectionTokenSets
|
|
129
145
|
};
|
|
130
146
|
}
|
|
@@ -146,19 +162,70 @@ var compareScopes = (scopes, requiredScopes) => {
|
|
|
146
162
|
};
|
|
147
163
|
|
|
148
164
|
// src/server-client.ts
|
|
165
|
+
var import_jose = require("jose");
|
|
166
|
+
|
|
167
|
+
// src/telemetry.ts
|
|
168
|
+
function getTelemetryConfig(config) {
|
|
169
|
+
if (config?.enabled === false) {
|
|
170
|
+
return config;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
enabled: true,
|
|
174
|
+
name: config?.name ?? "@auth0/auth0-server-js",
|
|
175
|
+
version: config?.version ?? "1.4.0"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/server-client.ts
|
|
180
|
+
var DEFAULT_SCOPES = "openid profile email offline_access";
|
|
181
|
+
var normalizeDomain = (value) => {
|
|
182
|
+
const trimmed = value.trim();
|
|
183
|
+
const parsed = trimmed.startsWith("http") ? new URL(trimmed) : new URL(`https://${trimmed}`);
|
|
184
|
+
return parsed.host.toLowerCase();
|
|
185
|
+
};
|
|
186
|
+
var decodeIssuer = (token) => {
|
|
187
|
+
try {
|
|
188
|
+
const { iss } = (0, import_jose.decodeJwt)(token);
|
|
189
|
+
return typeof iss === "string" ? iss : void 0;
|
|
190
|
+
} catch {
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var ensureOpenIdScope = (scope) => {
|
|
195
|
+
if (!scope) {
|
|
196
|
+
return DEFAULT_SCOPES;
|
|
197
|
+
}
|
|
198
|
+
const scopes = scope.split(" ");
|
|
199
|
+
if (!scopes.includes("openid")) {
|
|
200
|
+
scopes.unshift("openid");
|
|
201
|
+
}
|
|
202
|
+
return scopes.join(" ");
|
|
203
|
+
};
|
|
149
204
|
var ServerClient = class {
|
|
150
205
|
#options;
|
|
151
206
|
#transactionStore;
|
|
152
207
|
#transactionStoreIdentifier;
|
|
153
208
|
#stateStore;
|
|
154
209
|
#stateStoreIdentifier;
|
|
210
|
+
#authClientOptions;
|
|
211
|
+
#staticDomain;
|
|
212
|
+
#authClient;
|
|
155
213
|
/**
|
|
156
214
|
* The underlying `authClient` instance that can be used to interact with the Auth0 Authentication API.
|
|
157
215
|
* Generally, you should prefer to use the higher-level methods exposed on the `ServerClient` instance.
|
|
158
|
-
*
|
|
216
|
+
*
|
|
217
|
+
* This property can only be used when `domain` is configured as a static string.
|
|
218
|
+
* In resolver mode (`domain` as a function), the SDK resolves the domain per request,
|
|
219
|
+
* so use `ServerClient` methods instead.
|
|
220
|
+
*
|
|
159
221
|
* Important: the methods exposed on the `authClient` instance do not handle any session or state management.
|
|
160
222
|
*/
|
|
161
|
-
authClient
|
|
223
|
+
get authClient() {
|
|
224
|
+
if (!this.#authClient) {
|
|
225
|
+
throw new InvalidConfigurationError("authClient is only available when using a static domain configuration.");
|
|
226
|
+
}
|
|
227
|
+
return this.#authClient;
|
|
228
|
+
}
|
|
162
229
|
constructor(options) {
|
|
163
230
|
this.#options = options;
|
|
164
231
|
this.#stateStoreIdentifier = this.#options.stateIdentifier || "__a0_session";
|
|
@@ -171,17 +238,77 @@ var ServerClient = class {
|
|
|
171
238
|
if (!this.#options.transactionStore) {
|
|
172
239
|
throw new MissingRequiredArgumentError("transactionStore");
|
|
173
240
|
}
|
|
174
|
-
this.
|
|
175
|
-
domain
|
|
241
|
+
if (typeof this.#options.domain !== "string" && typeof this.#options.domain !== "function") {
|
|
242
|
+
throw new InvalidConfigurationError("domain must be a string or resolver function");
|
|
243
|
+
}
|
|
244
|
+
this.#authClientOptions = {
|
|
176
245
|
clientId: this.#options.clientId,
|
|
177
246
|
clientSecret: this.#options.clientSecret,
|
|
178
247
|
clientAssertionSigningKey: this.#options.clientAssertionSigningKey,
|
|
179
248
|
clientAssertionSigningAlg: this.#options.clientAssertionSigningAlg,
|
|
180
249
|
authorizationParams: this.#options.authorizationParams,
|
|
250
|
+
discoveryCache: this.#options.discoveryCache,
|
|
181
251
|
customFetch: this.#options.customFetch,
|
|
182
252
|
useMtls: this.#options.useMtls
|
|
253
|
+
};
|
|
254
|
+
if (typeof this.#options.domain === "string") {
|
|
255
|
+
const domain = normalizeDomain(this.#options.domain);
|
|
256
|
+
this.#staticDomain = domain;
|
|
257
|
+
this.#authClient = new import_auth0_auth_js.AuthClient({
|
|
258
|
+
domain,
|
|
259
|
+
...this.#authClientOptions,
|
|
260
|
+
telemetry: getTelemetryConfig(this.#options.telemetry)
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async #resolveDomain(storeOptions) {
|
|
265
|
+
if (typeof this.#options.domain === "function") {
|
|
266
|
+
const resolved = await this.#options.domain(storeOptions);
|
|
267
|
+
if (typeof resolved !== "string" || resolved.trim().length === 0) {
|
|
268
|
+
throw new InvalidConfigurationError("domainResolver returned no domain");
|
|
269
|
+
}
|
|
270
|
+
return normalizeDomain(resolved);
|
|
271
|
+
}
|
|
272
|
+
return normalizeDomain(this.#options.domain);
|
|
273
|
+
}
|
|
274
|
+
#createAuthClient(domain) {
|
|
275
|
+
return new import_auth0_auth_js.AuthClient({
|
|
276
|
+
domain,
|
|
277
|
+
...this.#authClientOptions,
|
|
278
|
+
telemetry: getTelemetryConfig(this.#options.telemetry)
|
|
183
279
|
});
|
|
184
280
|
}
|
|
281
|
+
#getAuthClient(domain) {
|
|
282
|
+
const normalizedDomain = normalizeDomain(domain);
|
|
283
|
+
if (this.#authClient && this.#staticDomain === normalizedDomain) {
|
|
284
|
+
return this.#authClient;
|
|
285
|
+
}
|
|
286
|
+
return this.#createAuthClient(normalizedDomain);
|
|
287
|
+
}
|
|
288
|
+
#getSessionDomain(stateData) {
|
|
289
|
+
if (stateData.domain) {
|
|
290
|
+
return normalizeDomain(stateData.domain);
|
|
291
|
+
}
|
|
292
|
+
if (this.#staticDomain) {
|
|
293
|
+
return this.#staticDomain;
|
|
294
|
+
}
|
|
295
|
+
const issuerFromClaims = stateData.user?.iss;
|
|
296
|
+
if (typeof issuerFromClaims === "string" && issuerFromClaims.trim().length > 0) {
|
|
297
|
+
return normalizeDomain(issuerFromClaims);
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
#isResolverMode() {
|
|
302
|
+
return typeof this.#options.domain === "function";
|
|
303
|
+
}
|
|
304
|
+
async #isSessionForCurrentDomain(stateData, storeOptions) {
|
|
305
|
+
const sessionDomain = this.#getSessionDomain(stateData);
|
|
306
|
+
if (!sessionDomain) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
310
|
+
return sessionDomain === resolvedDomain;
|
|
311
|
+
}
|
|
185
312
|
/**
|
|
186
313
|
* Starts the interactive login process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
|
|
187
314
|
* @param options Optional options used to configure the interactive login process.
|
|
@@ -196,16 +323,21 @@ var ServerClient = class {
|
|
|
196
323
|
if (!redirectUri) {
|
|
197
324
|
throw new MissingRequiredArgumentError("authorizationParams.redirect_uri");
|
|
198
325
|
}
|
|
199
|
-
const
|
|
326
|
+
const scope = ensureOpenIdScope(options?.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
|
|
327
|
+
const domain = await this.#resolveDomain(storeOptions);
|
|
328
|
+
const authClient = this.#getAuthClient(domain);
|
|
329
|
+
const { codeVerifier, authorizationUrl } = await authClient.buildAuthorizationUrl({
|
|
200
330
|
pushedAuthorizationRequests: options?.pushedAuthorizationRequests,
|
|
201
331
|
authorizationParams: {
|
|
202
332
|
...options?.authorizationParams,
|
|
203
|
-
redirect_uri: redirectUri
|
|
333
|
+
redirect_uri: redirectUri,
|
|
334
|
+
scope
|
|
204
335
|
}
|
|
205
336
|
});
|
|
206
337
|
const transactionState = {
|
|
207
338
|
audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
|
|
208
|
-
codeVerifier
|
|
339
|
+
codeVerifier,
|
|
340
|
+
domain
|
|
209
341
|
};
|
|
210
342
|
if (options?.appState) {
|
|
211
343
|
transactionState.appState = options.appState;
|
|
@@ -229,11 +361,15 @@ var ServerClient = class {
|
|
|
229
361
|
if (!transactionData) {
|
|
230
362
|
throw new MissingTransactionError();
|
|
231
363
|
}
|
|
232
|
-
const
|
|
364
|
+
const domain = transactionData.domain ?? await this.#resolveDomain(storeOptions);
|
|
365
|
+
const authClient = this.#getAuthClient(domain);
|
|
366
|
+
const tokenEndpointResponse = await authClient.getTokenByCode(url, {
|
|
233
367
|
codeVerifier: transactionData.codeVerifier
|
|
234
368
|
});
|
|
235
369
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
236
|
-
const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse
|
|
370
|
+
const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
|
|
371
|
+
domain
|
|
372
|
+
});
|
|
237
373
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
238
374
|
await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
|
|
239
375
|
return { appState: transactionData.appState, authorizationDetails: tokenEndpointResponse.authorizationDetails };
|
|
@@ -255,7 +391,15 @@ var ServerClient = class {
|
|
|
255
391
|
"Unable to start the user linking process without a logged in user. Ensure to login using the SDK before starting the user linking process."
|
|
256
392
|
);
|
|
257
393
|
}
|
|
258
|
-
|
|
394
|
+
if (this.#isResolverMode()) {
|
|
395
|
+
const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
|
|
396
|
+
if (!isCurrentDomain) {
|
|
397
|
+
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const domain = this.#getSessionDomain(stateData);
|
|
401
|
+
const authClient = this.#getAuthClient(domain);
|
|
402
|
+
const { linkUserUrl, codeVerifier } = await authClient.buildLinkUserUrl({
|
|
259
403
|
connection: options.connection,
|
|
260
404
|
connectionScope: options.connectionScope,
|
|
261
405
|
idToken: stateData.idToken,
|
|
@@ -263,7 +407,8 @@ var ServerClient = class {
|
|
|
263
407
|
});
|
|
264
408
|
const transactionState = {
|
|
265
409
|
audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
|
|
266
|
-
codeVerifier
|
|
410
|
+
codeVerifier,
|
|
411
|
+
domain
|
|
267
412
|
};
|
|
268
413
|
if (options?.appState) {
|
|
269
414
|
transactionState.appState = options.appState;
|
|
@@ -305,14 +450,23 @@ var ServerClient = class {
|
|
|
305
450
|
"Unable to start the user unlinking process without a logged in user. Ensure to login using the SDK before starting the user unlinking process."
|
|
306
451
|
);
|
|
307
452
|
}
|
|
308
|
-
|
|
453
|
+
if (this.#isResolverMode()) {
|
|
454
|
+
const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
|
|
455
|
+
if (!isCurrentDomain) {
|
|
456
|
+
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const domain = this.#getSessionDomain(stateData);
|
|
460
|
+
const authClient = this.#getAuthClient(domain);
|
|
461
|
+
const { unlinkUserUrl, codeVerifier } = await authClient.buildUnlinkUserUrl({
|
|
309
462
|
connection: options.connection,
|
|
310
463
|
idToken: stateData.idToken,
|
|
311
464
|
authorizationParams: options.authorizationParams
|
|
312
465
|
});
|
|
313
466
|
const transactionState = {
|
|
314
467
|
audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
|
|
315
|
-
codeVerifier
|
|
468
|
+
codeVerifier,
|
|
469
|
+
domain
|
|
316
470
|
};
|
|
317
471
|
if (options?.appState) {
|
|
318
472
|
transactionState.appState = options.appState;
|
|
@@ -350,16 +504,23 @@ var ServerClient = class {
|
|
|
350
504
|
* @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
|
|
351
505
|
*/
|
|
352
506
|
async loginBackchannel(options, storeOptions) {
|
|
353
|
-
const
|
|
507
|
+
const scope = ensureOpenIdScope(options.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
|
|
508
|
+
const domain = await this.#resolveDomain(storeOptions);
|
|
509
|
+
const authClient = this.#getAuthClient(domain);
|
|
510
|
+
const tokenEndpointResponse = await authClient.backchannelAuthentication({
|
|
354
511
|
bindingMessage: options.bindingMessage,
|
|
355
512
|
loginHint: options.loginHint,
|
|
356
|
-
authorizationParams:
|
|
513
|
+
authorizationParams: {
|
|
514
|
+
...options.authorizationParams,
|
|
515
|
+
scope
|
|
516
|
+
}
|
|
357
517
|
});
|
|
358
518
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
359
519
|
const stateData = updateStateData(
|
|
360
520
|
this.#options.authorizationParams?.audience ?? "default",
|
|
361
521
|
existingStateData,
|
|
362
|
-
tokenEndpointResponse
|
|
522
|
+
tokenEndpointResponse,
|
|
523
|
+
{ domain }
|
|
363
524
|
);
|
|
364
525
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
365
526
|
return {
|
|
@@ -373,16 +534,31 @@ var ServerClient = class {
|
|
|
373
534
|
*/
|
|
374
535
|
async getUser(storeOptions) {
|
|
375
536
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
376
|
-
|
|
537
|
+
if (!stateData) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (this.#isResolverMode()) {
|
|
541
|
+
const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
|
|
542
|
+
if (!isCurrentDomain) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return stateData.user;
|
|
377
547
|
}
|
|
378
548
|
/**
|
|
379
549
|
* Retrieve the user session from the store, or undefined if no session found.
|
|
380
550
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
381
|
-
* @returns The
|
|
551
|
+
* @returns The session or undefined if no session found in the store.
|
|
382
552
|
*/
|
|
383
553
|
async getSession(storeOptions) {
|
|
384
554
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
385
555
|
if (stateData) {
|
|
556
|
+
if (this.#isResolverMode()) {
|
|
557
|
+
const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
|
|
558
|
+
if (!isCurrentDomain) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
386
562
|
const { internal, ...sessionData } = stateData;
|
|
387
563
|
return sessionData;
|
|
388
564
|
}
|
|
@@ -400,6 +576,19 @@ var ServerClient = class {
|
|
|
400
576
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
401
577
|
const audience = this.#options.authorizationParams?.audience ?? "default";
|
|
402
578
|
const scope = this.#options.authorizationParams?.scope;
|
|
579
|
+
const sessionDomain = stateData ? this.#getSessionDomain(stateData) : this.#staticDomain;
|
|
580
|
+
if (this.#isResolverMode()) {
|
|
581
|
+
if (!stateData) {
|
|
582
|
+
throw new MissingSessionError("Unable to retrieve access token without a logged in user.");
|
|
583
|
+
}
|
|
584
|
+
if (!sessionDomain) {
|
|
585
|
+
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
586
|
+
}
|
|
587
|
+
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
588
|
+
if (sessionDomain !== resolvedDomain) {
|
|
589
|
+
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
403
592
|
const tokenSet = stateData?.tokenSets.find(
|
|
404
593
|
(tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
|
|
405
594
|
);
|
|
@@ -411,11 +600,14 @@ var ServerClient = class {
|
|
|
411
600
|
"The access token has expired and a refresh token was not provided. The user needs to re-authenticate."
|
|
412
601
|
);
|
|
413
602
|
}
|
|
414
|
-
const
|
|
603
|
+
const domainForSession = sessionDomain;
|
|
604
|
+
const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenByRefreshToken({
|
|
415
605
|
refreshToken: stateData.refreshToken
|
|
416
606
|
});
|
|
417
607
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
418
|
-
const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse
|
|
608
|
+
const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse, {
|
|
609
|
+
domain: domainForSession
|
|
610
|
+
});
|
|
419
611
|
await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
|
|
420
612
|
return {
|
|
421
613
|
accessToken: tokenEndpointResponse.accessToken,
|
|
@@ -441,6 +633,19 @@ var ServerClient = class {
|
|
|
441
633
|
*/
|
|
442
634
|
async getAccessTokenForConnection(options, storeOptions) {
|
|
443
635
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
636
|
+
const sessionDomain = stateData ? this.#getSessionDomain(stateData) : this.#staticDomain;
|
|
637
|
+
if (this.#isResolverMode()) {
|
|
638
|
+
if (!stateData) {
|
|
639
|
+
throw new MissingSessionError("Unable to retrieve an access token for a connection without a logged in user.");
|
|
640
|
+
}
|
|
641
|
+
if (!sessionDomain) {
|
|
642
|
+
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
643
|
+
}
|
|
644
|
+
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
645
|
+
if (sessionDomain !== resolvedDomain) {
|
|
646
|
+
throw new MissingSessionError("Session domain does not match the current domain.");
|
|
647
|
+
}
|
|
648
|
+
}
|
|
444
649
|
const connectionTokenSet = stateData?.connectionTokenSets?.find(
|
|
445
650
|
(tokenSet) => tokenSet.connection === options.connection
|
|
446
651
|
);
|
|
@@ -452,12 +657,20 @@ var ServerClient = class {
|
|
|
452
657
|
"A refresh token was not found but is required to be able to retrieve an access token for a connection."
|
|
453
658
|
);
|
|
454
659
|
}
|
|
455
|
-
const
|
|
660
|
+
const domainForSession = sessionDomain;
|
|
661
|
+
const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenForConnection({
|
|
456
662
|
connection: options.connection,
|
|
457
663
|
loginHint: options.loginHint,
|
|
458
664
|
refreshToken: stateData.refreshToken
|
|
459
665
|
});
|
|
460
|
-
const updatedStateData = updateStateDataForConnectionTokenSet(
|
|
666
|
+
const updatedStateData = updateStateDataForConnectionTokenSet(
|
|
667
|
+
options,
|
|
668
|
+
{
|
|
669
|
+
...stateData,
|
|
670
|
+
domain: stateData.domain ?? domainForSession
|
|
671
|
+
},
|
|
672
|
+
tokenEndpointResponse
|
|
673
|
+
);
|
|
461
674
|
await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
|
|
462
675
|
return {
|
|
463
676
|
accessToken: tokenEndpointResponse.accessToken,
|
|
@@ -474,8 +687,21 @@ var ServerClient = class {
|
|
|
474
687
|
* @returns {URL}
|
|
475
688
|
*/
|
|
476
689
|
async logout(options, storeOptions) {
|
|
477
|
-
|
|
478
|
-
|
|
690
|
+
if (!this.#isResolverMode()) {
|
|
691
|
+
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
692
|
+
return this.authClient.buildLogoutUrl(options);
|
|
693
|
+
}
|
|
694
|
+
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
695
|
+
const authClient = this.#getAuthClient(resolvedDomain);
|
|
696
|
+
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
697
|
+
const sessionDomain = stateData ? this.#getSessionDomain(stateData) : void 0;
|
|
698
|
+
if (!stateData) {
|
|
699
|
+
return authClient.buildLogoutUrl(options);
|
|
700
|
+
}
|
|
701
|
+
if (sessionDomain && sessionDomain === resolvedDomain) {
|
|
702
|
+
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
703
|
+
}
|
|
704
|
+
return authClient.buildLogoutUrl(options);
|
|
479
705
|
}
|
|
480
706
|
/**
|
|
481
707
|
* Handles the backchannel logout process by verifying the logout token and deleting the session from the store if the logout token was considered valid.
|
|
@@ -489,19 +715,37 @@ var ServerClient = class {
|
|
|
489
715
|
if (!logoutToken) {
|
|
490
716
|
throw new BackchannelLogoutError("Missing Logout Token");
|
|
491
717
|
}
|
|
492
|
-
|
|
493
|
-
|
|
718
|
+
if (!this.#isResolverMode()) {
|
|
719
|
+
const logoutTokenClaims2 = await this.authClient.verifyLogoutToken({ logoutToken });
|
|
720
|
+
await this.#stateStore.deleteByLogoutToken(logoutTokenClaims2, storeOptions);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const issuer = decodeIssuer(logoutToken);
|
|
724
|
+
if (!issuer) {
|
|
725
|
+
throw new BackchannelLogoutError("Logout token is missing an issuer");
|
|
726
|
+
}
|
|
727
|
+
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
728
|
+
const domain = normalizeDomain(issuer);
|
|
729
|
+
if (domain !== resolvedDomain) {
|
|
730
|
+
throw new BackchannelLogoutError("Logout token issuer does not match the resolved domain");
|
|
731
|
+
}
|
|
732
|
+
const authClient = this.#getAuthClient(domain);
|
|
733
|
+
const logoutTokenClaims = await authClient.verifyLogoutToken({ logoutToken });
|
|
734
|
+
await this.#stateStore.deleteByLogoutToken({ ...logoutTokenClaims, iss: issuer }, storeOptions);
|
|
494
735
|
}
|
|
495
736
|
};
|
|
496
737
|
|
|
497
738
|
// src/encryption/index.ts
|
|
498
|
-
var
|
|
739
|
+
var import_jose2 = require("jose");
|
|
499
740
|
var ENC = "A256CBC-HS512";
|
|
500
741
|
var ALG = "dir";
|
|
501
742
|
var DIGEST = "SHA-256";
|
|
502
743
|
var BIT_LENGTH = 512;
|
|
503
744
|
var HKDF_INFO = "derived cookie encryption secret";
|
|
504
745
|
var encoder;
|
|
746
|
+
function isDecryptionError(e) {
|
|
747
|
+
return e instanceof import_jose2.errors.JWEDecryptionFailed || e instanceof import_jose2.errors.JWEInvalid;
|
|
748
|
+
}
|
|
505
749
|
async function deriveEncryptionSecret(secret, salt, kid) {
|
|
506
750
|
encoder ||= new TextEncoder();
|
|
507
751
|
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), "HKDF", false, ["deriveBits"]);
|
|
@@ -518,13 +762,24 @@ async function deriveEncryptionSecret(secret, salt, kid) {
|
|
|
518
762
|
)
|
|
519
763
|
);
|
|
520
764
|
}
|
|
521
|
-
async function
|
|
765
|
+
async function _encrypt(payload, secret, salt, expiration) {
|
|
522
766
|
const kid = crypto.randomUUID();
|
|
523
767
|
const encryptionSecret = await deriveEncryptionSecret(secret, salt, kid);
|
|
524
|
-
return await new
|
|
768
|
+
return await new import_jose2.EncryptJWT(payload).setProtectedHeader({ enc: ENC, alg: ALG, kid }).setExpirationTime(expiration).encrypt(encryptionSecret);
|
|
525
769
|
}
|
|
526
|
-
async function
|
|
527
|
-
|
|
770
|
+
async function encrypt(payload, secret, salt, expiration) {
|
|
771
|
+
if (typeof secret === "string") {
|
|
772
|
+
return await _encrypt(payload, secret, salt, expiration);
|
|
773
|
+
} else {
|
|
774
|
+
const [newSecret] = secret;
|
|
775
|
+
if (!newSecret) {
|
|
776
|
+
throw new InvalidConfigurationError("At least one secret must be provided");
|
|
777
|
+
}
|
|
778
|
+
return await _encrypt(payload, newSecret, salt, expiration);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
async function _decrypt(value, secret, salt) {
|
|
782
|
+
const res = await (0, import_jose2.jwtDecrypt)(
|
|
528
783
|
value,
|
|
529
784
|
async (protectedHeader) => {
|
|
530
785
|
if (!protectedHeader.kid) {
|
|
@@ -536,6 +791,35 @@ async function decrypt(value, secret, salt) {
|
|
|
536
791
|
);
|
|
537
792
|
return res.payload;
|
|
538
793
|
}
|
|
794
|
+
async function decrypt(value, secret, salt) {
|
|
795
|
+
if (typeof secret === "string") {
|
|
796
|
+
return await _decrypt(value, secret, salt);
|
|
797
|
+
} else {
|
|
798
|
+
const [newSecret, ...oldSecrets] = secret;
|
|
799
|
+
if (!newSecret) {
|
|
800
|
+
throw new InvalidConfigurationError("At least one secret must be provided");
|
|
801
|
+
}
|
|
802
|
+
let firstError;
|
|
803
|
+
try {
|
|
804
|
+
return await _decrypt(value, newSecret, salt);
|
|
805
|
+
} catch (err) {
|
|
806
|
+
firstError = err;
|
|
807
|
+
if (!isDecryptionError(err)) {
|
|
808
|
+
throw firstError;
|
|
809
|
+
}
|
|
810
|
+
for (const oldSecret of oldSecrets) {
|
|
811
|
+
try {
|
|
812
|
+
return await _decrypt(value, oldSecret, salt);
|
|
813
|
+
} catch (fallbackErr) {
|
|
814
|
+
if (!isDecryptionError(fallbackErr)) {
|
|
815
|
+
throw fallbackErr;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
throw firstError;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
539
823
|
|
|
540
824
|
// src/store/abstract-store.ts
|
|
541
825
|
var AbstractStore = class {
|
|
@@ -547,7 +831,14 @@ var AbstractStore = class {
|
|
|
547
831
|
return await encrypt(stateData, this.options.secret, identifier, expiration);
|
|
548
832
|
}
|
|
549
833
|
async decrypt(identifier, encryptedStateData) {
|
|
550
|
-
|
|
834
|
+
try {
|
|
835
|
+
return await decrypt(encryptedStateData, this.options.secret, identifier);
|
|
836
|
+
} catch (e) {
|
|
837
|
+
if (isDecryptionError(e)) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
throw e;
|
|
841
|
+
}
|
|
551
842
|
}
|
|
552
843
|
};
|
|
553
844
|
|
|
@@ -674,7 +965,9 @@ var StatefulStateStore = class extends AbstractSessionStore {
|
|
|
674
965
|
const cookieValue = this.#cookieHandler.getCookie(identifier, options);
|
|
675
966
|
if (cookieValue) {
|
|
676
967
|
const sessionCookie = await this.decrypt(identifier, cookieValue);
|
|
677
|
-
|
|
968
|
+
if (sessionCookie) {
|
|
969
|
+
return sessionCookie.id;
|
|
970
|
+
}
|
|
678
971
|
}
|
|
679
972
|
}
|
|
680
973
|
deleteByLogoutToken(claims, options) {
|
|
@@ -762,6 +1055,8 @@ var StatelessStateStore = class extends AbstractSessionStore {
|
|
|
762
1055
|
AbstractTransactionStore,
|
|
763
1056
|
BackchannelLogoutError,
|
|
764
1057
|
CookieTransactionStore,
|
|
1058
|
+
InvalidConfigurationError,
|
|
1059
|
+
IssuerValidationError,
|
|
765
1060
|
MissingRequiredArgumentError,
|
|
766
1061
|
MissingSessionError,
|
|
767
1062
|
MissingTransactionError,
|