dms-middleware-auth 1.1.5 → 1.1.7

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.
@@ -24,9 +24,12 @@ export declare class AuthMiddleware implements NestMiddleware, OnModuleInit {
24
24
  private static readonly licenceExpiredMessage;
25
25
  private static readonly CLOCK_TOLERANCE_MS;
26
26
  private static readonly LIC_EXPIRY_CHECK_INTERVAL_MS;
27
+ private static readonly MAX_SAFE_TTL_MS;
28
+ private static readonly MAX_SAFE_TTL_SECONDS;
27
29
  private static readonly LIC_CACHE_TTL_SECONDS;
28
30
  private static readonly CLIENT_TOKEN_TTL_MAX_SECONDS;
29
31
  private static readonly CLIENT_TOKEN_TTL_SAFETY_SKEW_SECONDS;
32
+ private static readonly ROLE_ATTRIBUTES_TTL_SECONDS;
30
33
  constructor(cacheManager: Cache, options: AuthMiddlewareOptions);
31
34
  onModuleInit(): Promise<void>;
32
35
  use(req: any, res: Response, next: NextFunction): Promise<void | Response<any, Record<string, any>>>;
@@ -92,7 +92,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
92
92
  server_time: new Date().toISOString(),
93
93
  licence_expiry: AuthMiddleware_1.licenceValidatedUntilMs
94
94
  ? new Date(AuthMiddleware_1.licenceValidatedUntilMs).toISOString()
95
- : 'Unknown'
95
+ : 'Unknown',
96
96
  });
97
97
  }
98
98
  // Allow bypass URL
@@ -118,8 +118,8 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
118
118
  if (!clientAttributes) {
119
119
  const roleObj = await this.getClientRoleAttributes(role, clientToken);
120
120
  clientAttributes = JSON.stringify(roleObj.attributes ?? {});
121
- // store indefinitely (or you can choose a short TTL)
122
- await this.cacheSetSeconds(clientRoleCacheKey, clientAttributes, 0);
121
+ // Cache with safe TTL
122
+ await this.cacheSetSeconds(clientRoleCacheKey, clientAttributes, AuthMiddleware_1.ROLE_ATTRIBUTES_TTL_SECONDS);
123
123
  }
124
124
  // Route access check
125
125
  const attrs = JSON.parse(clientAttributes);
@@ -137,11 +137,12 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
137
137
  }
138
138
  req['user'] = {
139
139
  role,
140
- userName: decoded?.preferred_username
140
+ userName: decoded?.preferred_username,
141
141
  };
142
142
  return next();
143
143
  }
144
144
  catch (error) {
145
+ this.logger.error(`Middleware error: ${error?.message || error}`, error?.stack);
145
146
  return res.status(500).json({ message: error?.message || 'Internal error' });
146
147
  }
147
148
  }
@@ -173,6 +174,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
173
174
  if (AuthMiddleware_1.shutdownInitiated)
174
175
  return;
175
176
  AuthMiddleware_1.shutdownInitiated = true;
177
+ this.logger.warn('Initiating graceful shutdown due to licence expiry');
176
178
  // immediate SIGTERM, then force exit after 5s
177
179
  setTimeout(() => {
178
180
  try {
@@ -182,7 +184,10 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
182
184
  process.exit(1);
183
185
  }
184
186
  if (!AuthMiddleware_1.shutdownTimer) {
185
- AuthMiddleware_1.shutdownTimer = setTimeout(() => process.exit(1), 5000);
187
+ AuthMiddleware_1.shutdownTimer = setTimeout(() => {
188
+ this.logger.error('Force exit after SIGTERM timeout');
189
+ process.exit(1);
190
+ }, 5000);
186
191
  }
187
192
  }, 0);
188
193
  }
@@ -207,12 +212,18 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
207
212
  const cachedToken = (await this.cacheManager.get('client_Licence_token'));
208
213
  if (cachedToken) {
209
214
  const ok = await this.validateLicence(cachedToken, publicKey, false);
210
- if (ok)
215
+ if (ok) {
216
+ this.logger.log('Licence validated from cache');
211
217
  return true;
218
+ }
212
219
  }
213
220
  // 2) Fetch from licensing service
221
+ this.logger.log('Fetching licence from service');
214
222
  const token = await this.getLicencingTokenFromService(realm);
215
223
  const ok = await this.validateLicence(token, publicKey, true);
