@thunderid/node 1.2.2 → 1.3.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/dist/ThunderIDNodeClient.d.ts +48 -1
- package/dist/ThunderIDNodeClient.d.ts.map +1 -1
- package/dist/cjs/index.cjs +118 -46
- package/dist/index.js +117 -46
- package/dist/utils/NodeCryptoUtils.d.ts.map +1 -1
- package/dist/utils/SessionUtils.d.ts +2 -1
- package/dist/utils/SessionUtils.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { BackchannelLogoutResult, ExtendedAuthorizeRequestUrlParams, IdToken, OIDCEndpoints, Storage, ThunderIDJavaScriptClient, TokenExchangeRequestConfig, TokenResponse, User } from '@thunderid/javascript';
|
|
1
|
+
import { BackchannelLogoutResult, ExtendedAuthorizeRequestUrlParams, IdToken, ManagedFlowListResponse, ManagedUserListResponse, ManagedUserTypeListResponse, OIDCEndpoints, Storage, ThunderIDJavaScriptClient, TokenExchangeRequestConfig, TokenResponse, User } from '@thunderid/javascript';
|
|
2
2
|
import { ThunderIDNodeConfig } from './models/config';
|
|
3
3
|
declare class ThunderIDNodeClient<T extends ThunderIDNodeConfig = ThunderIDNodeConfig> extends ThunderIDJavaScriptClient<T> {
|
|
4
4
|
private _nodeInstanceId;
|
|
5
5
|
private _handledLogoutTokens;
|
|
6
|
+
private _refreshesInFlight;
|
|
6
7
|
constructor(instanceId?: number);
|
|
7
8
|
initialize(config: T, storage?: Storage): Promise<boolean>;
|
|
8
9
|
getInstanceId(): number;
|
|
@@ -45,8 +46,17 @@ declare class ThunderIDNodeClient<T extends ThunderIDNodeConfig = ThunderIDNodeC
|
|
|
45
46
|
*/
|
|
46
47
|
private assertNotReplayed;
|
|
47
48
|
private rememberHandled;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the session holds a valid access token. An expired access token is refreshed when the
|
|
51
|
+
* session holds a refresh token. A session whose access token expired and cannot be refreshed is
|
|
52
|
+
* removed.
|
|
53
|
+
*/
|
|
48
54
|
isSignedIn(userId?: string): Promise<boolean>;
|
|
49
55
|
getIdToken(userId?: string): Promise<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Refreshes the session's tokens. Concurrent calls for one session share a single request, so a
|
|
58
|
+
* rotated refresh token is not sent twice.
|
|
59
|
+
*/
|
|
50
60
|
refreshAccessToken(userId?: string): Promise<TokenResponse | User>;
|
|
51
61
|
revokeAccessToken(userId?: string): Promise<Response | boolean>;
|
|
52
62
|
getDecodedIdToken(userId?: string, idToken?: string): Promise<IdToken>;
|
|
@@ -59,6 +69,43 @@ declare class ThunderIDNodeClient<T extends ThunderIDNodeConfig = ThunderIDNodeC
|
|
|
59
69
|
* attribute the user type schema declares `credential: true`).
|
|
60
70
|
*/
|
|
61
71
|
updateUserCredentials(payload: Record<string, string>, userId?: string): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* The URL inputs and bearer token for a management request made on behalf of the session identified by
|
|
74
|
+
* `userId`. The access token stays on the server.
|
|
75
|
+
*/
|
|
76
|
+
private managementRequestConfig;
|
|
77
|
+
/**
|
|
78
|
+
* Retrieves one page of the user directory on behalf of the session identified by `userId`.
|
|
79
|
+
*
|
|
80
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
81
|
+
*/
|
|
82
|
+
getUsers(options?: {
|
|
83
|
+
filter?: string;
|
|
84
|
+
limit?: number;
|
|
85
|
+
offset?: number;
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
}, userId?: string): Promise<ManagedUserListResponse>;
|
|
88
|
+
/**
|
|
89
|
+
* Retrieves one page of flows on behalf of the session identified by `userId`.
|
|
90
|
+
*
|
|
91
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
92
|
+
*/
|
|
93
|
+
getFlows(options?: {
|
|
94
|
+
flowType?: string;
|
|
95
|
+
limit?: number;
|
|
96
|
+
offset?: number;
|
|
97
|
+
signal?: AbortSignal;
|
|
98
|
+
}, userId?: string): Promise<ManagedFlowListResponse>;
|
|
99
|
+
/**
|
|
100
|
+
* Retrieves one page of user types on behalf of the session identified by `userId`.
|
|
101
|
+
*
|
|
102
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
103
|
+
*/
|
|
104
|
+
getUserTypes(options?: {
|
|
105
|
+
limit?: number;
|
|
106
|
+
offset?: number;
|
|
107
|
+
signal?: AbortSignal;
|
|
108
|
+
}, userId?: string): Promise<ManagedUserTypeListResponse>;
|
|
62
109
|
}
|
|
63
110
|
export default ThunderIDNodeClient;
|
|
64
111
|
//# sourceMappingURL=ThunderIDNodeClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThunderIDNodeClient.d.ts","sourceRoot":"","sources":["../src/ThunderIDNodeClient.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,uBAAuB,EACvB,iCAAiC,
|
|
1
|
+
{"version":3,"file":"ThunderIDNodeClient.d.ts","sourceRoot":"","sources":["../src/ThunderIDNodeClient.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -31,8 +31,10 @@ let base64url = require("base64url");
|
|
|
31
31
|
base64url = __toESM(base64url);
|
|
32
32
|
let fast_sha256 = require("fast-sha256");
|
|
33
33
|
fast_sha256 = __toESM(fast_sha256);
|
|
34
|
-
let
|
|
35
|
-
|
|
34
|
+
let jose_key_import = require("jose/key/import");
|
|
35
|
+
jose_key_import = __toESM(jose_key_import);
|
|
36
|
+
let jose_jwt_verify = require("jose/jwt/verify");
|
|
37
|
+
jose_jwt_verify = __toESM(jose_jwt_verify);
|
|
36
38
|
let secure_random_bytes = require("secure-random-bytes");
|
|
37
39
|
secure_random_bytes = __toESM(secure_random_bytes);
|
|
38
40
|
let uuid = require("uuid");
|
|
@@ -76,8 +78,7 @@ var NodeCryptoUtils = class {
|
|
|
76
78
|
return (0, secure_random_bytes.default)(length);
|
|
77
79
|
}
|
|
78
80
|
async verifyJwt(idToken, jwk, algorithms, clientId, issuer, subject, clockTolerance) {
|
|
79
|
-
|
|
80
|
-
return jose.jwtVerify(idToken, key, {
|
|
81
|
+
return (0, jose_jwt_verify.jwtVerify)(idToken, await (0, jose_key_import.importJWK)(jwk), {
|
|
81
82
|
algorithms,
|
|
82
83
|
audience: [clientId],
|
|
83
84
|
clockTolerance,
|
|
@@ -164,49 +165,12 @@ var SessionIndex = class {
|
|
|
164
165
|
};
|
|
165
166
|
var SessionIndex_default = SessionIndex;
|
|
166
167
|
|
|
167
|
-
//#endregion
|
|
168
|
-
//#region src/utils/SessionUtils.ts
|
|
169
|
-
const UUID_VERSION = 4;
|
|
170
|
-
/**
|
|
171
|
-
* Utility class for session validation and UUID management.
|
|
172
|
-
*/
|
|
173
|
-
var SessionUtils = class {
|
|
174
|
-
constructor() {}
|
|
175
|
-
/**
|
|
176
|
-
* Generates a new UUID v4 string.
|
|
177
|
-
*
|
|
178
|
-
* @returns A new UUID string.
|
|
179
|
-
*/
|
|
180
|
-
static createUUID() {
|
|
181
|
-
return (0, uuid.v4)();
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Returns `true` if the given string is a valid UUID v4.
|
|
185
|
-
*
|
|
186
|
-
* @param uuid - The UUID string to validate.
|
|
187
|
-
*/
|
|
188
|
-
static validateUUID(uuid$1) {
|
|
189
|
-
if ((0, uuid.validate)(uuid$1) && (0, uuid.version)(uuid$1) === UUID_VERSION) return Promise.resolve(true);
|
|
190
|
-
return Promise.resolve(false);
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Returns `true` if the session token is still within its validity window.
|
|
194
|
-
*
|
|
195
|
-
* @param sessionData - The session data to check.
|
|
196
|
-
*/
|
|
197
|
-
static validateSession(sessionData) {
|
|
198
|
-
const currentTime = Date.now();
|
|
199
|
-
const expiryTimeStamp = sessionData.created_at + parseInt(sessionData.expires_in, 10) * 60 * 1e3;
|
|
200
|
-
return Promise.resolve(currentTime < expiryTimeStamp);
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
var SessionUtils_default = SessionUtils;
|
|
204
|
-
|
|
205
168
|
//#endregion
|
|
206
169
|
//#region src/ThunderIDNodeClient.ts
|
|
207
170
|
var ThunderIDNodeClient = class extends __thunderid_javascript.ThunderIDJavaScriptClient {
|
|
208
171
|
_nodeInstanceId = 0;
|
|
209
172
|
_handledLogoutTokens = /* @__PURE__ */ new Map();
|
|
173
|
+
_refreshesInFlight = /* @__PURE__ */ new Map();
|
|
210
174
|
constructor(instanceId = 0) {
|
|
211
175
|
super(void 0, new NodeCryptoUtils_default());
|
|
212
176
|
this._nodeInstanceId = instanceId;
|
|
@@ -382,13 +346,23 @@ var ThunderIDNodeClient = class extends __thunderid_javascript.ThunderIDJavaScri
|
|
|
382
346
|
rememberHandled(claims) {
|
|
383
347
|
this._handledLogoutTokens.set(claims.jti, claims.exp * 1e3);
|
|
384
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Whether the session holds a valid access token. An expired access token is refreshed when the
|
|
351
|
+
* session holds a refresh token. A session whose access token expired and cannot be refreshed is
|
|
352
|
+
* removed.
|
|
353
|
+
*/
|
|
385
354
|
async isSignedIn(userId) {
|
|
386
355
|
try {
|
|
387
|
-
if (
|
|
356
|
+
if (await super.isSignedIn(userId)) return true;
|
|
388
357
|
const sm = this.getStorageManager();
|
|
389
358
|
const sessionData = await sm.getSessionData(userId);
|
|
390
|
-
if (
|
|
391
|
-
if (
|
|
359
|
+
if (!sessionData?.access_token) return false;
|
|
360
|
+
if (sessionData.refresh_token) try {
|
|
361
|
+
await this.refreshAccessToken(userId);
|
|
362
|
+
return true;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
__thunderid_javascript.logger.debug("The expired session could not be refreshed, so it is removed.", error);
|
|
365
|
+
}
|
|
392
366
|
await this.forgetSession(userId, sessionData);
|
|
393
367
|
await sm.removeSessionData(userId);
|
|
394
368
|
return false;
|
|
@@ -400,8 +374,22 @@ var ThunderIDNodeClient = class extends __thunderid_javascript.ThunderIDJavaScri
|
|
|
400
374
|
if (!await this.isSignedIn(userId)) return Promise.reject(new __thunderid_javascript.ThunderIDAuthException("NODE-AUTH_CLIENT-GIT-NF01", "The user is not logged in.", "No session was found for the requested user."));
|
|
401
375
|
return super.getIdToken(userId);
|
|
402
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Refreshes the session's tokens. Concurrent calls for one session share a single request, so a
|
|
379
|
+
* rotated refresh token is not sent twice.
|
|
380
|
+
*/
|
|
403
381
|
async refreshAccessToken(userId) {
|
|
404
|
-
|
|
382
|
+
const key = userId ?? "";
|
|
383
|
+
const inFlight = this._refreshesInFlight.get(key);
|
|
384
|
+
if (inFlight) return inFlight;
|
|
385
|
+
const refresh = (async () => {
|
|
386
|
+
await this.loadOpenIDProviderConfiguration(false);
|
|
387
|
+
return super.refreshAccessToken(userId);
|
|
388
|
+
})().finally(() => {
|
|
389
|
+
this._refreshesInFlight.delete(key);
|
|
390
|
+
});
|
|
391
|
+
this._refreshesInFlight.set(key, refresh);
|
|
392
|
+
return refresh;
|
|
405
393
|
}
|
|
406
394
|
async revokeAccessToken(userId) {
|
|
407
395
|
await this.forgetSession(userId);
|
|
@@ -436,6 +424,51 @@ var ThunderIDNodeClient = class extends __thunderid_javascript.ThunderIDJavaScri
|
|
|
436
424
|
url: (0, __thunderid_javascript.resolveResourceEndpoint)("usersMeCredentials", configData)
|
|
437
425
|
});
|
|
438
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* The URL inputs and bearer token for a management request made on behalf of the session identified by
|
|
429
|
+
* `userId`. The access token stays on the server.
|
|
430
|
+
*/
|
|
431
|
+
async managementRequestConfig(key, userId) {
|
|
432
|
+
const configData = await this.getStorageManager().getConfigData();
|
|
433
|
+
return {
|
|
434
|
+
baseUrl: configData?.baseUrl,
|
|
435
|
+
headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}` },
|
|
436
|
+
url: (0, __thunderid_javascript.resolveResourceEndpoint)(key, configData)
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Retrieves one page of the user directory on behalf of the session identified by `userId`.
|
|
441
|
+
*
|
|
442
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
443
|
+
*/
|
|
444
|
+
async getUsers(options = {}, userId) {
|
|
445
|
+
return (0, __thunderid_javascript.getUsers)({
|
|
446
|
+
...options,
|
|
447
|
+
...await this.managementRequestConfig("users", userId)
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Retrieves one page of flows on behalf of the session identified by `userId`.
|
|
452
|
+
*
|
|
453
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
454
|
+
*/
|
|
455
|
+
async getFlows(options = {}, userId) {
|
|
456
|
+
return (0, __thunderid_javascript.getFlows)({
|
|
457
|
+
...options,
|
|
458
|
+
...await this.managementRequestConfig("flows", userId)
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Retrieves one page of user types on behalf of the session identified by `userId`.
|
|
463
|
+
*
|
|
464
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
465
|
+
*/
|
|
466
|
+
async getUserTypes(options = {}, userId) {
|
|
467
|
+
return (0, __thunderid_javascript.getUserTypes)({
|
|
468
|
+
...options,
|
|
469
|
+
...await this.managementRequestConfig("userTypes", userId)
|
|
470
|
+
});
|
|
471
|
+
}
|
|
439
472
|
};
|
|
440
473
|
var ThunderIDNodeClient_default = ThunderIDNodeClient;
|
|
441
474
|
|
|
@@ -544,6 +577,45 @@ var CookieChunking = class CookieChunking {
|
|
|
544
577
|
};
|
|
545
578
|
var CookieChunking_default = CookieChunking;
|
|
546
579
|
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/utils/SessionUtils.ts
|
|
582
|
+
const UUID_VERSION = 4;
|
|
583
|
+
/**
|
|
584
|
+
* Utility class for session validation and UUID management.
|
|
585
|
+
*/
|
|
586
|
+
var SessionUtils = class {
|
|
587
|
+
constructor() {}
|
|
588
|
+
/**
|
|
589
|
+
* Generates a new UUID v4 string.
|
|
590
|
+
*
|
|
591
|
+
* @returns A new UUID string.
|
|
592
|
+
*/
|
|
593
|
+
static createUUID() {
|
|
594
|
+
return (0, uuid.v4)();
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Returns `true` if the given string is a valid UUID v4.
|
|
598
|
+
*
|
|
599
|
+
* @param uuid - The UUID string to validate.
|
|
600
|
+
*/
|
|
601
|
+
static validateUUID(uuid$1) {
|
|
602
|
+
if ((0, uuid.validate)(uuid$1) && (0, uuid.version)(uuid$1) === UUID_VERSION) return Promise.resolve(true);
|
|
603
|
+
return Promise.resolve(false);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Returns `true` if the session token is still within its validity window.
|
|
607
|
+
*
|
|
608
|
+
* @param sessionData - The session data to check. `expires_in` is in seconds, as the token
|
|
609
|
+
* endpoint returns it, and `created_at` in milliseconds.
|
|
610
|
+
*/
|
|
611
|
+
static validateSession(sessionData) {
|
|
612
|
+
const currentTime = Date.now();
|
|
613
|
+
const expiryTimeStamp = sessionData.created_at + parseInt(sessionData.expires_in, 10) * 1e3;
|
|
614
|
+
return Promise.resolve(currentTime < expiryTimeStamp);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
var SessionUtils_default = SessionUtils;
|
|
618
|
+
|
|
547
619
|
//#endregion
|
|
548
620
|
//#region src/utils/LoggedOutSessions.ts
|
|
549
621
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import fetch, { Headers, Request, Response } from "cross-fetch";
|
|
2
|
-
import { BackchannelLogoutConstants, InvalidLogoutTokenError, ThunderIDAuthException, ThunderIDJavaScriptClient, getVendorPrefix, logger, resolveResourceEndpoint, updateMeCredentials } from "@thunderid/javascript";
|
|
2
|
+
import { BackchannelLogoutConstants, InvalidLogoutTokenError, ThunderIDAuthException, ThunderIDJavaScriptClient, getFlows, getUserTypes, getUsers, getVendorPrefix, logger, resolveResourceEndpoint, updateMeCredentials } from "@thunderid/javascript";
|
|
3
3
|
import cache from "memory-cache";
|
|
4
4
|
import base64url from "base64url";
|
|
5
5
|
import sha256 from "fast-sha256";
|
|
6
|
-
import
|
|
6
|
+
import { importJWK } from "jose/key/import";
|
|
7
|
+
import { jwtVerify } from "jose/jwt/verify";
|
|
7
8
|
import randombytes from "secure-random-bytes";
|
|
8
9
|
import { v4, validate, version } from "uuid";
|
|
9
10
|
|
|
@@ -47,8 +48,7 @@ var NodeCryptoUtils = class {
|
|
|
47
48
|
return randombytes(length);
|
|
48
49
|
}
|
|
49
50
|
async verifyJwt(idToken, jwk, algorithms, clientId, issuer, subject, clockTolerance) {
|
|
50
|
-
|
|
51
|
-
return jose.jwtVerify(idToken, key, {
|
|
51
|
+
return jwtVerify(idToken, await importJWK(jwk), {
|
|
52
52
|
algorithms,
|
|
53
53
|
audience: [clientId],
|
|
54
54
|
clockTolerance,
|
|
@@ -135,49 +135,12 @@ var SessionIndex = class {
|
|
|
135
135
|
};
|
|
136
136
|
var SessionIndex_default = SessionIndex;
|
|
137
137
|
|
|
138
|
-
//#endregion
|
|
139
|
-
//#region src/utils/SessionUtils.ts
|
|
140
|
-
const UUID_VERSION = 4;
|
|
141
|
-
/**
|
|
142
|
-
* Utility class for session validation and UUID management.
|
|
143
|
-
*/
|
|
144
|
-
var SessionUtils = class {
|
|
145
|
-
constructor() {}
|
|
146
|
-
/**
|
|
147
|
-
* Generates a new UUID v4 string.
|
|
148
|
-
*
|
|
149
|
-
* @returns A new UUID string.
|
|
150
|
-
*/
|
|
151
|
-
static createUUID() {
|
|
152
|
-
return v4();
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Returns `true` if the given string is a valid UUID v4.
|
|
156
|
-
*
|
|
157
|
-
* @param uuid - The UUID string to validate.
|
|
158
|
-
*/
|
|
159
|
-
static validateUUID(uuid) {
|
|
160
|
-
if (validate(uuid) && version(uuid) === UUID_VERSION) return Promise.resolve(true);
|
|
161
|
-
return Promise.resolve(false);
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Returns `true` if the session token is still within its validity window.
|
|
165
|
-
*
|
|
166
|
-
* @param sessionData - The session data to check.
|
|
167
|
-
*/
|
|
168
|
-
static validateSession(sessionData) {
|
|
169
|
-
const currentTime = Date.now();
|
|
170
|
-
const expiryTimeStamp = sessionData.created_at + parseInt(sessionData.expires_in, 10) * 60 * 1e3;
|
|
171
|
-
return Promise.resolve(currentTime < expiryTimeStamp);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
var SessionUtils_default = SessionUtils;
|
|
175
|
-
|
|
176
138
|
//#endregion
|
|
177
139
|
//#region src/ThunderIDNodeClient.ts
|
|
178
140
|
var ThunderIDNodeClient = class extends ThunderIDJavaScriptClient {
|
|
179
141
|
_nodeInstanceId = 0;
|
|
180
142
|
_handledLogoutTokens = /* @__PURE__ */ new Map();
|
|
143
|
+
_refreshesInFlight = /* @__PURE__ */ new Map();
|
|
181
144
|
constructor(instanceId = 0) {
|
|
182
145
|
super(void 0, new NodeCryptoUtils_default());
|
|
183
146
|
this._nodeInstanceId = instanceId;
|
|
@@ -353,13 +316,23 @@ var ThunderIDNodeClient = class extends ThunderIDJavaScriptClient {
|
|
|
353
316
|
rememberHandled(claims) {
|
|
354
317
|
this._handledLogoutTokens.set(claims.jti, claims.exp * 1e3);
|
|
355
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Whether the session holds a valid access token. An expired access token is refreshed when the
|
|
321
|
+
* session holds a refresh token. A session whose access token expired and cannot be refreshed is
|
|
322
|
+
* removed.
|
|
323
|
+
*/
|
|
356
324
|
async isSignedIn(userId) {
|
|
357
325
|
try {
|
|
358
|
-
if (
|
|
326
|
+
if (await super.isSignedIn(userId)) return true;
|
|
359
327
|
const sm = this.getStorageManager();
|
|
360
328
|
const sessionData = await sm.getSessionData(userId);
|
|
361
|
-
if (
|
|
362
|
-
if (
|
|
329
|
+
if (!sessionData?.access_token) return false;
|
|
330
|
+
if (sessionData.refresh_token) try {
|
|
331
|
+
await this.refreshAccessToken(userId);
|
|
332
|
+
return true;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
logger.debug("The expired session could not be refreshed, so it is removed.", error);
|
|
335
|
+
}
|
|
363
336
|
await this.forgetSession(userId, sessionData);
|
|
364
337
|
await sm.removeSessionData(userId);
|
|
365
338
|
return false;
|
|
@@ -371,8 +344,22 @@ var ThunderIDNodeClient = class extends ThunderIDJavaScriptClient {
|
|
|
371
344
|
if (!await this.isSignedIn(userId)) return Promise.reject(new ThunderIDAuthException("NODE-AUTH_CLIENT-GIT-NF01", "The user is not logged in.", "No session was found for the requested user."));
|
|
372
345
|
return super.getIdToken(userId);
|
|
373
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Refreshes the session's tokens. Concurrent calls for one session share a single request, so a
|
|
349
|
+
* rotated refresh token is not sent twice.
|
|
350
|
+
*/
|
|
374
351
|
async refreshAccessToken(userId) {
|
|
375
|
-
|
|
352
|
+
const key = userId ?? "";
|
|
353
|
+
const inFlight = this._refreshesInFlight.get(key);
|
|
354
|
+
if (inFlight) return inFlight;
|
|
355
|
+
const refresh = (async () => {
|
|
356
|
+
await this.loadOpenIDProviderConfiguration(false);
|
|
357
|
+
return super.refreshAccessToken(userId);
|
|
358
|
+
})().finally(() => {
|
|
359
|
+
this._refreshesInFlight.delete(key);
|
|
360
|
+
});
|
|
361
|
+
this._refreshesInFlight.set(key, refresh);
|
|
362
|
+
return refresh;
|
|
376
363
|
}
|
|
377
364
|
async revokeAccessToken(userId) {
|
|
378
365
|
await this.forgetSession(userId);
|
|
@@ -407,6 +394,51 @@ var ThunderIDNodeClient = class extends ThunderIDJavaScriptClient {
|
|
|
407
394
|
url: resolveResourceEndpoint("usersMeCredentials", configData)
|
|
408
395
|
});
|
|
409
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* The URL inputs and bearer token for a management request made on behalf of the session identified by
|
|
399
|
+
* `userId`. The access token stays on the server.
|
|
400
|
+
*/
|
|
401
|
+
async managementRequestConfig(key, userId) {
|
|
402
|
+
const configData = await this.getStorageManager().getConfigData();
|
|
403
|
+
return {
|
|
404
|
+
baseUrl: configData?.baseUrl,
|
|
405
|
+
headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}` },
|
|
406
|
+
url: resolveResourceEndpoint(key, configData)
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Retrieves one page of the user directory on behalf of the session identified by `userId`.
|
|
411
|
+
*
|
|
412
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
413
|
+
*/
|
|
414
|
+
async getUsers(options = {}, userId) {
|
|
415
|
+
return getUsers({
|
|
416
|
+
...options,
|
|
417
|
+
...await this.managementRequestConfig("users", userId)
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Retrieves one page of flows on behalf of the session identified by `userId`.
|
|
422
|
+
*
|
|
423
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
424
|
+
*/
|
|
425
|
+
async getFlows(options = {}, userId) {
|
|
426
|
+
return getFlows({
|
|
427
|
+
...options,
|
|
428
|
+
...await this.managementRequestConfig("flows", userId)
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Retrieves one page of user types on behalf of the session identified by `userId`.
|
|
433
|
+
*
|
|
434
|
+
* Makes a single request and does not follow `links`. The access token stays on the server.
|
|
435
|
+
*/
|
|
436
|
+
async getUserTypes(options = {}, userId) {
|
|
437
|
+
return getUserTypes({
|
|
438
|
+
...options,
|
|
439
|
+
...await this.managementRequestConfig("userTypes", userId)
|
|
440
|
+
});
|
|
441
|
+
}
|
|
410
442
|
};
|
|
411
443
|
var ThunderIDNodeClient_default = ThunderIDNodeClient;
|
|
412
444
|
|
|
@@ -515,6 +547,45 @@ var CookieChunking = class CookieChunking {
|
|
|
515
547
|
};
|
|
516
548
|
var CookieChunking_default = CookieChunking;
|
|
517
549
|
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/utils/SessionUtils.ts
|
|
552
|
+
const UUID_VERSION = 4;
|
|
553
|
+
/**
|
|
554
|
+
* Utility class for session validation and UUID management.
|
|
555
|
+
*/
|
|
556
|
+
var SessionUtils = class {
|
|
557
|
+
constructor() {}
|
|
558
|
+
/**
|
|
559
|
+
* Generates a new UUID v4 string.
|
|
560
|
+
*
|
|
561
|
+
* @returns A new UUID string.
|
|
562
|
+
*/
|
|
563
|
+
static createUUID() {
|
|
564
|
+
return v4();
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Returns `true` if the given string is a valid UUID v4.
|
|
568
|
+
*
|
|
569
|
+
* @param uuid - The UUID string to validate.
|
|
570
|
+
*/
|
|
571
|
+
static validateUUID(uuid) {
|
|
572
|
+
if (validate(uuid) && version(uuid) === UUID_VERSION) return Promise.resolve(true);
|
|
573
|
+
return Promise.resolve(false);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Returns `true` if the session token is still within its validity window.
|
|
577
|
+
*
|
|
578
|
+
* @param sessionData - The session data to check. `expires_in` is in seconds, as the token
|
|
579
|
+
* endpoint returns it, and `created_at` in milliseconds.
|
|
580
|
+
*/
|
|
581
|
+
static validateSession(sessionData) {
|
|
582
|
+
const currentTime = Date.now();
|
|
583
|
+
const expiryTimeStamp = sessionData.created_at + parseInt(sessionData.expires_in, 10) * 1e3;
|
|
584
|
+
return Promise.resolve(currentTime < expiryTimeStamp);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
var SessionUtils_default = SessionUtils;
|
|
588
|
+
|
|
518
589
|
//#endregion
|
|
519
590
|
//#region src/utils/LoggedOutSessions.ts
|
|
520
591
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeCryptoUtils.d.ts","sourceRoot":"","sources":["../../src/utils/NodeCryptoUtils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,MAAM,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"NodeCryptoUtils.d.ts","sourceRoot":"","sources":["../../src/utils/NodeCryptoUtils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,MAAM,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAS3D;;;GAGG;AACH,cAAM,eAAgB,YAAW,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IAC/C,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM;IAI/C,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAItC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM;IAIzC,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM;IAI9C,SAAS,CACpB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,EAC1B,UAAU,EAAE,MAAM,EAAE,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC;CAUpB;AAED,eAAe,eAAe,CAAC"}
|
|
@@ -19,7 +19,8 @@ declare class SessionUtils {
|
|
|
19
19
|
/**
|
|
20
20
|
* Returns `true` if the session token is still within its validity window.
|
|
21
21
|
*
|
|
22
|
-
* @param sessionData - The session data to check.
|
|
22
|
+
* @param sessionData - The session data to check. `expires_in` is in seconds, as the token
|
|
23
|
+
* endpoint returns it, and `created_at` in milliseconds.
|
|
23
24
|
*/
|
|
24
25
|
static validateSession(sessionData: SessionData): Promise<boolean>;
|
|
25
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionUtils.d.ts","sourceRoot":"","sources":["../../src/utils/SessionUtils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAKlD;;GAEG;AACH,cAAM,YAAY;IAChB,OAAO;IAEP;;;;OAIG;WACW,UAAU,IAAI,MAAM;IAIlC;;;;OAIG;WACW,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO1D
|
|
1
|
+
{"version":3,"file":"SessionUtils.d.ts","sourceRoot":"","sources":["../../src/utils/SessionUtils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAKlD;;GAEG;AACH,cAAM,YAAY;IAChB,OAAO;IAEP;;;;OAIG;WACW,UAAU,IAAI,MAAM;IAIlC;;;;OAIG;WACW,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO1D;;;;;OAKG;WACW,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAK1E;AAED,eAAe,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thunderid/node",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Node.js runtime SDK for ThunderID",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"thunderid",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"vitest": "4.1.11"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@thunderid/javascript": "^1.
|
|
47
|
+
"@thunderid/javascript": "^1.4.1",
|
|
48
48
|
"base64url": "3.0.1",
|
|
49
49
|
"cross-fetch": "4.1.0",
|
|
50
50
|
"fast-sha256": "1.3.0",
|