@rodit/rodit-auth-be 9.11.16 → 9.11.20

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.
@@ -973,6 +973,31 @@ function resolveCredentialExpirationUnix(now, sessionExpiration, own_rodit) {
973
973
  keyCreationDuration,
974
974
  });
975
975
 
976
+ const signatureStart = Date.now();
977
+ const timeString = await unixTimeToDateString(now);
978
+ const roditidandtimestamp = new TextEncoder().encode(
979
+ token.rodit_id + timeString
980
+ );
981
+ let privateKeyToUse = config_own_rodit.own_rodit_bytes_private_key;
982
+ if (Buffer.isBuffer(privateKeyToUse)) {
983
+ privateKeyToUse = new Uint8Array(privateKeyToUse);
984
+ } else if (!(privateKeyToUse instanceof Uint8Array)) {
985
+ privateKeyToUse = new Uint8Array(Array.from(privateKeyToUse));
986
+ }
987
+ const own_rodit_bytes_signature = nacl.sign.detached(
988
+ roditidandtimestamp,
989
+ privateKeyToUse
990
+ );
991
+ const renewed_rodit_idsignature = Buffer.from(
992
+ own_rodit_bytes_signature
993
+ ).toString("base64url");
994
+ logger.debug("Regenerated rodit_idsignature for renewed credential", {
995
+ requestId,
996
+ signatureDuration: Date.now() - signatureStart,
997
+ roditId: token.rodit_id,
998
+ iat: now,
999
+ });
1000
+
976
1001
  // Keep existing session ID and creation time
977
1002
  const session_id = existingSessionId;
978
1003
  const session_iat = token.session_iat;