224
+ if (ok) {
225
+ this.logger.log('Licence validated from service');
226
+ }
216
227
  return ok;
217
228
  })();
218
229
  try {
@@ -223,7 +234,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
223
234
  }
224
235
  }
225
236
  catch (e) {
226
- this.logger.warn(`checkLicenceAndValidate failed: ${e?.message || e}`);
237
+ this.logger.error(`checkLicenceAndValidate failed: ${e?.message || e}`, e?.stack);
227
238
  return false;
228
239
  }
229
240
  }
@@ -245,6 +256,8 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
245
256
  }
246
257
  // ✅ Save only the timestamp (supports 50 days / 500 days / years)
247
258
  AuthMiddleware_1.licenceValidatedUntilMs = licEndMs;
259
+ const daysUntilExpiry = Math.floor((licEndMs - now) / (1000 * 60 * 60 * 24));
260
+ this.logger.log(`Licence valid until ${new Date(licEndMs).toISOString()} (${daysUntilExpiry} days remaining)`);
248
261
  // ✅ Cache only briefly (avoid long TTL -> avoids TimeoutOverflowWarning)
249
262
  if (updateCache) {
250
263
  await this.cacheSetSeconds('client_Licence_token', tokenJwt, AuthMiddleware_1.LIC_CACHE_TTL_SECONDS);
@@ -268,15 +281,21 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
268
281
  const url = this.options.licenceServiceUrl ??
269
282
  'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/';
270
283
  const body = { client_name: realm };
271
- const resp = await axios_1.default.post(url, body, { timeout: 15_000 });
272
- if (resp.status !== axios_1.HttpStatusCode.Ok) {
273
- throw new Error(`Licencing service returned status ${resp.status}`);
284
+ try {
285
+ const resp = await axios_1.default.post(url, body, { timeout: 15_000 });
286
+ if (resp.status !== axios_1.HttpStatusCode.Ok) {
287
+ throw new Error(`Licencing service returned status ${resp.status}`);
288
+ }
289
+ const token = resp?.data?.token;
290
+ if (!token || typeof token !== 'string') {
291
+ throw new Error('Licencing service response missing token');
292
+ }
293
+ return token;
274
294
  }
275
- const token = resp?.data?.token;
276
- if (!token || typeof token !== 'string') {
277
- throw new Error('Licencing service response missing token');
295
+ catch (e) {
296
+ this.logger.error(`Failed to fetch licence from service: ${e?.message || e}`);
297
+ throw e;
278
298
  }
279
- return token;
280
299
  }
281
300
  // -------------------------
282
301
  // Keycloak client token caching (safe TTL)
@@ -302,7 +321,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
302
321
  const resp = await axios_1.default.post(`${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`, new URLSearchParams({
303
322
  grant_type: 'client_credentials',
304
323
  client_id: clientId,
305
- client_secret: clientSecret
324
+ client_secret: clientSecret,
306
325
  }).toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 15_000 });
307
326
  const token = resp?.data?.access_token;
308
327
  if (!token) {
@@ -324,7 +343,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
324
343
  return jwt.verify(token, publicKeys, {
325
344
  algorithms: ['RS256'],
326
345
  ignoreExpiration: true,
327
- clockTolerance: 60
346
+ clockTolerance: 60,
328
347
  });
329
348
  }
330
349
  decodeAccessToken(authHeader, publicKey) {
@@ -332,7 +351,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
332
351
  const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
333
352
  return jwt.verify(token, publicKeys, {
334
353
  algorithms: ['RS256'],
335
- clockTolerance: 60
354
+ clockTolerance: 60,
336
355
  });
337
356
  }
338
357
  extractBearerToken(authHeader) {
@@ -352,19 +371,101 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
352
371
  return n;
353
372
  }
354
373
  // -------------------------
355
- // Cache helper (seconds-based, safe across stores)
356
- // - Using seconds avoids ms/sec mismatch across cache-manager stores
357
- // - TTL is kept small so no overflow even if a store uses Node timers
374
+ // ✅ ULTIMATE FIX: Cache helper with COMPLETE OVERFLOW PROTECTION
375
+ // -------------------------
376
+ // Works with ANY cache-manager version and ANY cache store
377
+ // Prevents cache stores from calculating TTL from JWT claims
358
378
  // -------------------------
359
379
  async cacheSetSeconds(key, value, ttlSeconds) {
360
- // ttlSeconds=0 => store forever (supported by many stores); if not, we just set without ttl
361
- if (!ttlSeconds || ttlSeconds <= 0) {
362
- await this.cacheManager.set(key, value);
363
- return;
380
+ try {
381
+ // ttlSeconds=0 => store forever (no expiry)
382
+ if (!ttlSeconds || ttlSeconds <= 0) {
383
+ await this.cacheManager.set(key, value);
384
+ this.logger.debug(`Cache set (no TTL): ${key}`);
385
+ return;
386
+ }
387
+ // ✅ CRITICAL: Clamp TTL to prevent overflow
388
+ let safeTtlSeconds = ttlSeconds;
389
+ if (ttlSeconds > AuthMiddleware_1.MAX_SAFE_TTL_SECONDS) {
390
+ safeTtlSeconds = AuthMiddleware_1.MAX_SAFE_TTL_SECONDS;
391
+ this.logger.warn(`Cache TTL clamped: key=${key}, requested=${ttlSeconds}s (~${Math.floor(ttlSeconds / 86400)} days), using=${safeTtlSeconds}s (~24 days)`);
392
+ }
393
+ // Convert to milliseconds
394
+ const ttlMs = safeTtlSeconds * 1000;
395
+ // ✅ CRITICAL: Double-check we're not exceeding the limit
396
+ // This catches any edge cases where ttlMs might still overflow
397
+ const finalTtlMs = Math.min(ttlMs, AuthMiddleware_1.MAX_SAFE_TTL_MS);
398
+ if (finalTtlMs !== ttlMs) {
399
+ this.logger.warn(`TTL further clamped: ${ttlMs}ms -> ${finalTtlMs}ms`);
400
+ }
401
+ // ✅ Try multiple methods to ensure compatibility with all cache stores
402
+ let cacheSetSuccess = false;
403
+ let lastError = null;
404
+ // Method 1: Modern cache-manager v5+ (TTL in milliseconds as 3rd param)
405
+ if (!cacheSetSuccess) {
406
+ try {
407
+ await this.cacheManager.set(key, value, finalTtlMs);
408
+ cacheSetSuccess = true;
409
+ this.logger.debug(`Cache set (v5 format): ${key} (TTL: ${safeTtlSeconds}s / ${finalTtlMs}ms)`);
410
+ }
411
+ catch (err) {
412
+ lastError = err;
413
+ // Try next method
414
+ }
415
+ }
416
+ // Method 2: Object-based TTL (milliseconds) - some stores
417
+ if (!cacheSetSuccess) {
418
+ try {
419
+ await this.cacheManager.set(key, value, { ttl: finalTtlMs });
420
+ cacheSetSuccess = true;
421
+ this.logger.debug(`Cache set (object ms format): ${key} (TTL: ${finalTtlMs}ms)`);
422
+ }
423
+ catch (err) {
424
+ lastError = err;
425
+ // Try next method
426
+ }
427
+ }
428
+ // Method 3: Object-based TTL (seconds) - Redis and others
429
+ if (!cacheSetSuccess) {
430
+ try {
431
+ await this.cacheManager.set(key, value, { ttl: safeTtlSeconds });
432
+ cacheSetSuccess = true;
433
+ this.logger.debug(`Cache set (object sec format): ${key} (TTL: ${safeTtlSeconds}s)`);
434
+ }
435
+ catch (err) {
436
+ lastError = err;
437
+ // Try next method
438
+ }
439
+ }
440
+ // Method 4: Direct store access (for Redis stores)
441
+ if (!cacheSetSuccess) {
442
+ try {
443
+ const store = this.cacheManager.store;
444
+ const redisClient = store?.client || store?.getClient?.();
445
+ if (redisClient && typeof redisClient.set === 'function') {
446
+ // Redis native command: SET key value EX seconds
447
+ await redisClient.set(key, value, { EX: safeTtlSeconds });
448
+ cacheSetSuccess = true;
449
+ this.logger.debug(`Cache set (Redis direct): ${key} (EX: ${safeTtlSeconds}s)`);
450
+ }
451
+ }
452
+ catch (err) {
453
+ lastError = err;
454
+ // Continue to fallback
455
+ }
456
+ }
457
+ // Method 5: Fallback - cache without TTL (better than failing)
458
+ if (!cacheSetSuccess) {
459
+ this.logger.warn(`All TTL methods failed for key=${key}, caching without TTL. Last error: ${lastError?.message}`);
460
+ await this.cacheManager.set(key, value);
461
+ this.logger.debug(`Cache set (no TTL fallback): ${key}`);
462
+ }
463
+ }
464
+ catch (e) {
465
+ // Even if all methods fail, don't crash the app
466
+ this.logger.error(`Cache set completely failed for key=${key}: ${e?.message || e}`, e?.stack);
467
+ // Cache failures are non-fatal - log and continue
364
468
  }
365
- // cache-manager / Nest stores vary: some accept third param number, others accept { ttl }
366
- // We use { ttl } for better compatibility, and TTL is small anyway.
367
- await this.cacheManager.set(key, value, { ttl: ttlSeconds });
368
469
  }
369
470
  };
370
471
  exports.AuthMiddleware = AuthMiddleware;
@@ -382,11 +483,15 @@ AuthMiddleware.CLOCK_TOLERANCE_MS = 60_000; // 1 min
382
483
  // IMPORTANT:
383
484
  // Node timers overflow above ~2,147,483,647ms (~24.8 days).
384
485
  // We avoid scheduling long timeouts completely.
385
- AuthMiddleware.LIC_EXPIRY_CHECK_INTERVAL_MS = 60_000; // 1 min
386
- // Cache TTLs (short by design; safe for any store, avoids timer overflow)
486
+ AuthMiddleware.LIC_EXPIRY_CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
487
+ // OVERFLOW PROTECTION: Max safe TTL for 32-bit timers
488
+ AuthMiddleware.MAX_SAFE_TTL_MS = 2_147_483_647; // ~24.8 days
489
+ AuthMiddleware.MAX_SAFE_TTL_SECONDS = Math.floor(2_147_483_647 / 1000); // ~24 days
490
+ // Cache TTLs (safe by design; prevents timer overflow)
387
491
  AuthMiddleware.LIC_CACHE_TTL_SECONDS = 15 * 60; // 15 minutes
388
492
  AuthMiddleware.CLIENT_TOKEN_TTL_MAX_SECONDS = 60 * 60; // 1 hour cap
389
493
  AuthMiddleware.CLIENT_TOKEN_TTL_SAFETY_SKEW_SECONDS = 30; // refresh a bit early
494
+ AuthMiddleware.ROLE_ATTRIBUTES_TTL_SECONDS = 24 * 60 * 60; // 24 hours
390
495
  exports.AuthMiddleware = AuthMiddleware = AuthMiddleware_1 = __decorate([
391
496
  (0, common_1.Injectable)(),
392
497
  __param(0, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dms-middleware-auth",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Reusable middleware for authentication and authorization in NestJS applications.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -3,7 +3,7 @@ import {
3
3
  Injectable,
4
4
  Logger,
5
5
  NestMiddleware,
6
- OnModuleInit
6
+ OnModuleInit,
7
7
  } from '@nestjs/common';
8
8
  import { Response, NextFunction } from 'express';
9
9
  import axios, { HttpStatusCode } from 'axios';
@@ -52,18 +52,23 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
52
52
  // IMPORTANT:
53
53
  // Node timers overflow above ~2,147,483,647ms (~24.8 days).
54
54
  // We avoid scheduling long timeouts completely.
55
- private static readonly LIC_EXPIRY_CHECK_INTERVAL_MS = 60_000; // 1 min
55
+ private static readonly LIC_EXPIRY_CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
56
56
 
57
- // Cache TTLs (short by design; safe for any store, avoids timer overflow)
57
+ // OVERFLOW PROTECTION: Max safe TTL for 32-bit timers
58
+ private static readonly MAX_SAFE_TTL_MS = 2_147_483_647; // ~24.8 days
59
+ private static readonly MAX_SAFE_TTL_SECONDS = Math.floor(2_147_483_647 / 1000); // ~24 days
60
+
61
+ // Cache TTLs (safe by design; prevents timer overflow)
58
62
  private static readonly LIC_CACHE_TTL_SECONDS = 15 * 60; // 15 minutes
59
63
  private static readonly CLIENT_TOKEN_TTL_MAX_SECONDS = 60 * 60; // 1 hour cap
60
64
  private static readonly CLIENT_TOKEN_TTL_SAFETY_SKEW_SECONDS = 30; // refresh a bit early
65
+ private static readonly ROLE_ATTRIBUTES_TTL_SECONDS = 24 * 60 * 60; // 24 hours
61
66
 
62
67
  constructor(
63
68
  @Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
64
69
  @Inject('AUTH_MIDDLEWARE_OPTIONS')
65
70
  private readonly options: AuthMiddlewareOptions
66
- ) {}
71
+ ) { }
67
72
 
68
73
  // -------------------------
69
74
  // Startup: validate licence once, then run a small periodic expiry check
@@ -102,7 +107,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
102
107
  server_time: new Date().toISOString(),
103
108
  licence_expiry: AuthMiddleware.licenceValidatedUntilMs
104
109
  ? new Date(AuthMiddleware.licenceValidatedUntilMs).toISOString()
105
- : 'Unknown'
110
+ : 'Unknown',
106
111
  });
