dms-middleware-auth 1.1.0 → 1.1.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.
@@ -4,7 +4,7 @@ import { HttpStatusCode } from 'axios';
4
4
  const baseOptions = {
5
5
  publicKey: 'PUBLIC_KEY',
6
6
  keycloakUrl: '',
7
- realm: '',
7
+ realm: 'my-realm',
8
8
  clientId: 'client',
9
9
  clientSecret: '',
10
10
  clientUuid: '',
@@ -29,94 +29,92 @@ const createRes = () => {
29
29
  };
30
30
 
31
31
  const createReq = (override: Record<string, any> = {}) =>
32
- ({
33
- headers: { authorization: 'Bearer access-token' },
34
- originalUrl: '/route',
35
- body: {},
36
- ...override,
37
- } as any);
32
+ ({
33
+ headers: { authorization: 'Bearer access-token' },
34
+ originalUrl: '/route',
35
+ body: {},
36
+ ...override,
37
+ } as any);
38
38
 
39
39
  describe('AuthMiddleware licensing', () => {
40
+ let cache: any;
41
+ let middleware: AuthMiddleware;
42
+
40
43
  beforeEach(() => {
41
44
  jest.restoreAllMocks();
42
45
  jest.clearAllMocks();
43
46
  jest.useRealTimers();
47
+
44
48
  // Reset static flags/timers between tests
45
49
  const cls: any = AuthMiddleware;
46
- if (cls.shutdownTimer) {
47
- clearTimeout(cls.shutdownTimer);
48
- }
50
+ if (cls.shutdownTimer) clearTimeout(cls.shutdownTimer);
51
+ if (cls.licenceExpiryTimer) clearTimeout(cls.licenceExpiryTimer);
49
52
  cls.licenceExpired = false;
50
53
  cls.shutdownTimer = null;
54
+ cls.licenceExpiryTimer = null;
55
+ cls.licenceValidatedUntilMs = null;
56
+ cls.shutdownInitiated = false;
57
+
58
+ cache = createCacheMock();
59
+ middleware = new AuthMiddleware(cache as any, baseOptions as any);
51
60
  });
52
61
 
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);
62
+ it('checks the licence in cache first', async () => {
63
+ cache.get.mockResolvedValueOnce('cached-token');
64
+ const verifySpy = jest.spyOn(middleware as any, 'verifyJwt').mockReturnValue({
65
+ lic_end: Math.floor(Date.now() / 1000) + 3600, // Valid for 1 hour
66
+ });
67
+ const fetchSpy = jest.spyOn(middleware as any, 'getLicencingDetails');
68
+
69
+ const result = await (middleware as any).checkLicenceAndValidate('my-realm', 'PUBLIC_KEY');
70
+
71
+ expect(result).toBe(true);
72
+ expect(cache.get).toHaveBeenCalledWith('client_Licence_token');
73
+ expect(verifySpy).toHaveBeenCalledWith('cached-token', 'PUBLIC_KEY');
74
+ expect(fetchSpy).not.toHaveBeenCalled();
75
+ });
56
76
 
77
+ it('fetches licence from server if not in cache', async () => {
78
+ cache.get.mockResolvedValueOnce(null);
57
79
  jest.spyOn(middleware as any, 'getLicencingDetails').mockResolvedValue({
58
80
  code: HttpStatusCode.Ok,
59
- data: 'licence-token',
81
+ data: 'server-token',
60
82
  });
61
83
  jest.spyOn(middleware as any, 'verifyJwt').mockReturnValue({
62
- lic_end: Math.floor(Date.now() / 1000) + 60, // seconds epoch
84
+ lic_end: Math.floor(Date.now() / 1000) + 3600,
63
85
  });
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
86
 
78
- await middleware.use(req, res as any, next);
87
+ const result = await (middleware as any).checkLicenceAndValidate('my-realm', 'PUBLIC_KEY');
79
88
 
80
- expect(res.status).not.toHaveBeenCalled();
81
- expect(next).toHaveBeenCalledTimes(1);
82
- expect(cache.set).toHaveBeenCalledWith('client_Licence_token', 'licence-token');
89
+ expect(result).toBe(true);
90
+ expect(cache.set).toHaveBeenCalledWith('client_Licence_token', 'server-token', 0);
83
91
  });
84
92
 
85
- it('rejects and schedules shutdown when licence is expired', async () => {
93
+ it('schedules licence expiration correctly but DOES NOT shutdown immediately', async () => {
86
94
  jest.useFakeTimers();
87
- const cache = createCacheMock({ client_access_token: 'cached-client-token' });
88
- const middleware = new AuthMiddleware(cache as any, baseOptions as any);
89
-
95
+ cache.get.mockResolvedValueOnce(null);
90
96
  jest.spyOn(middleware as any, 'getLicencingDetails').mockResolvedValue({
91
97
  code: HttpStatusCode.Ok,
92
- data: 'licence-token',
98
+ data: 'server-token',
93
99
  });
94
100
  jest.spyOn(middleware as any, 'verifyJwt').mockReturnValue({
95
- lic_end: Date.now() - 1, // already expired (ms epoch)
101
+ lic_end: Date.now() + 1000, // Expires in 1 second
96
102
  });
97
- const exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => undefined) as any);
98
103
 
99
- const req = createReq();
100
- const res = createRes();
101
- const next = jest.fn();
104
+ await (middleware as any).checkLicenceAndValidate('my-realm', 'PUBLIC_KEY');
102
105
 
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();
106
+ // Fast forward to expiry
107
+ jest.advanceTimersByTime(1001);
108
108
  expect((AuthMiddleware as any).licenceExpired).toBe(true);
109
109
 
110
- // advance time to trigger shutdown timer
111
- jest.advanceTimersByTime(2 * 60 * 1000);
112
- expect(exitSpy).toHaveBeenCalledWith(1);
110
+ // Should NOT have initiated shutdown yet (no request made after expiry)
111
+ expect((AuthMiddleware as any).shutdownInitiated).toBe(false);
113
112
  });
114
113
 
115
- it('immediately rejects when already marked expired', async () => {
116
- const cache = createCacheMock();
114
+ it('shuts down on the next request after licence expiry', async () => {
115
+ jest.useFakeTimers();
117
116
  (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');
117
+ const stopSpy = jest.spyOn(middleware as any, 'stopServer');
120
118
 
121
119
  const req = createReq();
122
120
  const res = createRes();
@@ -124,8 +122,18 @@ describe('AuthMiddleware licensing', () => {
124
122
 
125
123
  await middleware.use(req, res as any, next);
126
124
 
127
- expect(checkSpy).not.toHaveBeenCalled();
128
125
  expect(res.status).toHaveBeenCalledWith(HttpStatusCode.Forbidden);
129
- expect(next).not.toHaveBeenCalled();
126
+ expect(res.json).toHaveBeenCalledWith({ message: (AuthMiddleware as any).licenceExpiredMessage });
127
+ expect(stopSpy).toHaveBeenCalled();
128
+ });
129
+
130
+ it('fails if server is invalid and no cache', async () => {
131
+ cache.get.mockResolvedValueOnce(null);
132
+ jest.spyOn(middleware as any, 'getLicencingDetails').mockResolvedValue({
133
+ code: HttpStatusCode.InternalServerError,
134
+ });
135
+
136
+ const result = await (middleware as any).checkLicenceAndValidate('my-realm', 'PUBLIC_KEY');
137
+ expect(result).toBe(false);
130
138
  });
131
139
  });
@@ -33,6 +33,7 @@ export declare class AuthMiddleware implements NestMiddleware, OnModuleInit {
33
33
  private scheduleLicenceShutdown;
34
34
  private stopServer;
35
35
  private normalizeEpochMs;
36
+ private getUtcNowMs;
36
37
  private verifyJwt;
37
38
  private decodeAccessToken;
38
39
  private extractBearerToken;
@@ -111,29 +111,13 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
111
111
  }
112
112
  }
113
113
  }
114
- // Cache interface details
115
- /*const userName = decoded.preferred_username;
116
- let userAttributes: any = await this.cacheManager.get(userName);
117
- if (!userAttributes) {
118
- userAttributes = await this.getUserDetails(userName, clientToken);
119
- userAttributes = JSON.stringify(userAttributes.attributes);
120
- await this.cacheManager.set(userName, userAttributes, 0);
121
- }
122
-
123
- // Attach attributes to request
124
- userAttributes = JSON.parse(userAttributes);*/
125
114
  req['user'] = {
126
115
  role: role,
127
116
  userName: decoded.preferred_username
128
117
  };
129
- // req['attributes'] = {
130
- // client: clientAttributes[req.originalUrl],
131
- // // user: userAttributes,
132
- // };
133
118
  return next();
134
119
  }
135
120
  catch (error) {
136
- // console.error('AuthMiddleware error:', error);
137
121
  return res.status(500).json({ message: error.message });
138
122
  }
139
123
  }
@@ -176,19 +160,29 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
176
160
  if (AuthMiddleware_1.licenceExpired) {
177
161
  return false;
178
162
  }
179
- if (AuthMiddleware_1.licenceValidatedUntilMs && AuthMiddleware_1.licenceValidatedUntilMs > Date.now()) {
163
+ // 1. check the licence is available in cache if available then check the expiry
164
+ const cachedToken = await this.cacheManager.get('client_Licence_token');
165
+ if (cachedToken) {
166
+ const validate = await this.validateLicence(cachedToken, publicKey, false);
167
+ if (validate.status) {
168
+ return true;
169
+ }
170
+ // If cached token is invalid/expired, we'll try to get a new one from the server.
171
+ }
172
+ if (AuthMiddleware_1.licenceValidatedUntilMs && AuthMiddleware_1.licenceValidatedUntilMs > this.getUtcNowMs()) {
180
173
  return true;
181
174
  }
182
175
  if (AuthMiddleware_1.licenceValidationPromise) {
183
176
  return await AuthMiddleware_1.licenceValidationPromise;
184
177
  }
178
+ // 2. if the licence details not available in cache get the data from another server and validate.
185
179
  AuthMiddleware_1.licenceValidationPromise = (async () => {
186
180
  const response = await this.getLicencingDetails(realm);
187
181
  if (response.code === axios_1.HttpStatusCode.InternalServerError) {
188
182
  return false;
189
183
  }
190
184
  else {
191
- const validate = await this.validateLicence(response.data, publicKey);
185
+ const validate = await this.validateLicence(response.data, publicKey, true);
192
186
  return validate.status;
193
187
  }
194
188
  })();
@@ -228,57 +222,49 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
228
222
  };
229
223
  }
