dms-middleware-auth 1.1.2 → 1.1.4
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/auth.middleware.d.ts +1 -0
- package/dist/auth.middleware.js +21 -12
- package/package.json +3 -2
- package/src/auth.middleware.ts +20 -13
- package/test_specific_date.ts +76 -0
- package/test_ttl_overflow.ts +31 -0
|
@@ -21,6 +21,7 @@ export declare class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
21
21
|
private static shutdownInitiated;
|
|
22
22
|
private static readonly licenceExpiredMessage;
|
|
23
23
|
private static readonly CLOCK_TOLERANCE_MS;
|
|
24
|
+
private static readonly MAX_INT32;
|
|
24
25
|
private readonly logger;
|
|
25
26
|
constructor(cacheManager: Cache, options: AuthMiddlewareOptions);
|
|
26
27
|
onModuleInit(): Promise<void>;
|
package/dist/auth.middleware.js
CHANGED
|
@@ -90,7 +90,9 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
|
|
|
90
90
|
if (!clientToken) {
|
|
91
91
|
clientToken = await this.clientLogin();
|
|
92
92
|
const decodedToken = jwt.decode(clientToken);
|
|
93
|
-
const
|
|
93
|
+
const ttlRaw = (decodedToken.exp - Math.floor(Date.now() / 1000)) * 1000;
|
|
94
|
+
const ttl = Math.min(Math.max(0, ttlRaw), AuthMiddleware_1.MAX_INT32);
|
|
95
|
+
console.error(`[AuthMiddleware] Client Token TTL: ${ttl}ms (Capped from ${ttlRaw})`);
|
|
94
96
|
await this.cacheManager.set('client_access_token', clientToken, ttl);
|
|
95
97
|
}
|
|
96
98
|
// Cache client role attributes
|
|
@@ -232,10 +234,10 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
|
|
|
232
234
|
try {
|
|
233
235
|
const token = await this.verifyJwt(lic_data, publicKey);
|
|
234
236
|
const licEndMs = this.normalizeEpochMs(token?.lic_end);
|
|
235
|
-
// Reject when licence end (epoch) is missing or already in the past (with tolerance).
|
|
236
237
|
const now = this.getUtcNowMs();
|
|
238
|
+
// Reject when licence end (epoch) is missing or already in the past (with tolerance).
|
|
237
239
|
if (!licEndMs || (licEndMs + AuthMiddleware_1.CLOCK_TOLERANCE_MS) <= now) {
|
|
238
|
-
|
|
240
|
+
console.error(`[AUTH-ERROR] Licence expired. Expiry: ${new Date(licEndMs || 0).toISOString()}, Now: ${new Date(now).toISOString()}`);
|
|
239
241
|
this.markLicenceExpired();
|
|
240
242
|
return { status: false };
|
|
241
243
|
}
|
|
@@ -243,7 +245,9 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
|
|
|
243
245
|
AuthMiddleware_1.licenceValidatedUntilMs = licEndMs;
|
|
244
246
|
this.scheduleLicenceShutdown(licEndMs);
|
|
245
247
|
if (updateCache) {
|
|
246
|
-
const
|
|
248
|
+
const calculatedTtl = licEndMs - now;
|
|
249
|
+
const ttl = Math.min(Math.max(0, calculatedTtl), AuthMiddleware_1.MAX_INT32);
|
|
250
|
+
console.error(`[AuthMiddleware] Licence Cache TTL: ${ttl}ms (Capped from ${calculatedTtl})`);
|
|
247
251
|
await this.cacheManager.set('client_Licence_token', lic_data, ttl);
|
|
248
252
|
}
|
|
249
253
|
return {
|
|
@@ -251,7 +255,13 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
|
|
|
251
255
|
};
|
|
252
256
|
}
|
|
253
257
|
catch (error) {
|
|
254
|
-
|
|
258
|
+
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
|
|
259
|
+
this.logger.error(`Invalid Licence Token: ${error.message}`);
|
|
260
|
+
this.markLicenceExpired();
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
this.logger.warn(`Transient error during licence validation: ${error.message}`);
|
|
264
|
+
}
|
|
255
265
|
return { status: false };
|
|
256
266
|
}
|
|
257
267
|
}
|
|
@@ -267,25 +277,21 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
|
|
|
267
277
|
const now = this.getUtcNowMs();
|
|
268
278
|
const delay = (licEndMs + AuthMiddleware_1.CLOCK_TOLERANCE_MS) - now;
|
|
269
279
|
if (delay <= 0) {
|
|
270
|
-
|
|
280
|
+
console.error('[AUTH-WARN] Licence expired immediately (including tolerance).');
|
|
271
281
|
this.markLicenceExpired();
|
|
272
282
|
return;
|
|
273
283
|
}
|
|
274
284
|
if (AuthMiddleware_1.licenceExpiryTimer) {
|
|
275
285
|
clearTimeout(AuthMiddleware_1.licenceExpiryTimer);
|
|
276
286
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// so we cap the delay and re-check when it fires.
|
|
280
|
-
const MAX_TIMEOUT_VAL = 2147483647;
|
|
281
|
-
const safeDelay = Math.min(delay, MAX_TIMEOUT_VAL);
|
|
287
|
+
const safeDelay = Math.min(delay, AuthMiddleware_1.MAX_INT32);
|
|
288
|
+
console.error(`[AuthMiddleware] Expiry timer set: ${safeDelay}ms (Full delay: ${delay}ms)`);
|
|
282
289
|
AuthMiddleware_1.licenceExpiryTimer = setTimeout(() => {
|
|
283
290
|
const remaining = (licEndMs + AuthMiddleware_1.CLOCK_TOLERANCE_MS) - this.getUtcNowMs();
|
|
284
291
|
if (remaining <= 0) {
|
|
285
292
|
this.markLicenceExpired();
|
|
286
293
|
}
|
|
287
294
|
else {
|
|
288
|
-
// Reschedule if we still have time left
|
|
289
295
|
this.scheduleLicenceShutdown(licEndMs);
|
|
290
296
|
}
|
|
291
297
|
}, safeDelay);
|
|
@@ -295,6 +301,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
|
|
|
295
301
|
return;
|
|
296
302
|
}
|
|
297
303
|
AuthMiddleware_1.shutdownInitiated = true;
|
|
304
|
+
console.error(`[AUTH-DEBUG] setTimeout(stopServer) delay: 0`);
|
|
298
305
|
setTimeout(() => {
|
|
299
306
|
try {
|
|
300
307
|
process.kill(process.pid, 'SIGTERM');
|
|
@@ -303,6 +310,7 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
|
|
|
303
310
|
process.exit(1);
|
|
304
311
|
}
|
|
305
312
|
if (!AuthMiddleware_1.shutdownTimer) {
|
|
313
|
+
console.error(`[AUTH-DEBUG] setTimeout(shutdownTimer) delay: 5000`);
|
|
306
314
|
AuthMiddleware_1.shutdownTimer = setTimeout(() => {
|
|
307
315
|
process.exit(1);
|
|
308
316
|
}, 5000);
|
|
@@ -349,6 +357,7 @@ AuthMiddleware.licenceValidationPromise = null;
|
|
|
349
357
|
AuthMiddleware.shutdownInitiated = false;
|
|
350
358
|
AuthMiddleware.licenceExpiredMessage = "server Licence is expired!. please renew";
|
|
351
359
|
AuthMiddleware.CLOCK_TOLERANCE_MS = 60000; // 1 minute grace period
|
|
360
|
+
AuthMiddleware.MAX_INT32 = 2147483647;
|
|
352
361
|
exports.AuthMiddleware = AuthMiddleware = AuthMiddleware_1 = __decorate([
|
|
353
362
|
(0, common_1.Injectable)(),
|
|
354
363
|
__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.
|
|
3
|
+
"version": "1.1.4",
|
|
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",
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"@types/express": "^5.0.0",
|
|
26
26
|
"@types/jest": "^29.5.14",
|
|
27
27
|
"@types/jsonwebtoken": "^9.0.7",
|
|
28
|
+
"@types/node-forge": "^1.3.14",
|
|
28
29
|
"jest": "^29.7.0",
|
|
29
30
|
"ts-jest": "^29.3.4",
|
|
30
31
|
"typescript": "^5.7.2"
|
|
31
32
|
}
|
|
32
|
-
}
|
|
33
|
+
}
|
package/src/auth.middleware.ts
CHANGED
|
@@ -26,6 +26,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
26
26
|
private static shutdownInitiated = false;
|
|
27
27
|
private static readonly licenceExpiredMessage = "server Licence is expired!. please renew";
|
|
28
28
|
private static readonly CLOCK_TOLERANCE_MS = 60000; // 1 minute grace period
|
|
29
|
+
private static readonly MAX_INT32 = 2147483647;
|
|
29
30
|
private readonly logger = new Logger('AuthMiddleware');
|
|
30
31
|
|
|
31
32
|
constructor(
|
|
@@ -72,7 +73,9 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
72
73
|
if (!clientToken) {
|
|
73
74
|
clientToken = await this.clientLogin();
|
|
74
75
|
const decodedToken: any = jwt.decode(clientToken);
|
|
75
|
-
const
|
|
76
|
+
const ttlRaw = (decodedToken.exp - Math.floor(Date.now() / 1000)) * 1000;
|
|
77
|
+
const ttl = Math.min(Math.max(0, ttlRaw), AuthMiddleware.MAX_INT32);
|
|
78
|
+
console.error(`[AuthMiddleware] Client Token TTL: ${ttl}ms (Capped from ${ttlRaw})`);
|
|
76
79
|
await this.cacheManager.set('client_access_token', clientToken, ttl);
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -229,11 +232,11 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
229
232
|
try {
|
|
230
233
|
const token: any = await this.verifyJwt(lic_data, publicKey);
|
|
231
234
|
const licEndMs = this.normalizeEpochMs(token?.lic_end);
|
|
235
|
+
const now = this.getUtcNowMs();
|
|
232
236
|
|
|
233
237
|
// Reject when licence end (epoch) is missing or already in the past (with tolerance).
|
|
234
|
-
const now = this.getUtcNowMs();
|
|
235
238
|
if (!licEndMs || (licEndMs + AuthMiddleware.CLOCK_TOLERANCE_MS) <= now) {
|
|
236
|
-
|
|
239
|
+
console.error(`[AUTH-ERROR] Licence expired. Expiry: ${new Date(licEndMs || 0).toISOString()}, Now: ${new Date(now).toISOString()}`);
|
|
237
240
|
this.markLicenceExpired();
|
|
238
241
|
return { status: false };
|
|
239
242
|
}
|
|
@@ -243,16 +246,22 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
243
246
|
this.scheduleLicenceShutdown(licEndMs);
|
|
244
247
|
|
|
245
248
|
if (updateCache) {
|
|
246
|
-
const
|
|
249
|
+
const calculatedTtl = licEndMs - now;
|
|
250
|
+
const ttl = Math.min(Math.max(0, calculatedTtl), AuthMiddleware.MAX_INT32);
|
|
251
|
+
console.error(`[AuthMiddleware] Licence Cache TTL: ${ttl}ms (Capped from ${calculatedTtl})`);
|
|
247
252
|
await this.cacheManager.set('client_Licence_token', lic_data, ttl);
|
|
248
253
|
}
|
|
249
254
|
|
|
250
255
|
return {
|
|
251
256
|
status: true,
|
|
252
|
-
|
|
253
257
|
};
|
|
254
258
|
} catch (error: any) {
|
|
255
|
-
|
|
259
|
+
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
|
|
260
|
+
this.logger.error(`Invalid Licence Token: ${error.message}`);
|
|
261
|
+
this.markLicenceExpired();
|
|
262
|
+
} else {
|
|
263
|
+
this.logger.warn(`Transient error during licence validation: ${error.message}`);
|
|
264
|
+
}
|
|
256
265
|
return { status: false };
|
|
257
266
|
}
|
|
258
267
|
}
|
|
@@ -271,7 +280,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
271
280
|
const delay = (licEndMs + AuthMiddleware.CLOCK_TOLERANCE_MS) - now;
|
|
272
281
|
|
|
273
282
|
if (delay <= 0) {
|
|
274
|
-
|
|
283
|
+
console.error('[AUTH-WARN] Licence expired immediately (including tolerance).');
|
|
275
284
|
this.markLicenceExpired();
|
|
276
285
|
return;
|
|
277
286
|
}
|
|
@@ -280,18 +289,14 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
280
289
|
clearTimeout(AuthMiddleware.licenceExpiryTimer);
|
|
281
290
|
}
|
|
282
291
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// so we cap the delay and re-check when it fires.
|
|
286
|
-
const MAX_TIMEOUT_VAL = 2147483647;
|
|
287
|
-
const safeDelay = Math.min(delay, MAX_TIMEOUT_VAL);
|
|
292
|
+
const safeDelay = Math.min(delay, AuthMiddleware.MAX_INT32);
|
|
293
|
+
console.error(`[AuthMiddleware] Expiry timer set: ${safeDelay}ms (Full delay: ${delay}ms)`);
|
|
288
294
|
|
|
289
295
|
AuthMiddleware.licenceExpiryTimer = setTimeout(() => {
|
|
290
296
|
const remaining = (licEndMs + AuthMiddleware.CLOCK_TOLERANCE_MS) - this.getUtcNowMs();
|
|
291
297
|
if (remaining <= 0) {
|
|
292
298
|
this.markLicenceExpired();
|
|
293
299
|
} else {
|
|
294
|
-
// Reschedule if we still have time left
|
|
295
300
|
this.scheduleLicenceShutdown(licEndMs);
|
|
296
301
|
}
|
|
297
302
|
}, safeDelay);
|
|
@@ -302,6 +307,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
302
307
|
return;
|
|
303
308
|
}
|
|
304
309
|
AuthMiddleware.shutdownInitiated = true;
|
|
310
|
+
console.error(`[AUTH-DEBUG] setTimeout(stopServer) delay: 0`);
|
|
305
311
|
setTimeout(() => {
|
|
306
312
|
try {
|
|
307
313
|
process.kill(process.pid, 'SIGTERM');
|
|
@@ -309,6 +315,7 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
|
|
|
309
315
|
process.exit(1);
|
|
310
316
|
}
|
|
311
317
|
if (!AuthMiddleware.shutdownTimer) {
|
|
318
|
+
console.error(`[AUTH-DEBUG] setTimeout(shutdownTimer) delay: 5000`);
|
|
312
319
|
AuthMiddleware.shutdownTimer = setTimeout(() => {
|
|
313
320
|
process.exit(1);
|
|
314
321
|
}, 5000);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
import { AuthMiddleware } from './src/auth.middleware';
|
|
3
|
+
import * as jwt from 'jsonwebtoken';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
|
|
6
|
+
async function runTest() {
|
|
7
|
+
console.log("--- Starting Thorough Test for Date 1774981740000 ---");
|
|
8
|
+
|
|
9
|
+
// 1. Setup Mocks
|
|
10
|
+
const mockCache = {
|
|
11
|
+
get: async (key: string) => undefined,
|
|
12
|
+
set: async (key: string, val: any, ttl: any) => {
|
|
13
|
+
console.log(`[MockCache] set key=${key}, ttl=${ttl}`);
|
|
14
|
+
},
|
|
15
|
+
} as any;
|
|
16
|
+
|
|
17
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
|
|
18
|
+
modulusLength: 2048,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
let publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' }) as string;
|
|
22
|
+
// Strip headers as AuthMiddleware adds them
|
|
23
|
+
const publicKeyBody = publicKeyPem
|
|
24
|
+
.replace('-----BEGIN PUBLIC KEY-----\n', '')
|
|
25
|
+
.replace('\n-----END PUBLIC KEY-----\n', '');
|
|
26
|
+
|
|
27
|
+
const mockOptions = {
|
|
28
|
+
publicKey: publicKeyBody,
|
|
29
|
+
keycloakUrl: 'http://localhost:8080',
|
|
30
|
+
realm: 'test-realm',
|
|
31
|
+
clientId: 'test-client',
|
|
32
|
+
clientSecret: 'secret',
|
|
33
|
+
clientUuid: 'uuid',
|
|
34
|
+
bypassURL: '/health',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const authMiddleware = new AuthMiddleware(mockCache, mockOptions);
|
|
38
|
+
|
|
39
|
+
// 2. Create Token with specific date
|
|
40
|
+
const licEnd = 1774981740000;
|
|
41
|
+
const payload = {
|
|
42
|
+
lic_end: licEnd
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' }) as string;
|
|
46
|
+
const token = jwt.sign(payload, privateKeyPem, { algorithm: 'RS256', noTimestamp: true });
|
|
47
|
+
|
|
48
|
+
console.log(`Test Token: ${token}`);
|
|
49
|
+
console.log(`Current Time (Date.now()): ${Date.now()}`);
|
|
50
|
+
console.log(`User's Date to test: ${licEnd}`);
|
|
51
|
+
|
|
52
|
+
// 3. Run Validation
|
|
53
|
+
console.log("\n--- Calling validateLicence ---");
|
|
54
|
+
// We can access private methods using any cast for testing
|
|
55
|
+
const result = await (authMiddleware as any).validateLicence(token, publicKeyBody, true);
|
|
56
|
+
|
|
57
|
+
console.log(`\nResult Status: ${result.status}`);
|
|
58
|
+
|
|
59
|
+
if (result.status) {
|
|
60
|
+
console.log("SUCCESS: Middleware accepted the future date.");
|
|
61
|
+
} else {
|
|
62
|
+
console.error("FAILURE: Middleware rejected the future date.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 4. Check if expired flag was set
|
|
66
|
+
const isExpired = (AuthMiddleware as any).licenceExpired;
|
|
67
|
+
console.log(`AuthMiddleware.licenceExpired: ${isExpired}`);
|
|
68
|
+
|
|
69
|
+
// 5. Cleanup Timer
|
|
70
|
+
if ((AuthMiddleware as any).licenceExpiryTimer) {
|
|
71
|
+
console.log("Clearing expiry timer...");
|
|
72
|
+
clearTimeout((AuthMiddleware as any).licenceExpiryTimer);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
runTest().catch(console.error);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
import { caching } from 'cache-manager';
|
|
3
|
+
|
|
4
|
+
async function testTTL() {
|
|
5
|
+
console.log("--- Testing Cache TTL Overflow ---");
|
|
6
|
+
const cache = await caching('memory');
|
|
7
|
+
|
|
8
|
+
const largeTTL = 4351255821; // ~50 days in ms
|
|
9
|
+
console.log(`Setting key with TTL: ${largeTTL}ms`);
|
|
10
|
+
|
|
11
|
+
await cache.set('test_key', 'test_value', largeTTL);
|
|
12
|
+
|
|
13
|
+
const val = await cache.get('test_key');
|
|
14
|
+
console.log(`Immediate get: ${val}`);
|
|
15
|
+
|
|
16
|
+
if (!val) {
|
|
17
|
+
console.error("FAIL: Value expired immediately!");
|
|
18
|
+
} else {
|
|
19
|
+
console.log("Value still present. Waiting 2s to check again...");
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
21
|
+
const val2 = await cache.get('test_key');
|
|
22
|
+
console.log(`After 2s get: ${val2}`);
|
|
23
|
+
if (!val2) {
|
|
24
|
+
console.error("FAIL: Value expired after 2s (likely overflow)!");
|
|
25
|
+
} else {
|
|
26
|
+
console.log("SUCCESS: Cache handles large TTL.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
testTTL().catch(console.error);
|