107
112
  }
108
113
 
@@ -135,8 +140,13 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
135
140
  if (!clientAttributes) {
136
141
  const roleObj = await this.getClientRoleAttributes(role, clientToken);
137
142
  clientAttributes = JSON.stringify(roleObj.attributes ?? {});
138
- // store indefinitely (or you can choose a short TTL)
139
- await this.cacheSetSeconds(clientRoleCacheKey, clientAttributes, 0);
143
+
144
+ // Cache with safe TTL
145
+ await this.cacheSetSeconds(
146
+ clientRoleCacheKey,
147
+ clientAttributes,
148
+ AuthMiddleware.ROLE_ATTRIBUTES_TTL_SECONDS
149
+ );
140
150
  }
141
151
 
142
152
  // Route access check
@@ -157,11 +167,12 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
157
167
 
158
168
  req['user'] = {
159
169
  role,
160
- userName: decoded?.preferred_username
170
+ userName: decoded?.preferred_username,
161
171
  };
162
172
 
163
173
  return next();
164
174
  } catch (error: any) {
175
+ this.logger.error(`Middleware error: ${error?.message || error}`, error?.stack);
165
176
  return res.status(500).json({ message: error?.message || 'Internal error' });
166
177
  }
167
178
  }
@@ -197,6 +208,8 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
197
208
  if (AuthMiddleware.shutdownInitiated) return;
