dms-middleware-auth 1.0.8 → 1.0.10

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.
@@ -0,0 +1,131 @@
1
+ import { AuthMiddleware } from '../src/auth.middleware';
2
+ import { HttpStatusCode } from 'axios';
3
+
4
+ const baseOptions = {
5
+ publicKey: 'PUBLIC_KEY',
6
+ keycloakUrl: '',
7
+ realm: '',
8
+ clientId: 'client',
9
+ clientSecret: '',
10
+ clientUuid: '',
11
+ };
12
+
13
+ const createCacheMock = (initial: Record<string, any> = {}) => {
14
+ const store = { ...initial };
15
+ return {
16
+ get: jest.fn(async (key: string) => store[key]),
17
+ set: jest.fn(async (key: string, value: any) => {
18
+ store[key] = value;
19
+ }),
20
+ };
21
+ };
22
+
23
+ const createRes = () => {
24
+ const res: any = {
25
+ status: jest.fn().mockReturnThis(),
26
+ json: jest.fn(),
27
+ };
28
+ return res;
29
+ };
30
+
31
+ const createReq = (override: Record<string, any> = {}) =>
32
+ ({
33
+ headers: { authorization: 'Bearer access-token' },
34
+ originalUrl: '/route',
35
+ body: {},
36
+ ...override,
37
+ } as any);
38
+
39
+ describe('AuthMiddleware licensing', () => {
40
+ beforeEach(() => {
41
+ jest.restoreAllMocks();
42
+ jest.clearAllMocks();
43
+ jest.useRealTimers();
44
+ // Reset static flags/timers between tests
45
+ const cls: any = AuthMiddleware;
46
+ if (cls.shutdownTimer) {
47
+ clearTimeout(cls.shutdownTimer);
48
+ }
49
+ cls.licenceExpired = false;
50
+ cls.shutdownTimer = null;
51
+ });
52
+
53
+ it('allows requests when licence is valid (supports seconds epoch)', async () => {
54
+ const cache = createCacheMock({ client_access_token: 'cached-client-token' });
55
+ const middleware = new AuthMiddleware(cache as any, baseOptions as any);
56
+
57
+ jest.spyOn(middleware as any, 'getLicencingDetails').mockResolvedValue({
58
+ code: HttpStatusCode.Ok,
59
+ data: 'licence-token',
60
+ });
61
+ jest.spyOn(middleware as any, 'verifyJwt').mockReturnValue({
62
+ lic_end: Math.floor(Date.now() / 1000) + 60, // seconds epoch
63
+ });
64
+ jest
65
+ .spyOn(middleware as any, 'decodeAccessToken')
66
+ .mockReturnValue({
67
+ resource_access: { client: { roles: ['admin'] } },
68
+ preferred_username: 'user',
69
+ });
70
+ jest.spyOn(middleware as any, 'getClientRoleAttributes').mockResolvedValue({
71
+ attributes: { '/route': '{"params":"false"}' },
72
+ });
73
+
74
+ const req = createReq();
75
+ const res = createRes();
76
+ const next = jest.fn();
77
+
78
+ await middleware.use(req, res as any, next);
79
+
80
+ expect(res.status).not.toHaveBeenCalled();
81
+ expect(next).toHaveBeenCalledTimes(1);
82
+ expect(cache.set).toHaveBeenCalledWith('client_Licence_token', 'licence-token');
83
+ });
84
+
85
+ it('rejects and schedules shutdown when licence is expired', async () => {
86
+ jest.useFakeTimers();
87
+ const cache = createCacheMock({ client_access_token: 'cached-client-token' });
88
+ const middleware = new AuthMiddleware(cache as any, baseOptions as any);
89
+
90
+ jest.spyOn(middleware as any, 'getLicencingDetails').mockResolvedValue({
91
+ code: HttpStatusCode.Ok,
92
+ data: 'licence-token',
93
+ });
94
+ jest.spyOn(middleware as any, 'verifyJwt').mockReturnValue({
95
+ lic_end: Date.now() - 1, // already expired (ms epoch)
96
+ });
97
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => undefined) as any);
98
+
99
+ const req = createReq();
100
+ const res = createRes();
101
+ const next = jest.fn();
102
+
103
+ await middleware.use(req, res as any, next);
104
+
105
+ expect(res.status).toHaveBeenCalledWith(HttpStatusCode.Forbidden);
106
+ expect(res.json).toHaveBeenCalled();
107
+ expect(next).not.toHaveBeenCalled();
108
+ expect((AuthMiddleware as any).licenceExpired).toBe(true);
109
+
110
+ // advance time to trigger shutdown timer
111
+ jest.advanceTimersByTime(2 * 60 * 1000);
112
+ expect(exitSpy).toHaveBeenCalledWith(1);
113
+ });
114
+
115
+ it('immediately rejects when already marked expired', async () => {
116
+ const cache = createCacheMock();
117
+ (AuthMiddleware as any).licenceExpired = true;
118
+ const middleware = new AuthMiddleware(cache as any, baseOptions as any);
119
+ const checkSpy = jest.spyOn(middleware as any, 'checkLicenceAndValidate');
120
+
121
+ const req = createReq();
122
+ const res = createRes();
123
+ const next = jest.fn();
124
+
125
+ await middleware.use(req, res as any, next);
126
+
127
+ expect(checkSpy).not.toHaveBeenCalled();
128
+ expect(res.status).toHaveBeenCalledWith(HttpStatusCode.Forbidden);
129
+ expect(next).not.toHaveBeenCalled();
130
+ });
131
+ });
@@ -1,4 +1,4 @@
1
- import { NestMiddleware } from '@nestjs/common';
1
+ import { NestMiddleware, OnModuleInit } from '@nestjs/common';
2
2
  import { Response, NextFunction } from 'express';
