@thunderid/node 1.3.0 → 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.
@@ -3,6 +3,7 @@ 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>;
@@ -1 +1 @@
1
- {"version":3,"file":"ThunderIDNodeClient.d.ts","sourceRoot":"","sources":["../src/ThunderIDNodeClient.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,uBAAuB,EACvB,iCAAiC,EAIjC,OAAO,EAIP,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,aAAa,EAGb,OAAO,EAEP,yBAAyB,EACzB,0BAA0B,EAC1B,aAAa,EAEb,IAAI,EACL,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAC,mBAAmB,EAAC,MAAM,iBAAiB,CAAC;AAMpD,cAAM,mBAAmB,CAAC,CAAC,SAAS,mBAAmB,GAAG,mBAAmB,CAAE,SAAQ,yBAAyB,CAAC,CAAC,CAAC;IACjH,OAAO,CAAC,eAAe,CAAK;IAG5B,OAAO,CAAC,oBAAoB,CAAkD;gBAE3D,UAAU,SAAI;IAKX,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAMhE,aAAa,IAAI,MAAM;IAIjB,MAAM,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAsE9C,YAAY,CAChC,aAAa,CAAC,EAAE,iCAAiC,EACjD,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;IAcI,OAAO,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAe9D;;;;;;;;;;;;;OAaG;IACU,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAsD3F;;;;;OAKG;YACW,aAAa;IAYX,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;cAI7B,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7E;;;OAGG;YACW,YAAY;YAYZ,kBAAkB;IAgBhC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,eAAe;IAID,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB7C,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa5C,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAIlE,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC;IAO/D,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItE,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhD,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAI7D,aAAa,CACjC,MAAM,EAAE,0BAA0B,EAClC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,GAAG,QAAQ,GAAG,IAAI,CAAC;IAI3C;;;OAGG;IACU,qBAAqB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcnG;;;OAGG;YACW,uBAAuB;IAerC;;;;OAIG;IACU,QAAQ,CACnB,OAAO,GAAE;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAM,EACtF,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,uBAAuB,CAAC;IAInC;;;;OAIG;IACU,QAAQ,CACnB,OAAO,GAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAM,EACxF,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,uBAAuB,CAAC;IAInC;;;;OAIG;IACU,YAAY,CACvB,OAAO,GAAE;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAM,EACrE,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,2BAA2B,CAAC;CAGxC;AAED,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"ThunderIDNodeClient.d.ts","sourceRoot":"","sources":["../src/ThunderIDNodeClient.ts"],"names":[],"mappings":"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"}
@@ -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 jose = require("jose");
35
- jose = __toESM(jose);
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
- const key = await jose.importJWK(jwk);
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 (!await super.isSignedIn(userId)) return false;
356
+ if (await super.isSignedIn(userId)) return true;
388
357
  const sm = this.getStorageManager();
389
358
  const sessionData = await sm.getSessionData(userId);
390
- if (await SessionUtils_default.validateSession(sessionData)) return true;
391
- if (await this.refreshAccessToken(userId)) return true;
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
- return super.refreshAccessToken(userId);
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);
@@ -589,6 +577,45 @@ var CookieChunking = class CookieChunking {
589
577
  };
590
578
  var CookieChunking_default = CookieChunking;
591
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
+
592
619
  //#endregion
593
620
  //#region src/utils/LoggedOutSessions.ts
594
621
  /**
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ import { BackchannelLogoutConstants, InvalidLogoutTokenError, ThunderIDAuthExcep
3
3
  import cache from "memory-cache";
4
4
  import base64url from "base64url";
5
5
  import sha256 from "fast-sha256";
6
- import * as jose from "jose";
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
- const key = await jose.importJWK(jwk);
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 (!await super.isSignedIn(userId)) return false;
326
+ if (await super.isSignedIn(userId)) return true;
359
327
  const sm = this.getStorageManager();
360
328
  const sessionData = await sm.getSessionData(userId);
361
- if (await SessionUtils_default.validateSession(sessionData)) return true;
362
- if (await this.refreshAccessToken(userId)) return true;
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
- return super.refreshAccessToken(userId);
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);
@@ -560,6 +547,45 @@ var CookieChunking = class CookieChunking {
560
547
  };
561
548
  var CookieChunking_default = CookieChunking;
562
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
+
563
589
  //#endregion
564
590
  //#region src/utils/LoggedOutSessions.ts
565
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;AAM3D;;;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;CAYpB;AAED,eAAe,eAAe,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;;;;OAIG;WACW,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAK1E;AAED,eAAe,YAAY,CAAC"}
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.0",
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.4.0",
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",