198
209
  AuthMiddleware.shutdownInitiated = true;
199
210
 
211
+ this.logger.warn('Initiating graceful shutdown due to licence expiry');
212
+
200
213
  // immediate SIGTERM, then force exit after 5s
201
214
  setTimeout(() => {
202
215
  try {
@@ -206,7 +219,10 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
206
219
  }
207
220
 
208
221
  if (!AuthMiddleware.shutdownTimer) {
209
- AuthMiddleware.shutdownTimer = setTimeout(() => process.exit(1), 5000);
222
+ AuthMiddleware.shutdownTimer = setTimeout(() => {
223
+ this.logger.error('Force exit after SIGTERM timeout');
224
+ process.exit(1);
225
+ }, 5000);
210
226
  }
211
227
  }, 0);
212
228
  }
@@ -236,12 +252,21 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
236
252
  const cachedToken = (await this.cacheManager.get('client_Licence_token')) as string | undefined;
237
253
  if (cachedToken) {
238
254
  const ok = await this.validateLicence(cachedToken, publicKey, false);
239
- if (ok) return true;
255
+ if (ok) {
256
+ this.logger.log('Licence validated from cache');
257
+ return true;
258
+ }
240
259
  }
241
260
 
242
261
  // 2) Fetch from licensing service
262
+ this.logger.log('Fetching licence from service');
243
263
  const token = await this.getLicencingTokenFromService(realm);