230
224
  }
231
- async validateLicence(lic_data, publicKey) {
225
+ async validateLicence(lic_data, publicKey, updateCache = true) {
232
226
  try {
233
227
  const token = await this.verifyJwt(lic_data, publicKey);
234
228
  const licEndMs = this.normalizeEpochMs(token?.lic_end);
235
229
  // Reject when licence end (epoch) is missing or already in the past.
236
- if (!licEndMs) {
230
+ if (!licEndMs || licEndMs <= this.getUtcNowMs()) {
237
231
  this.markLicenceExpired();
238
- return {
239
- status: false
240
- };
241
- }
242
- if (licEndMs <= Date.now()) {
243
- this.markLicenceExpired();
244
- return {
245
- status: false
246
- };
232
+ return { status: false };
247
233
  }
248
- else {
249
- AuthMiddleware_1.licenceValidatedUntilMs = licEndMs;
250
- this.scheduleLicenceShutdown(licEndMs);
251
- return {
252
- status: true,
253
- ttl: (licEndMs - Date.now()) > 0 ? (licEndMs - Date.now()) : null
254
- };
234
+ // 3. if the licencing server have the future expiry time wait till the expiry time
235
+ AuthMiddleware_1.licenceValidatedUntilMs = licEndMs;
236
+ this.scheduleLicenceShutdown(licEndMs);
237
+ if (updateCache) {
238
+ const ttl = (licEndMs - this.getUtcNowMs()) > 0 ? (licEndMs - this.getUtcNowMs()) : null;
239
+ await this.cacheManager.set('client_Licence_token', lic_data, ttl);
255
240
  }
241
+ return {
242
+ status: true,
243
+ };
256
244
  }
257
245
  catch (error) {
258
246
  this.markLicenceExpired();
259
- return {
260
- status: false
261
- };
247
+ return { status: false };
262
248
  }
263
249
  }
264
250
  markLicenceExpired() {
265
251
  if (!AuthMiddleware_1.licenceExpired) {
266
252
  AuthMiddleware_1.licenceExpired = true;
267
253
  }
268
- // this.stopServer();
269
254
  }
270
255
  scheduleLicenceShutdown(licEndMs) {
271
256
  if (!licEndMs) {
272
257
  return;
273
258
  }
274
- const delay = licEndMs - Date.now();
259
+ const delay = licEndMs - this.getUtcNowMs();
275
260
  if (delay <= 0) {
276
261
  this.markLicenceExpired();
277
262
  return;
278
263
  }
279
264
  if (AuthMiddleware_1.licenceExpiryTimer) {
280
- return;
265
+ clearTimeout(AuthMiddleware_1.licenceExpiryTimer);
281
266
  }
267
+ // and then shutdown for till next user request to know the user to respond and shutdown the server.
282
268
  AuthMiddleware_1.licenceExpiryTimer = setTimeout(() => {
283
269
  this.markLicenceExpired();
284
270
  }, delay);
@@ -306,12 +292,14 @@ let AuthMiddleware = AuthMiddleware_1 = class AuthMiddleware {
306
292
  if (!epoch || Number.isNaN(epoch)) {
307
293
  return null;
308
294
  }
309
- // Accept seconds or milliseconds since Unix epoch and normalize to ms for Date.now() comparison.
310
295
  if (epoch < 1_000_000_000_000) {
311
296
  return epoch * 1000;
312
297
  }
313
298
  return epoch;
314
299
  }
300
+ getUtcNowMs() {
301
+ return Date.now();
302
+ }
315
303
  verifyJwt(token, publicKey) {
316
304
  const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
317
305
  return jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dms-middleware-auth",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
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",
@@ -29,4 +29,4 @@
29
29
  "ts-jest": "^29.3.4",
30
30
  "typescript": "^5.7.2"
31
31
  }
32
- }
32
+ }
@@ -5,6 +5,7 @@ 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
9
  interface AuthMiddlewareOptions {
9
10
  publicKey: string;
10
11
  keycloakUrl: string;
@@ -28,51 +29,49 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
28
29
  constructor(
29
30
  @Inject(CACHE_MANAGER) private cacheManager: Cache,
30
31
  @Inject('AUTH_MIDDLEWARE_OPTIONS') private readonly options: AuthMiddlewareOptions
31
- ) {}
32
+ ) { }
32
33
 
33
34
  async onModuleInit() {
34
35
  const { publicKey, realm } = this.options;
35
36
  const isValid = await this.checkLicenceAndValidate(realm, publicKey);
36
-
37
37
  }
38
38
 
39
39
  async use(req: any, res: Response, next: NextFunction) {
40
-
41
-
42
40
  const { publicKey, clientId, realm, bypassURL } = this.options;
43
41
 
44
42
  try {
45
- if (AuthMiddleware.licenceExpired) {
46
-
43
+ if (AuthMiddleware.licenceExpired) {
47
44
  this.stopServer();
48
-
49
45
  return res.status(HttpStatusCode.Forbidden).json({ message: AuthMiddleware.licenceExpiredMessage });
50
46
  }
51
47
 
52
48
  if (!AuthMiddleware.licenceValidatedUntilMs) {
53
49
  return res.status(HttpStatusCode.Forbidden).json({ message: AuthMiddleware.licenceExpiredMessage });
54
50
  }
51
+
55
52
  if (req.originalUrl == bypassURL) {
56
53
  return next();
57
54
  }
58
- const authHeader = req.headers['authorization'];
59
55
 
56
+ const authHeader = req.headers['authorization'];
60
57
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
61
58
  return res.status(401).json({ message: 'Bearer token required' });
62
59
  }
63
- const decoded: any = this.decodeAccessToken(authHeader, publicKey)
60
+
61
+ const decoded: any = this.decodeAccessToken(authHeader, publicKey);
62
+
64
63
  // Cache the client token
65
64
  let clientToken: any = await this.cacheManager.get('client_access_token');
66
65
  if (!clientToken) {
67
66
  clientToken = await this.clientLogin();
68
67
  const decodedToken: any = jwt.decode(clientToken);
69
68
  const ttl = (decodedToken.exp - Math.floor(Date.now() / 1000)) * 1000;
70
- await this.cacheManager.set('client_access_token', clientToken, ttl );
69
+ await this.cacheManager.set('client_access_token', clientToken, ttl);
71
70
  }
72
71
 
73
72
  // Cache client role attributes
74
73
  const role = decoded.resource_access[clientId]?.roles?.[0];
75
- const clientRoleName = `${clientId+role}`;
74
+ const clientRoleName = `${clientId + role}`;
76
75
  let clientAttributes: any = await this.cacheManager.get(clientRoleName);
77
76
  if (!clientAttributes) {
78
77
  clientAttributes = await this.getClientRoleAttributes(role, clientToken);
@@ -82,54 +81,32 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
82
81
 
83
82
  // Check route access
84
83
  clientAttributes = JSON.parse(clientAttributes);
85
-
86
84
  if (!clientAttributes[req.originalUrl]) {
87
-
88
85
  return res.status(403).json({ message: 'Access denied for this route' });
89
- }
90
- else {
91
- const apiPermission = JSON.parse(clientAttributes[req.originalUrl]);
92
- if (apiPermission?.params === "true")
93
- {
86
+ } else {
87
+ const apiPermission = JSON.parse(clientAttributes[req.originalUrl]);
88
+ if (apiPermission?.params === "true") {
94
89
  const event = req?.body?.event;
95
90
  const url = req?.originalUrl + `?action=${event}`;
96
- if (!clientAttributes[url]){
91
+ if (!clientAttributes[url]) {
97
92
  return res.status(403).json({ message: 'Access denied for event' });
98
93
  }
99
94
  }
100
95
  }
101
96
 
102
- // Cache interface details
103
- /*const userName = decoded.preferred_username;
104
- let userAttributes: any = await this.cacheManager.get(userName);
105
- if (!userAttributes) {
106
- userAttributes = await this.getUserDetails(userName, clientToken);
107
- userAttributes = JSON.stringify(userAttributes.attributes);
108
- await this.cacheManager.set(userName, userAttributes, 0);
109
- }
110
-
111
- // Attach attributes to request
112
- userAttributes = JSON.parse(userAttributes);*/
113
97
  req['user'] = {
114
98
  role: role,
115
- userName:decoded.preferred_username
99
+ userName: decoded.preferred_username
116
100
  };
117
101
 
118
- // req['attributes'] = {
119
- // client: clientAttributes[req.originalUrl],
120
- // // user: userAttributes,
121
- // };
122
-
123
102
  return next();
124
- } catch (error:any) {
125
- // console.error('AuthMiddleware error:', error);
103
+ } catch (error: any) {
126
104
  return res.status(500).json({ message: error.message });
127
105
  }
128
106
  }
129
107
 
130
108
  private async clientLogin() {
131
109
  const { keycloakUrl, realm, clientId, clientSecret } = this.options;
132
-
133
110
  try {
134
111
  const response = await axios.post(
135
112
  `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`,
@@ -141,130 +118,133 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
141
118
  { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
142
119
  );
143
120
  return response.data.access_token;
144
- } catch (error:any) {
121
+ } catch (error: any) {
145
122
  throw new Error(`Failed to obtain client token: ${error.response?.data?.error_description || error.message}`);
146
123
  }
147
124
  }
148
125
 
149
126
  private async getUserDetails(username: string, token: string) {
150
127
  const { keycloakUrl, realm } = this.options;
151
-
152
128
  try {
153
129
  const response = await axios.get(
154
130
  `${keycloakUrl}/admin/realms/${realm}/users?username=${username}`,
155
131
  { headers: { Authorization: `Bearer ${token}` } }
156
132
  );
157
133
  return response.data[0];
158
- } catch (error:any) {
134
+ } catch (error: any) {
159
135
  throw new Error(`Failed to fetch user details: ${error.response?.data?.error_description || error.message}`);
160
136
  }
161
137
  }
162
138
 
163
139
  private async getClientRoleAttributes(role: string, token: string) {
164
140
  const { keycloakUrl, realm, clientUuid } = this.options;
165
-
166
141
  try {
167
142
  const response = await axios.get(
168
143
  `${keycloakUrl}/admin/realms/${realm}/clients/${clientUuid}/roles/${role}`,
169
144
  { headers: { Authorization: `Bearer ${token}` } }
170
145
  );
171
146
  return response.data;
172
- } catch (error:any) {
147
+ } catch (error: any) {
173
148
  throw new Error(`Failed to fetch client role attributes: ${error.response?.data?.error_description || error.message}`);
174
149
  }
175
150
  }
176
151
 
177
- private async checkLicenceAndValidate(realm: string, publicKey:string) {
152
+ private async checkLicenceAndValidate(realm: string, publicKey: string) {
178
153
  try {
179
154
  if (AuthMiddleware.licenceExpired) {
180
155
  return false;
181
156
  }
182
- if (AuthMiddleware.licenceValidatedUntilMs && AuthMiddleware.licenceValidatedUntilMs > Date.now()) {
157
+
158
+ // 1. check the licence is available in cache if available then check the expiry
159
+ const cachedToken: string | undefined = await this.cacheManager.get('client_Licence_token');
160
+ if (cachedToken) {
161
+ const validate = await this.validateLicence(cachedToken, publicKey, false);
162
+ if (validate.status) {
163
+ return true;
164
+ }
165
+ // If cached token is invalid/expired, we'll try to get a new one from the server.
166
+ }
167
+
168
+ if (AuthMiddleware.licenceValidatedUntilMs && AuthMiddleware.licenceValidatedUntilMs > this.getUtcNowMs()) {
183
169
  return true;
184
170
  }
171
+
185
172
  if (AuthMiddleware.licenceValidationPromise) {
186
173
  return await AuthMiddleware.licenceValidationPromise;
187
174
  }
175
+
176
+ // 2. if the licence details not available in cache get the data from another server and validate.
188
177
  AuthMiddleware.licenceValidationPromise = (async () => {
189
- const response:any = await this.getLicencingDetails(realm);
178
+ const response: any = await this.getLicencingDetails(realm);
190
179
  if (response.code === HttpStatusCode.InternalServerError) {
191
- return false
192
- }
193
- else{
194
- const validate = await this.validateLicence(response.data, publicKey);
180
+ return false;
181
+ } else {
182
+ const validate = await this.validateLicence(response.data, publicKey, true);
195
183
  return validate.status;
196
184
  }
197
185
  })();
186
+
198
187
  try {
199
188
  return await AuthMiddleware.licenceValidationPromise;
200
189
  } finally {
201
190
  AuthMiddleware.licenceValidationPromise = null;
202
191
  }
203
- }
204
- catch(error:any){
192
+ } catch (error: any) {
205
193
  return false;
206
-
207
194
  }
208
195
  }
209
- private async getLicencingDetails(realm: string) {
210
- try{
211
196
 
212
-
213
-
214
- const url = 'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/'
215
- const body = { "client_name": realm };
216
-
217
- const response = await axios.post( url , body );
218
- if (response.status === HttpStatusCode.Ok) {
219
- return {
220
- code: HttpStatusCode.Ok,
221
- data: response.data.token
222
- };
223
- }
224
- else {
225
- return {
226
- code: HttpStatusCode.InternalServerError,
227
- message: "InternalServer error"
228
- };
229
- }
230
-
231
- }catch(error:any){
197
+ private async getLicencingDetails(realm: string) {
198
+ try {
199
+ const url = 'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/';
200
+ const body = { "client_name": realm };
201
+ const response = await axios.post(url, body);
202
+ if (response.status === HttpStatusCode.Ok) {
203
+ return {
204
+ code: HttpStatusCode.Ok,
205
+ data: response.data.token
206
+ };
207
+ } else {
208
+ return {
209
+ code: HttpStatusCode.InternalServerError,
210
+ message: "InternalServer error"
211
+ };
212
+ }
213
+ } catch (error: any) {
232
214
  return {
233
215
  code: HttpStatusCode.InternalServerError,
234
216
  data: error?.response?.data || error?.message || 'Licencing service error'
235
217
  };
236
218
  }
237
219
  }
238
- private async validateLicence(lic_data:any, publicKey:any){
239
- try{
240
- const token:any = await this.verifyJwt(lic_data, publicKey);
220
+
221
+ private async validateLicence(lic_data: any, publicKey: any, updateCache = true) {
222
+ try {
223
+ const token: any = await this.verifyJwt(lic_data, publicKey);
241
224
  const licEndMs = this.normalizeEpochMs(token?.lic_end);
225
+
242
226
  // Reject when licence end (epoch) is missing or already in the past.
243
- if (!licEndMs) {
227
+ if (!licEndMs || licEndMs <= this.getUtcNowMs()) {
244
228
  this.markLicenceExpired();
245
- return{
246
- status: false
247
- }
229
+ return { status: false };
248
230
  }
249
- if (licEndMs <= Date.now()) {
250
- this.markLicenceExpired();
251
- return {
252
- status: false
253
- }
254
- } else {
255
- AuthMiddleware.licenceValidatedUntilMs = licEndMs;
256
- this.scheduleLicenceShutdown(licEndMs);
257
- return {
258
- status: true,
259
- ttl: (licEndMs - Date.now()) > 0 ? (licEndMs - Date.now()) : null
260
- }
231
+
232
+ // 3. if the licencing server have the future expiry time wait till the expiry time
233
+ AuthMiddleware.licenceValidatedUntilMs = licEndMs;
234
+ this.scheduleLicenceShutdown(licEndMs);
235
+
236
+ if (updateCache) {
237
+ const ttl: any = (licEndMs - this.getUtcNowMs()) > 0 ? (licEndMs - this.getUtcNowMs()) : null
238
+ await this.cacheManager.set('client_Licence_token', lic_data, ttl);
261
239
  }
262
- }
263
- catch(error:any){
264
- this.markLicenceExpired();
240
+
265
241
  return {
266
- status: false
242
+ status: true,
243
+
267
244
  };
245
+ } catch (error: any) {
246
+ this.markLicenceExpired();
247
+ return { status: false };
268
248
  }
269
249
  }
270
250
 
@@ -272,21 +252,21 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
272
252
  if (!AuthMiddleware.licenceExpired) {
273
253
  AuthMiddleware.licenceExpired = true;
274
254
  }
275
- // this.stopServer();
276
255
  }
277
256
 
278
257
  private scheduleLicenceShutdown(licEndMs: number) {
279
258
  if (!licEndMs) {
280
259
  return;
281
260
  }
282
- const delay = licEndMs - Date.now();
261
+ const delay = licEndMs - this.getUtcNowMs();
283
262
  if (delay <= 0) {
284
263
  this.markLicenceExpired();
285
264
  return;
286
265
  }
287
266
  if (AuthMiddleware.licenceExpiryTimer) {
288
- return;
267
+ clearTimeout(AuthMiddleware.licenceExpiryTimer);
289
268
  }
269
+ // and then shutdown for till next user request to know the user to respond and shutdown the server.
290
270
  AuthMiddleware.licenceExpiryTimer = setTimeout(() => {
291
271
  this.markLicenceExpired();
292
272
  }, delay);
@@ -315,13 +295,16 @@ export class AuthMiddleware implements NestMiddleware, OnModuleInit {
315
295
  if (!epoch || Number.isNaN(epoch)) {
316
296
  return null;
317
297
  }
318
- // Accept seconds or milliseconds since Unix epoch and normalize to ms for Date.now() comparison.
319
298
  if (epoch < 1_000_000_000_000) {
320
299
  return epoch * 1000;
321
300
  }
322
301
  return epoch;
323
302
  }
324
303
 
304
+ private getUtcNowMs() {
305
+ return Date.now();
306
+ }
307
+
325
308
  private verifyJwt(token: string, publicKey: string) {
326
309
  const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
327
310
  return jwt.verify(token, publicKeys, { algorithms: ['RS256'] });