@@ -1028,7 +1053,7 @@ function resolveCredentialExpirationUnix(now, sessionExpiration, own_rodit) {
1028
1053
  rodit_id: token.rodit_id,
1029
1054
  rodit_owner: token.rodit_owner,
1030
1055
  rodit_allowediso3166list: token.rodit_allowediso3166list,
1031
- rodit_idsignature: token.rodit_idsignature,
1056
+ rodit_idsignature: renewed_rodit_idsignature,
1032
1057
  rodit_maxrequests: token.rodit_maxrequests,
1033
1058
  rodit_maxrqwindow: token.rodit_maxrqwindow,
1034
1059
  rodit_permissionedroutes: token.rodit_permissionedroutes,
@@ -1665,14 +1690,35 @@ function resolveCredentialExpirationUnix(now, sessionExpiration, own_rodit) {
1665
1690
  newToken = renewalResult.newToken;
1666
1691
 
1667
1692
  if (isExpired && !newToken) {
1668
- logger.error("Token expired and renewal failed", {
1669
- component: "JwtAuth",
1670
- method: "validate_jwt_token_be",
1671
- requestId,
1672
- jti: payload.jti
1673
- });
1674
-
1675
- throw new Error("Error 007: Token has expired and renewal failed");
1693
+ const renewalNow = Math.floor(Date.now() / 1000);
1694
+ const sessionExpUnix =
1695
+ payload.session_exp != null ? Number(payload.session_exp) : null;
1696
+ const sessionStillActive =
1697
+ Number.isFinite(sessionExpUnix) && sessionExpUnix > renewalNow;
1698
+
1699
+ if (sessionStillActive) {
1700
+ logger.warn(
1701
+ "Credential expired and renewal failed but session still active; allowing request",
1702
+ {
1703
+ component: "JwtAuth",
1704
+ method: "validate_jwt_token_be",
1705
+ requestId,
1706
+ jti: payload.jti,
1707
+ sessionExp: sessionExpUnix,
1708
+ now: renewalNow,
1709
+ }
1710
+ );
1711
+ } else {
1712
+ logger.error("Token expired and renewal failed", {
1713
+ component: "JwtAuth",
1714
+ method: "validate_jwt_token_be",
1715
+ requestId,
1716
+ jti: payload.jti,
1717
+ sessionExp: sessionExpUnix,
1718
+ });
1719
+
1720
+ throw new Error("Error 007: Token has expired and renewal failed");
1721
+ }
1676
1722
  }
1677
1723
  } else if (isExpired) {
1678
1724
  logger.info("Allowing signature-valid expired token for special flow", {
@@ -2213,6 +2259,70 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
2213
2259
  }
2214
2260
  }
2215
2261
 
2262
+ /**
2263
+ * Decide brief vs thorough renewal verification using existing SECURITY_OPTIONS constants.
2264
+ *
2265
+ * @param {Object} params
2266
+ * @returns {Object} Plan with shouldDoFullVerification, urgency, pThorough, newduration, floors
2267
+ */
2268
+ function resolveRenewalVerificationPlan({
2269
+ forceRenewal = false,
2270
+ durationLeftpct = 0,
2271
+ currentDuration = 0,
2272
+ lapsedProportion = 0.8,
2273
+ thresholdValidationType = 0.1,
2274
+ durationRamp = 0.85,
2275
+ fallbackJwtDuration = 3600,
2276
+ roditMaxRqWindow,
2277
+ randomNumber = Math.random(),
2278
+ }) {
2279
+ const eligibilityTail = 1.0 - lapsedProportion;
2280
+ const safeCurrentDuration = Math.max(0, Number(currentDuration) || 0);
2281
+ const newduration = safeCurrentDuration * durationRamp;
2282
+
2283
+ let urgency;
2284
+ if (forceRenewal) {
2285
+ urgency = 1;
2286
+ } else if (eligibilityTail <= 0) {
2287
+ urgency = 1;
2288
+ } else {
2289
+ const tailFraction = durationLeftpct / 100 / eligibilityTail;
2290
+ urgency = Math.min(1, Math.max(0, 1 - tailFraction));
2291
+ }
2292
+
2293
+ const pThorough =
2294
+ thresholdValidationType + urgency * (1 - thresholdValidationType);
2295
+
2296
+ const baselineDuration = Math.max(1, Number(fallbackJwtDuration) || 3600);
2297
+ const rampedFloor = baselineDuration * eligibilityTail * durationRamp;
2298
+ const maxRqWindow = Number(roditMaxRqWindow) || baselineDuration;
2299
+ const rqWindowFloor = maxRqWindow * eligibilityTail;
2300
+
2301
+ const stochasticThorough = urgency >= 1 || randomNumber < pThorough;
2302
+ const rampedFloorThorough = newduration <= rampedFloor;
2303
+ const rqWindowFloorThorough = newduration <= rqWindowFloor;
2304
+ const deterministicThorough = rampedFloorThorough || rqWindowFloorThorough;
2305
+
2306
+ let verificationReason = "brief";
2307
+ if (deterministicThorough) {
2308
+ verificationReason = rampedFloorThorough ? "ramped_floor" : "rq_window_floor";
2309
+ } else if (stochasticThorough) {
2310
+ verificationReason = "stochastic";
2311
+ }
2312
+
2313
+ return {
2314
+ shouldDoFullVerification: stochasticThorough || deterministicThorough,
2315
+ urgency,
2316
+ pThorough,
2317
+ newduration,
2318
+ rampedFloor,
2319
+ rqWindowFloor,
2320
+ stochasticThorough,
2321
+ deterministicThorough,
2322
+ verificationReason,
2323
+ };
2324
+ }
2325
+
2216
2326
  /**
2217
2327
  * Check if a token needs renewal and renew if necessary
2218
2328
  *
@@ -2236,12 +2346,16 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
2236
2346
  const DURATIONRAMP = parseFloat(
2237
2347
  config.get('SECURITY_OPTIONS.DURATIONRAMP', '0.85')
2238
2348
  );
2349
+ const FALLBACK_JWT_DURATION = parseInt(
2350
+ config.get('SECURITY_OPTIONS.FALLBACK_JWT_DURATION', '3600'),
2351
+ 10
2352
+ );
2239
2353
 
2240
2354
  const currentTime = Math.floor(Date.now() / 1000);
2241
2355
  const timeLeft = payload.exp - currentTime;
2242
2356
  const currentDuration = payload.exp - payload.iat;
2243
- const durationLeftpct = (timeLeft / currentDuration) * 100;
2244
- const newduration = currentDuration * DURATIONRAMP;
2357
+ const durationLeftpct =
2358
+ currentDuration > 0 ? (timeLeft / currentDuration) * 100 : 0;
2245
2359
 
2246
2360
  // Log session information
2247
2361
  const sessionInfo = {
@@ -2301,13 +2415,40 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
2301
2415
  ...sessionInfo,
2302
2416
  });
2303
2417
 
2304
- // Determine verification method
2305
- const randomNumber = Math.random();
2306
- const shouldDoFullVerification =
2307
- randomNumber < THRESHOLD_VALIDATION_TYPE ||
2308
- newduration >
2309
- payload.rodit_maxrqwindow *
2310
- (100 - (LAPSED_LIFETIME_PROPORTION_4RENEWAL_ELIGIBILITY * 100));
2418
+ const renewalPlan = resolveRenewalVerificationPlan({
2419
+ forceRenewal,
2420
+ durationLeftpct,
2421
+ currentDuration,
2422
+ lapsedProportion: LAPSED_LIFETIME_PROPORTION_4RENEWAL_ELIGIBILITY,
2423
+ thresholdValidationType: THRESHOLD_VALIDATION_TYPE,
2424
+ durationRamp: DURATIONRAMP,
2425
+ fallbackJwtDuration: FALLBACK_JWT_DURATION,
2426
+ roditMaxRqWindow: payload.rodit_maxrqwindow,
2427
+ });
2428
+ const {
2429
+ shouldDoFullVerification,
2430
+ urgency,
2431
+ pThorough,
2432
+ newduration,
2433
+ rampedFloor,
2434
+ rqWindowFloor,
2435
+ verificationReason,
2436
+ } = renewalPlan;
2437
+
2438
+ logger.debug("Renewal verification plan", {
2439
+ component: "TokenRenewalService",
2440
+ method: "checkandrenew_jwt_token",
2441
+ requestId,
2442
+ forceRenewal,
2443
+ durationLeftpct: durationLeftpct.toFixed(1),
2444
+ urgency: urgency.toFixed(3),
2445
+ pThorough: pThorough.toFixed(3),
2446
+ newduration: Math.floor(newduration),
2447
+ rampedFloor: Math.floor(rampedFloor),
2448
+ rqWindowFloor: Math.floor(rqWindowFloor),
2449
+ verificationReason,
2450
+ shouldDoFullVerification,
2451
+ });
2311
2452
 
2312
2453
  const verificationStartTime = Date.now();
2313
2454
 
@@ -2392,7 +2533,7 @@ async function thorough_validate_jwt_token_be(token, requestId = ulid()) {
2392
2533
  logInfo: {
2393
2534
  newDuration: newduration,
2394
2535
  reason: shouldDoFullVerification
2395
- ? "Thorough verification"
2536
+ ? `Thorough verification (${verificationReason})`
2396
2537
  : "Brief verification",
2397
2538
  notAfter: notAfter,
2398
2539
  renewalDuration,
@@ -2439,6 +2580,7 @@ module.exports = {
2439
2580
  generate_jwt_token,
2440
2581
  base64url2jwk_public_key,
2441
2582
  checkandrenew_jwt_token,
2583
+ resolveRenewalVerificationPlan,
2442
2584
  thorough_validate_jwt_token_be,
2443
2585
  brief_validate_jwt_token_be,
2444
2586
  generate_jwt_token_fromtoken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rodit/rodit-auth-be",
3
- "version": "9.11.16",
3
+ "version": "9.11.20",
4
4
  "description": "RODiT-based authentication system for Express.js applications",
5
5
  "main": "index.js",
6
6
  "exports": {