244
264
  const ok = await this.validateLicence(token, publicKey, true);
265
+
266
+ if (ok) {
267
+ this.logger.log('Licence validated from service');
268
+ }
269
+
245
270
  return ok;
246
271
  })();
247
272
 
@@ -251,7 +276,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
251
276
  AuthMiddleware.licenceValidationPromise = null;
252
277
  }
253
278
  } catch (e: any) {
254
- this.logger.warn(`checkLicenceAndValidate failed: ${e?.message || e}`);
279
+ this.logger.error(`checkLicenceAndValidate failed: ${e?.message || e}`, e?.stack);
255
280
  return false;
256
281
  }
257
282
  }
@@ -281,6 +306,11 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
281
306
  // ✅ Save only the timestamp (supports 50 days / 500 days / years)
282
307
  AuthMiddleware.licenceValidatedUntilMs = licEndMs;
283
308
 
309
+ const daysUntilExpiry = Math.floor((licEndMs - now) / (1000 * 60 * 60 * 24));
310
+ this.logger.log(
311
+ `Licence valid until ${new Date(licEndMs).toISOString()} (${daysUntilExpiry} days remaining)`
312
+ );
313
+
284
314
  // ✅ Cache only briefly (avoid long TTL -> avoids TimeoutOverflowWarning)