3
3
  import { Cache } from 'cache-manager';
4
4
  interface AuthMiddlewareOptions {
@@ -8,14 +8,33 @@ interface AuthMiddlewareOptions {
8
8
  clientId: string;
9
9
  clientSecret: string;
10
10
  clientUuid: string;
11
+ bypassURL: string;
11
12
  }
12
- export declare class AuthMiddleware implements NestMiddleware {
13
+ export declare class AuthMiddleware implements NestMiddleware, OnModuleInit {
13
14
  private cacheManager;
14
15
  private readonly options;
16
+ private static licenceExpired;
17
+ private static shutdownTimer;
18
+ private static licenceExpiryTimer;
19
+ private static licenceValidatedUntilMs;
20
+ private static licenceValidationPromise;
21
+ private static shutdownInitiated;
22
+ private static readonly licenceExpiredMessage;
15
23
  constructor(cacheManager: Cache, options: AuthMiddlewareOptions);
24
+ onModuleInit(): Promise<void>;
16
25
  use(req: any, res: Response, next: NextFunction): Promise<void | Response<any, Record<string, any>>>;
17
26
  private clientLogin;
18
27
  private getUserDetails;
19
28
  private getClientRoleAttributes;
29
+ private checkLicenceAndValidate;
30
+ private getLicencingDetails;
31
+ private validateLicence;
32
+ private markLicenceExpired;
33
+ private scheduleLicenceShutdown;
34
+ private stopServer;
35
+ private normalizeEpochMs;
36
+ private verifyJwt;
37
+ private decodeAccessToken;
38
+ private extractBearerToken;
20
39
  }
21
40
  export {};
@@ -44,30 +44,43 @@ var __metadata = (this && this.__metadata) || function (k, v) {
44
44
  var __param = (this && this.__param) || function (paramIndex, decorator) {
45
45
  return function (target, key) { decorator(target, key, paramIndex); }
46
46
  };
47
- var __importDefault = (this && this.__importDefault) || function (mod) {
48
- return (mod && mod.__esModule) ? mod : { "default": mod };
49
- };
47
+ var AuthMiddleware_1;
50
48
  Object.defineProperty(exports, "__esModule", { value: true });
51
49
  exports.AuthMiddleware = void 0;
52
50
  const common_1 = require("@nestjs/common");
53
51
  const jwt = __importStar(require("jsonwebtoken"));
54
- const axios_1 = __importDefault(require("axios"));
52
+ const axios_1 = __importStar(require("axios"));
55
53
  const cache_manager_1 = require("@nestjs/cache-manager");
56
- let AuthMiddleware = class AuthMiddleware {
54
+ const process = __importStar(require("node:process"));
55
+ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
57
56
  constructor(cacheManager, options) {
58
57
  this.cacheManager = cacheManager;
59
58
  this.options = options;
60
59
  }
61
- async use(req, res, next) {
62
- const authHeader = req.headers['authorization'];
63
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
64
- return res.status(401).json({ message: 'Bearer token required' });
60
+ async onModuleInit() {
61
+ const { publicKey, realm } = this.options;
62
+ const isValid = await this.checkLicenceAndValidate(realm, publicKey);
63
+ if (!isValid) {
64
+ this.stopServer();
65
65
  }
66
- const { publicKey, clientId } = this.options;
66
+ }
67
+ async use(req, res, next) {
68
+ const { publicKey, clientId, realm, bypassURL } = this.options;
67
69
  try {
68
- const token = authHeader.split(' ')[1];
69
- const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
70
- const decoded = jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
70
+ if (AuthMiddleware_1.licenceExpired) {
71
+ return res.status(axios_1.HttpStatusCode.Forbidden).json({ message: AuthMiddleware_1.licenceExpiredMessage });
72
+ }
73
+ if (!AuthMiddleware_1.licenceValidatedUntilMs) {
74
+ return res.status(axios_1.HttpStatusCode.Forbidden).json({ message: AuthMiddleware_1.licenceExpiredMessage });
75
+ }
76
+ if (req.originalUrl == bypassURL) {
77
+ return next();
78
+ }
79
+ const authHeader = req.headers['authorization'];
80
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
81
+ return res.status(401).json({ message: 'Bearer token required' });
82
+ }
83
+ const decoded = this.decodeAccessToken(authHeader, publicKey);
71
84
  // Cache the client token
72
85
  let clientToken = await this.cacheManager.get('client_access_token');
73
86
  if (!clientToken) {
@@ -160,9 +173,168 @@ let AuthMiddleware = class AuthMiddleware {
160
173
  throw new Error(`Failed to fetch client role attributes: ${error.response?.data?.error_description || error.message}`);
161
174
  }
162
175
  }
176
+ async checkLicenceAndValidate(realm, publicKey) {
177
+ try {
178
+ if (AuthMiddleware_1.licenceExpired) {
179
+ return false;
180
+ }
181
+ if (AuthMiddleware_1.licenceValidatedUntilMs && AuthMiddleware_1.licenceValidatedUntilMs > Date.now()) {
182
+ return true;
183
+ }
184
+ if (AuthMiddleware_1.licenceValidationPromise) {
185
+ return await AuthMiddleware_1.licenceValidationPromise;
186
+ }
187
+ AuthMiddleware_1.licenceValidationPromise = (async () => {
188
+ const response = await this.getLicencingDetails(realm);
189
+ if (response.code === axios_1.HttpStatusCode.InternalServerError) {
190
+ return false;
191
+ }
192
+ else {
193
+ const validate = await this.validateLicence(response.data, publicKey);
194
+ return validate.status;
195
+ }
196
+ })();
197
+ try {
198
+ return await AuthMiddleware_1.licenceValidationPromise;
199
+ }
200
+ finally {
201
+ AuthMiddleware_1.licenceValidationPromise = null;
202
+ }
203
+ }
204
+ catch (error) {
205
+ return false;
206
+ }
207
+ }
208
+ async getLicencingDetails(realm) {
209
+ try {
210
+ const url = 'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/';
211
+ const body = { "client_name": realm };
212
+ const response = await axios_1.default.post(url, body);
213
+ if (response.status === axios_1.HttpStatusCode.Ok) {
214
+ return {
215
+ code: axios_1.HttpStatusCode.Ok,
216
+ data: response.data.token
217
+ };
218
+ }
219
+ else {
220
+ return {
221
+ code: axios_1.HttpStatusCode.InternalServerError,
222
+ message: "InternalServer error"
223
+ };
224
+ }
225
+ }
226
+ catch (error) {
227
+ return {
228
+ code: axios_1.HttpStatusCode.InternalServerError,
229
+ data: error?.response?.data || error?.message || 'Licencing service error'
230
+ };
231
+ }
232
+ }
233
+ async validateLicence(lic_data, publicKey) {
234
+ try {
235
+ const token = await this.verifyJwt(lic_data, publicKey);
236
+ const licEndMs = this.normalizeEpochMs(token?.lic_end);
237
+ // Reject when licence end (epoch) is missing or already in the past.
238
+ if (!licEndMs) {
239
+ this.markLicenceExpired();
240
+ return {
241
+ status: false
242
+ };
243
+ }
244
+ if (licEndMs <= Date.now()) {
245
+ this.markLicenceExpired();
246
+ return {
247
+ status: false
248
+ };
249
+ }
250
+ else {
251
+ AuthMiddleware_1.licenceValidatedUntilMs = licEndMs;
252
+ this.scheduleLicenceShutdown(licEndMs);
253
+ return {
254
+ status: true,
255
+ ttl: (licEndMs - Date.now()) > 0 ? (licEndMs - Date.now()) : null
256
+ };
257
+ }
258
+ }
259
+ catch (error) {
260
+ this.markLicenceExpired();
261
+ return {
262
+ status: false
263
+ };
264
+ }
265
+ }
266
+ markLicenceExpired() {
267
+ if (!AuthMiddleware_1.licenceExpired) {
268
+ AuthMiddleware_1.licenceExpired = true;
269
+ }
270
+ this.stopServer();
271
+ }
272
+ scheduleLicenceShutdown(licEndMs) {
273
+ if (!licEndMs) {
274
+ return;
275
+ }
276
+ const delay = licEndMs - Date.now();
277
+ if (delay <= 0) {
278
+ this.markLicenceExpired();
279
+ return;
280
+ }
281
+ if (AuthMiddleware_1.licenceExpiryTimer) {
282
+ return;
283
+ }
284
+ AuthMiddleware_1.licenceExpiryTimer = setTimeout(() => {
285
+ this.markLicenceExpired();
286
+ }, delay);
287
+ }
288
+ stopServer() {
289
+ if (AuthMiddleware_1.shutdownInitiated) {
290
+ return;
291
+ }
292
+ AuthMiddleware_1.shutdownInitiated = true;
293
+ setTimeout(() => {
294
+ try {
295
+ process.kill(process.pid, 'SIGTERM');
296
+ }
297
+ catch {
298
+ process.exit(1);
299
+ }
300
+ if (!AuthMiddleware_1.shutdownTimer) {
301
+ AuthMiddleware_1.shutdownTimer = setTimeout(() => {
302
+ process.exit(1);
303
+ }, 5000);
304
+ }
305
+ }, 0);
306
+ }
307
+ normalizeEpochMs(epoch) {
308
+ if (!epoch || Number.isNaN(epoch)) {
309
+ return null;
310
+ }
311
+ // Accept seconds or milliseconds since Unix epoch and normalize to ms for Date.now() comparison.
312
+ if (epoch < 1_000_000_000_000) {
313
+ return epoch * 1000;
314
+ }
315
+ return epoch;
316
+ }
317
+ verifyJwt(token, publicKey) {
318
+ const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
319
+ return jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
320
+ }
321
+ decodeAccessToken(authHeader, publicKey) {
322
+ const token = this.extractBearerToken(authHeader);
323
+ return this.verifyJwt(token, publicKey);
324
+ }
325
+ extractBearerToken(authHeader) {
326
+ return authHeader.split(' ')[1];
327
+ }
163
328
  };
164
329
  exports.AuthMiddleware = AuthMiddleware;
165
- exports.AuthMiddleware = AuthMiddleware = __decorate([
330
+ AuthMiddleware.licenceExpired = false;
331
+ AuthMiddleware.shutdownTimer = null;
332
+ AuthMiddleware.licenceExpiryTimer = null;
333
+ AuthMiddleware.licenceValidatedUntilMs = null;
334
+ AuthMiddleware.licenceValidationPromise = null;
335
+ AuthMiddleware.shutdownInitiated = false;
336
+ AuthMiddleware.licenceExpiredMessage = "server Licence is expired!. please renew";
337
+ exports.AuthMiddleware = AuthMiddleware = AuthMiddleware_1 = __decorate([
166
338
  (0, common_1.Injectable)(),
167
339
  __param(0, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
168
340
  __param(1, (0, common_1.Inject)('AUTH_MIDDLEWARE_OPTIONS')),
package/jest.config.js ADDED
@@ -0,0 +1,14 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ testMatch: ['**/__tests__/**/*.spec.ts'],
6
+ clearMocks: true,
7
+ roots: ['<rootDir>'],
8
+ moduleFileExtensions: ['ts', 'js', 'json'],
9
+ globals: {
10
+ 'ts-jest': {
11
+ tsconfig: '<rootDir>/tsconfig.jest.json',
12
+ },
13
+ },
14
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dms-middleware-auth",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
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",
@@ -23,7 +23,10 @@
23
23
  "devDependencies": {
24
24
  "@types/axios": "^0.14.4",
25
25
  "@types/express": "^5.0.0",
26
+ "@types/jest": "^29.5.14",
26
27
  "@types/jsonwebtoken": "^9.0.7",
28
+ "jest": "^29.7.0",
29
+ "ts-jest": "^29.3.4",
27
30
  "typescript": "^5.7.2"
28
31
  }
29
32
  }
@@ -1,10 +1,10 @@
1
- import { Inject, Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
2
- import { Request, Response, NextFunction } from 'express';
1
+ import { Inject, Injectable, NestMiddleware, OnModuleInit } from '@nestjs/common';
2
+ import { Response, NextFunction } from 'express';
3
3
  import * as jwt from 'jsonwebtoken';
4
- import axios from 'axios';
4
+ import axios, { HttpStatusCode } from 'axios';
5
5
  import { CACHE_MANAGER } from '@nestjs/cache-manager';
6
6
  import { Cache } from 'cache-manager';
7
-
7
+ import * as process from "node:process";
8
8
  interface AuthMiddlewareOptions {
9
9
  publicKey: string;
10
10
  keycloakUrl: string;
@@ -12,38 +12,60 @@ interface AuthMiddlewareOptions {
12
12
  clientId: string;
13
13
  clientSecret: string;
14
14
  clientUuid: string;
15
+ bypassURL: string;
15
16
  }
16
17
 
17
18
  @Injectable()
18
- export class AuthMiddleware implements NestMiddleware {
19
- constructor(
20
- @Inject(CACHE_MANAGER) private cacheManager: Cache,
21
- @Inject('AUTH_MIDDLEWARE_OPTIONS') private readonly options: AuthMiddlewareOptions
22
- ) {}
19
+ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
20
+ private static licenceExpired = false;
21
+ private static shutdownTimer: NodeJS.Timeout | null = null;
22
+ private static licenceExpiryTimer: NodeJS.Timeout | null = null;
23
+ private static licenceValidatedUntilMs: number | null = null;
24
+ private static licenceValidationPromise: Promise<boolean> | null = null;
25
+ private static shutdownInitiated = false;
26
+ private static readonly licenceExpiredMessage = "server Licence is expired!. please renew";
23
27
 
24
- async use(req: any, res: Response, next: NextFunction) {
25
- const authHeader = req.headers['authorization'];
28
+ constructor(
29
+ @Inject(CACHE_MANAGER) private cacheManager: Cache,
30
+ @Inject('AUTH_MIDDLEWARE_OPTIONS') private readonly options: AuthMiddlewareOptions
31
+ ) {}
32
+
33
+ async onModuleInit() {
34
+ const { publicKey, realm } = this.options;
35
+ const isValid = await this.checkLicenceAndValidate(realm, publicKey);
36
+ if (!isValid) {
37
+ this.stopServer();
38
+ }
39
+ }
40
+
41
+ async use(req: any, res: Response, next: NextFunction) {
26
42
 
27
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
28
- return res.status(401).json({ message: 'Bearer token required' });
29
- }
30
43
 
31
- const { publicKey, clientId } = this.options;
44
+ const { publicKey, clientId, realm, bypassURL } = this.options;
32
45
 
33
46
  try {
34
- const token = authHeader.split(' ')[1];
35
- const publicKeys: string = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
47
+ if (AuthMiddleware.licenceExpired) {
48
+ return res.status(HttpStatusCode.Forbidden).json({ message: AuthMiddleware.licenceExpiredMessage });
49
+ }
36
50
 
37
- const decoded: any = jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
51
+ if (!AuthMiddleware.licenceValidatedUntilMs) {
52
+ return res.status(HttpStatusCode.Forbidden).json({ message: AuthMiddleware.licenceExpiredMessage });
53
+ }
54
+ if (req.originalUrl == bypassURL) {
55
+ return next();
56
+ }
57
+ const authHeader = req.headers['authorization'];
38
58
 
59
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
60
+ return res.status(401).json({ message: 'Bearer token required' });
61
+ }
62
+ const decoded: any = this.decodeAccessToken(authHeader, publicKey)
39
63
  // Cache the client token
40
64
  let clientToken: any = await this.cacheManager.get('client_access_token');
41
65
  if (!clientToken) {
42
66
  clientToken = await this.clientLogin();
43
67
  const decodedToken: any = jwt.decode(clientToken);
44
68
  const ttl = (decodedToken.exp - Math.floor(Date.now() / 1000)) * 1000;
45
-
46
-
47
69
  await this.cacheManager.set('client_access_token', clientToken, ttl );
48
70
  }
49
71
 
@@ -76,7 +98,6 @@ export class AuthMiddleware implements NestMiddleware {
76
98
  }
77
99
  }
78
100
 
79
-
80
101
  // Cache interface details
81
102
  /*const userName = decoded.preferred_username;
82
103
  let userAttributes: any = await this.cacheManager.get(userName);
@@ -151,4 +172,166 @@ export class AuthMiddleware implements NestMiddleware {
151
172
  throw new Error(`Failed to fetch client role attributes: ${error.response?.data?.error_description || error.message}`);
152
173
  }
153
174
  }
175
+
176
+ private async checkLicenceAndValidate(realm: string, publicKey:string) {
177
+ try {
178
+ if (AuthMiddleware.licenceExpired) {
179
+ return false;
180
+ }
181
+ if (AuthMiddleware.licenceValidatedUntilMs && AuthMiddleware.licenceValidatedUntilMs > Date.now()) {
182
+ return true;
183
+ }
184
+ if (AuthMiddleware.licenceValidationPromise) {
185
+ return await AuthMiddleware.licenceValidationPromise;
186
+ }
187
+ AuthMiddleware.licenceValidationPromise = (async () => {
188
+ const response:any = await this.getLicencingDetails(realm);
189
+ if (response.code === HttpStatusCode.InternalServerError) {
190
+ return false
191
+ }
192
+ else{
193
+ const validate = await this.validateLicence(response.data, publicKey);
194
+ return validate.status;
195
+ }
196
+ })();
197
+ try {
198
+ return await AuthMiddleware.licenceValidationPromise;
199
+ } finally {
200
+ AuthMiddleware.licenceValidationPromise = null;
201
+ }
202
+ }
203
+ catch(error:any){
204
+ return false;
205
+
206
+ }
207
+ }
208
+ private async getLicencingDetails(realm: string) {
209
+ try{
210
+
211
+
212
+
213
+ const url = 'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/'
214
+ const body = { "client_name": realm };
215
+
216
+ const response = await axios.post( url , body );
217
+ if (response.status === HttpStatusCode.Ok) {
218
+ return {
219
+ code: HttpStatusCode.Ok,
220
+ data: response.data.token
221
+ };
222
+ }
223
+ else {
224
+ return {
225
+ code: HttpStatusCode.InternalServerError,
226
+ message: "InternalServer error"
227
+ };
228
+ }
229
+
230
+ }catch(error:any){
231
+ return {
232
+ code: HttpStatusCode.InternalServerError,
233
+ data: error?.response?.data || error?.message || 'Licencing service error'
234
+ };
235
+ }
236
+ }
237
+ private async validateLicence(lic_data:any, publicKey:any){
238
+ try{
239
+ const token:any = await this.verifyJwt(lic_data, publicKey);
240
+ const licEndMs = this.normalizeEpochMs(token?.lic_end);
241
+ // Reject when licence end (epoch) is missing or already in the past.
242
+ if (!licEndMs) {
243
+ this.markLicenceExpired();
244
+ return{
245
+ status: false
246
+ }
247
+ }
248
+ if (licEndMs <= Date.now()) {
249
+ this.markLicenceExpired();
250
+ return {
251
+ status: false
252
+ }
253
+ } else {
254
+ AuthMiddleware.licenceValidatedUntilMs = licEndMs;
255
+ this.scheduleLicenceShutdown(licEndMs);
256
+ return {
257
+ status: true,
258
+ ttl: (licEndMs - Date.now()) > 0 ? (licEndMs - Date.now()) : null
259
+ }
260
+ }
261
+ }
262
+ catch(error:any){
263
+ this.markLicenceExpired();
264
+ return {
265
+ status: false
266
+ };
267
+ }
268
+ }
269
+
270
+ private markLicenceExpired() {
271
+ if (!AuthMiddleware.licenceExpired) {
272
+ AuthMiddleware.licenceExpired = true;
273
+ }
274
+ this.stopServer();
275
+ }
276
+
277
+ private scheduleLicenceShutdown(licEndMs: number) {
278
+ if (!licEndMs) {
279
+ return;
280
+ }
281
+ const delay = licEndMs - Date.now();
282
+ if (delay <= 0) {
283
+ this.markLicenceExpired();
284
+ return;
285
+ }
286
+ if (AuthMiddleware.licenceExpiryTimer) {
287
+ return;
288
+ }
289
+ AuthMiddleware.licenceExpiryTimer = setTimeout(() => {
290
+ this.markLicenceExpired();
291
+ }, delay);
292
+ }
293
+
294
+ private stopServer() {
295
+ if (AuthMiddleware.shutdownInitiated) {
296
+ return;
297
+ }
298
+ AuthMiddleware.shutdownInitiated = true;
299
+ setTimeout(() => {
300
+ try {
301
+ process.kill(process.pid, 'SIGTERM');
302
+ } catch {
303
+ process.exit(1);
304
+ }
305
+ if (!AuthMiddleware.shutdownTimer) {
306
+ AuthMiddleware.shutdownTimer = setTimeout(() => {
307
+ process.exit(1);
308
+ }, 5000);
309
+ }
310
+ }, 0);
311
+ }
312
+
313
+ private normalizeEpochMs(epoch: number): number | null {
314
+ if (!epoch || Number.isNaN(epoch)) {
315
+ return null;
316
+ }
317
+ // Accept seconds or milliseconds since Unix epoch and normalize to ms for Date.now() comparison.
318
+ if (epoch < 1_000_000_000_000) {
319
+ return epoch * 1000;
320
+ }
321
+ return epoch;
322
+ }
323
+
324
+ private verifyJwt(token: string, publicKey: string) {
325
+ const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
326
+ return jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
327
+ }
328
+
329
+ private decodeAccessToken(authHeader: string, publicKey: string) {
330
+ const token = this.extractBearerToken(authHeader);
331
+ return this.verifyJwt(token, publicKey);
332
+ }
333
+
334
+ private extractBearerToken(authHeader: string) {
335
+ return authHeader.split(' ')[1];
336
+ }
154
337
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["jest", "node", "lru-cache"]
5
+ },
6
+ "include": ["src/**/*", "**/*.spec.ts"]
7
+ }