285
315
  if (updateCache) {
286
316
  await this.cacheSetSeconds('client_Licence_token', tokenJwt, AuthMiddleware.LIC_CACHE_TTL_SECONDS);
@@ -308,18 +338,24 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
308
338
  'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/';
309
339
 
310
340
  const body = { client_name: realm };
311
- const resp = await axios.post(url, body, { timeout: 15_000 });
312
341
 
313
- if (resp.status !== HttpStatusCode.Ok) {
314
- throw new Error(`Licencing service returned status ${resp.status}`);
315
- }
342
+ try {
343
+ const resp = await axios.post(url, body, { timeout: 15_000 });
316
344
 
317
- const token = resp?.data?.token;
318
- if (!token || typeof token !== 'string') {
319
- throw new Error('Licencing service response missing token');
320
- }
345
+ if (resp.status !== HttpStatusCode.Ok) {
346
+ throw new Error(`Licencing service returned status ${resp.status}`);
347
+ }
321
348
 
322
- return token;
349
+ const token = resp?.data?.token;
350
+ if (!token || typeof token !== 'string') {
351
+ throw new Error('Licencing service response missing token');
352
+ }
353
+
354
+ return token;
355
+ } catch (e: any) {
356
+ this.logger.error(`Failed to fetch licence from service: ${e?.message || e}`);
357
+ throw e;
358
+ }
323
359
  }
324
360
 
325
361
  // -------------------------
@@ -352,7 +388,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
352
388
  new URLSearchParams({
353
389
  grant_type: 'client_credentials',
354
390
  client_id: clientId,
355
- client_secret: clientSecret
391
+ client_secret: clientSecret,
356
392
  }).toString(),
357
393
  { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 15_000 }
358
394
  );
@@ -385,7 +421,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
385
421
  return jwt.verify(token, publicKeys, {
386
422
  algorithms: ['RS256'],
387
423
  ignoreExpiration: true,
388
- clockTolerance: 60
424
+ clockTolerance: 60,
389
425
  });
390
426
  }
391
427
 
@@ -395,7 +431,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
395
431
 
396
432
  return jwt.verify(token, publicKeys, {
397
433
  algorithms: ['RS256'],
398
- clockTolerance: 60
434
+ clockTolerance: 60,
399
435
  });
400
436
  }
401
437
 
@@ -417,19 +453,109 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
417
453
  }
418
454
 
419
455
  // -------------------------
420
- // Cache helper (seconds-based, safe across stores)
421
- // - Using seconds avoids ms/sec mismatch across cache-manager stores
422
- // - TTL is kept small so no overflow even if a store uses Node timers
456
+ // ✅ ULTIMATE FIX: Cache helper with COMPLETE OVERFLOW PROTECTION
457
+ // -------------------------
458
+ // Works with ANY cache-manager version and ANY cache store
459
+ // Prevents cache stores from calculating TTL from JWT claims
423
460
  // -------------------------
424
461
  private async cacheSetSeconds(key: string, value: any, ttlSeconds: number) {
425
- // ttlSeconds=0 => store forever (supported by many stores); if not, we just set without ttl
426
- if (!ttlSeconds || ttlSeconds <= 0) {
427
- await (this.cacheManager.set as any)(key, value);
428
- return;
429
- }
462
+ try {
463
+ // ttlSeconds=0 => store forever (no expiry)
464
+ if (!ttlSeconds || ttlSeconds <= 0) {
465
+ await this.cacheManager.set(key, value);
466
+ this.logger.debug(`Cache set (no TTL): ${key}`);
467
+ return;
468
+ }
469
+
470
+ // ✅ CRITICAL: Clamp TTL to prevent overflow
471
+ let safeTtlSeconds = ttlSeconds;
472
+ if (ttlSeconds > AuthMiddleware.MAX_SAFE_TTL_SECONDS) {
473
+ safeTtlSeconds = AuthMiddleware.MAX_SAFE_TTL_SECONDS;
474
+ this.logger.warn(
475
+ `Cache TTL clamped: key=${key}, requested=${ttlSeconds}s (~${Math.floor(ttlSeconds / 86400)} days), using=${safeTtlSeconds}s (~24 days)`
476
+ );
477
+ }
430
478
 
431
- // cache-manager / Nest stores vary: some accept third param number, others accept { ttl }
432
- // We use { ttl } for better compatibility, and TTL is small anyway.
433
- await (this.cacheManager.set as any)(key, value, { ttl: ttlSeconds });
479
+ // Convert to milliseconds
480
+ const ttlMs = safeTtlSeconds * 1000;
481
+
482
+ // ✅ CRITICAL: Double-check we're not exceeding the limit
483
+ // This catches any edge cases where ttlMs might still overflow
484
+ const finalTtlMs = Math.min(ttlMs, AuthMiddleware.MAX_SAFE_TTL_MS);
485
+
486
+ if (finalTtlMs !== ttlMs) {
487
+ this.logger.warn(`TTL further clamped: ${ttlMs}ms -> ${finalTtlMs}ms`);
488
+ }
489
+
490
+ // ✅ Try multiple methods to ensure compatibility with all cache stores
491
+ let cacheSetSuccess = false;
492
+ let lastError: any = null;
493
+
494
+ // Method 1: Modern cache-manager v5+ (TTL in milliseconds as 3rd param)
495
+ if (!cacheSetSuccess) {
496
+ try {
497
+ await this.cacheManager.set(key, value, finalTtlMs);
498
+ cacheSetSuccess = true;
499
+ this.logger.debug(`Cache set (v5 format): ${key} (TTL: ${safeTtlSeconds}s / ${finalTtlMs}ms)`);
500
+ } catch (err: any) {
501
+ lastError = err;
502
+ // Try next method
503
+ }
504
+ }
505
+
506
+ // Method 2: Object-based TTL (milliseconds) - some stores
507
+ if (!cacheSetSuccess) {
508
+ try {
509
+ await (this.cacheManager as any).set(key, value, { ttl: finalTtlMs });
510
+ cacheSetSuccess = true;
511
+ this.logger.debug(`Cache set (object ms format): ${key} (TTL: ${finalTtlMs}ms)`);
512
+ } catch (err: any) {
513
+ lastError = err;
514
+ // Try next method
515
+ }
516
+ }
517
+
518
+ // Method 3: Object-based TTL (seconds) - Redis and others
519
+ if (!cacheSetSuccess) {
520
+ try {
521
+ await (this.cacheManager as any).set(key, value, { ttl: safeTtlSeconds });
522
+ cacheSetSuccess = true;
523
+ this.logger.debug(`Cache set (object sec format): ${key} (TTL: ${safeTtlSeconds}s)`);
524
+ } catch (err: any) {
525
+ lastError = err;
526
+ // Try next method
527
+ }
528
+ }
529
+
530
+ // Method 4: Direct store access (for Redis stores)
531
+ if (!cacheSetSuccess) {
532
+ try {
533
+ const store = (this.cacheManager as any).store;
534
+ const redisClient = store?.client || store?.getClient?.();
535
+
536
+ if (redisClient && typeof redisClient.set === 'function') {
537
+ // Redis native command: SET key value EX seconds
538
+ await redisClient.set(key, value, { EX: safeTtlSeconds });
539
+ cacheSetSuccess = true;
540
+ this.logger.debug(`Cache set (Redis direct): ${key} (EX: ${safeTtlSeconds}s)`);
541
+ }
542
+ } catch (err: any) {
543
+ lastError = err;
544
+ // Continue to fallback
545
+ }
546
+ }
547
+
548
+ // Method 5: Fallback - cache without TTL (better than failing)
549
+ if (!cacheSetSuccess) {
550
+ this.logger.warn(`All TTL methods failed for key=${key}, caching without TTL. Last error: ${lastError?.message}`);
551
+ await this.cacheManager.set(key, value);
552
+ this.logger.debug(`Cache set (no TTL fallback): ${key}`);
553
+ }
554
+
555
+ } catch (e: any) {
556
+ // Even if all methods fail, don't crash the app
557
+ this.logger.error(`Cache set completely failed for key=${key}: ${e?.message || e}`, e?.stack);
558
+ // Cache failures are non-fatal - log and continue
559
+ }
434
560
  }
435
561
  }