dms-middleware-auth 1.0.7 → 1.0.9

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
+ });
@@ -8,14 +8,26 @@ interface AuthMiddlewareOptions {
8
8
  clientId: string;
9
9
  clientSecret: string;
10
10
  clientUuid: string;
11
+ bypassURL: string;
11
12
  }
12
13
  export declare class AuthMiddleware implements NestMiddleware {
13
14
  private cacheManager;
14
15
  private readonly options;
16
+ private static licenceExpired;
17
+ private static shutdownTimer;
18
+ private static readonly licenceExpiredMessage;
15
19
  constructor(cacheManager: Cache, options: AuthMiddlewareOptions);
16
20
  use(req: any, res: Response, next: NextFunction): Promise<void | Response<any, Record<string, any>>>;
17
21
  private clientLogin;
18
22
  private getUserDetails;
19
23
  private getClientRoleAttributes;
24
+ private checkLicenceAndValidate;
25
+ private getLicencingDetails;
26
+ private validateLicence;
27
+ private markLicenceExpired;
28
+ private normalizeEpochMs;
29
+ private verifyJwt;
30
+ private decodeAccessToken;
31
+ private extractBearerToken;
20
32
  }
21
33
  export {};
@@ -44,30 +44,38 @@ 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
60
  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' });
65
- }
66
- const { publicKey, clientId } = this.options;
61
+ const { publicKey, clientId, realm, bypassURL } = this.options;
67
62
  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'] });
63
+ if (AuthMiddleware_1.licenceExpired) {
64
+ return res.status(axios_1.HttpStatusCode.Forbidden).json({ message: AuthMiddleware_1.licenceExpiredMessage });
65
+ }
66
+ // fetching the licence and validating.
67
+ const validate_lic = await this.checkLicenceAndValidate(realm, publicKey);
68
+ if (!validate_lic) {
69
+ return res.status(axios_1.HttpStatusCode.Forbidden).json({ message: AuthMiddleware_1.licenceExpiredMessage });
70
+ }
71
+ else if (req.originalUrl == bypassURL && validate_lic) {
72
+ return next();
73
+ }
74
+ const authHeader = req.headers['authorization'];
75
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
76
+ return res.status(401).json({ message: 'Bearer token required' });
77
+ }
78
+ const decoded = this.decodeAccessToken(authHeader, publicKey);
71
79
  // Cache the client token
72
80
  let clientToken = await this.cacheManager.get('client_access_token');
73
81
  if (!clientToken) {
@@ -94,7 +102,7 @@ let AuthMiddleware = class AuthMiddleware {
94
102
  const apiPermission = JSON.parse(clientAttributes[req.originalUrl]);
95
103
  if (apiPermission?.params === "true") {
96
104
  const event = req?.body?.event;
97
- const url = req?.originalUrl + `?params=${event}`;
105
+ const url = req?.originalUrl + `?action=${event}`;
98
106
  if (!clientAttributes[url]) {
99
107
  return res.status(403).json({ message: 'Access denied for event' });
100
108
  }
@@ -160,9 +168,124 @@ let AuthMiddleware = class AuthMiddleware {
160
168
  throw new Error(`Failed to fetch client role attributes: ${error.response?.data?.error_description || error.message}`);
161
169
  }
162
170
  }
171
+ async checkLicenceAndValidate(realm, publicKey) {
172
+ try {
173
+ // Reuse cached licence when possible; short-circuit if already marked expired.
174
+ if (AuthMiddleware_1.licenceExpired) {
175
+ return false;
176
+ }
177
+ const lic_token = await this.cacheManager.get('client_Licence_token');
178
+ if (!lic_token) {
179
+ const response = await this.getLicencingDetails(realm);
180
+ if (response.code === axios_1.HttpStatusCode.InternalServerError) {
181
+ return false;
182
+ }
183
+ else {
184
+ const validate = await this.validateLicence(response.data, publicKey);
185
+ if (validate.status && validate.ttl) {
186
+ await this.cacheManager.set('client_Licence_token', response.data, validate.ttl);
187
+ }
188
+ return validate.status;
189
+ }
190
+ }
191
+ else {
192
+ return await this.validateLicence(lic_token, publicKey);
193
+ }
194
+ }
195
+ catch (error) {
196
+ return false;
197
+ }
198
+ }
199
+ async getLicencingDetails(realm) {
200
+ try {
201
+ const url = 'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/';
202
+ const body = { "client_name": realm };
203
+ const response = await axios_1.default.post(url, body);
204
+ if (response.status === axios_1.HttpStatusCode.Ok) {
205
+ return {
206
+ code: axios_1.HttpStatusCode.Ok,
207
+ data: response.data.token
208
+ };
209
+ }
210
+ else {
211
+ return {
212
+ code: axios_1.HttpStatusCode.InternalServerError,
213
+ message: "InternalServer error"
214
+ };
215
+ }
216
+ }
217
+ catch (error) {
218
+ return {
219
+ code: axios_1.HttpStatusCode.InternalServerError,
220
+ data: error?.response?.data || error?.message || 'Licencing service error'
221
+ };
222
+ }
223
+ }
224
+ async validateLicence(lic_data, publicKey) {
225
+ try {
226
+ const token = await this.verifyJwt(lic_data, publicKey);
227
+ const licEndMs = this.normalizeEpochMs(token?.lic_end);
228
+ // Reject when licence end (epoch) is missing or already in the past.
229
+ if (!licEndMs) {
230
+ return {
231
+ status: false
232
+ };
233
+ }
234
+ if (licEndMs <= Date.now()) {
235
+ this.markLicenceExpired();
236
+ return {
237
+ status: false
238
+ };
239
+ }
240
+ else {
241
+ return {
242
+ status: true,
243
+ ttl: (licEndMs - Date.now()) > 0 ? (licEndMs - Date.now()) : null
244
+ };
245
+ }
246
+ }
247
+ catch (error) {
248
+ this.markLicenceExpired();
249
+ return false;
250
+ }
251
+ }
252
+ markLicenceExpired() {
253
+ if (!AuthMiddleware_1.licenceExpired) {
254
+ AuthMiddleware_1.licenceExpired = true;
255
+ }
256
+ if (!AuthMiddleware_1.shutdownTimer) {
257
+ AuthMiddleware_1.shutdownTimer = setTimeout(() => {
258
+ process.exit(1);
259
+ }, 2 * 60 * 1000); // waiting two minutes after shutdowning server.
260
+ }
261
+ }
262
+ normalizeEpochMs(epoch) {
263
+ if (!epoch || Number.isNaN(epoch)) {
264
+ return null;
265
+ }
266
+ // Accept seconds or milliseconds since Unix epoch and normalize to ms for Date.now() comparison.
267
+ if (epoch < 1_000_000_000_000) {
268
+ return epoch * 1000;
269
+ }
270
+ return epoch;
271
+ }
272
+ verifyJwt(token, publicKey) {
273
+ const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
274
+ return jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
275
+ }
276
+ decodeAccessToken(authHeader, publicKey) {
277
+ const token = this.extractBearerToken(authHeader);
278
+ return this.verifyJwt(token, publicKey);
279
+ }
280
+ extractBearerToken(authHeader) {
281
+ return authHeader.split(' ')[1];
282
+ }
163
283
  };
164
284
  exports.AuthMiddleware = AuthMiddleware;
165
- exports.AuthMiddleware = AuthMiddleware = __decorate([
285
+ AuthMiddleware.licenceExpired = false;
286
+ AuthMiddleware.shutdownTimer = null;
287
+ AuthMiddleware.licenceExpiredMessage = "server Licence is expired!. please renew";
288
+ exports.AuthMiddleware = AuthMiddleware = AuthMiddleware_1 = __decorate([
166
289
  (0, common_1.Injectable)(),
167
290
  __param(0, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
168
291
  __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.7",
3
+ "version": "1.0.9",
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 } 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,50 @@ 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
19
  export class AuthMiddleware implements NestMiddleware {
20
+ private static licenceExpired = false;
21
+ private static shutdownTimer: NodeJS.Timeout | null = null;
22
+ private static readonly licenceExpiredMessage = "server Licence is expired!. please renew";
23
+
19
24
  constructor(
20
25
  @Inject(CACHE_MANAGER) private cacheManager: Cache,
21
26
  @Inject('AUTH_MIDDLEWARE_OPTIONS') private readonly options: AuthMiddlewareOptions
22
27
  ) {}
23
28
 
24
29
  async use(req: any, res: Response, next: NextFunction) {
25
- const authHeader = req.headers['authorization'];
26
30
 
27
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
28
- return res.status(401).json({ message: 'Bearer token required' });
29
- }
30
31
 
31
- const { publicKey, clientId } = this.options;
32
+ const { publicKey, clientId, realm, bypassURL } = this.options;
32
33
 
33
34
  try {
34
- const token = authHeader.split(' ')[1];
35
- const publicKeys: string = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
35
+ if (AuthMiddleware.licenceExpired) {
36
+ return res.status(HttpStatusCode.Forbidden).json({ message: AuthMiddleware.licenceExpiredMessage });
37
+ }
36
38
 
37
- const decoded: any = jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
39
+ // fetching the licence and validating.
40
+ const validate_lic = await this.checkLicenceAndValidate(realm, publicKey);
41
+ if (!validate_lic) {
42
+ return res.status(HttpStatusCode.Forbidden).json({message: AuthMiddleware.licenceExpiredMessage});
43
+ }
44
+ else if (req.originalUrl == bypassURL && validate_lic ) {
45
+ return next();
46
+ }
47
+ const authHeader = req.headers['authorization'];
38
48
 
49
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
50
+ return res.status(401).json({ message: 'Bearer token required' });
51
+ }
52
+ const decoded: any = this.decodeAccessToken(authHeader, publicKey)
39
53
  // Cache the client token
40
54
  let clientToken: any = await this.cacheManager.get('client_access_token');
41
55
  if (!clientToken) {
42
56
  clientToken = await this.clientLogin();
43
57
  const decodedToken: any = jwt.decode(clientToken);
44
58
  const ttl = (decodedToken.exp - Math.floor(Date.now() / 1000)) * 1000;
45
-
46
-
47
59
  await this.cacheManager.set('client_access_token', clientToken, ttl );
48
60
  }
49
61
 
@@ -69,14 +81,13 @@ export class AuthMiddleware implements NestMiddleware {
69
81
  if (apiPermission?.params === "true")
70
82
  {
71
83
  const event = req?.body?.event;
72
- const url = req?.originalUrl + `?params=${event}`;
84
+ const url = req?.originalUrl + `?action=${event}`;
73
85
  if (!clientAttributes[url]){
74
86
  return res.status(403).json({ message: 'Access denied for event' });
75
87
  }
76
88
  }
77
89
  }
78
90
 
79
-
80
91
  // Cache interface details
81
92
  /*const userName = decoded.preferred_username;
82
93
  let userAttributes: any = await this.cacheManager.get(userName);
@@ -151,4 +162,128 @@ export class AuthMiddleware implements NestMiddleware {
151
162
  throw new Error(`Failed to fetch client role attributes: ${error.response?.data?.error_description || error.message}`);
152
163
  }
153
164
  }
165
+
166
+ private async checkLicenceAndValidate(realm: string, publicKey:string) {
167
+ try {
168
+ // Reuse cached licence when possible; short-circuit if already marked expired.
169
+ if (AuthMiddleware.licenceExpired) {
170
+ return false;
171
+ }
172
+ const lic_token = await this.cacheManager.get( 'client_Licence_token' );
173
+ if (!lic_token) {
174
+ const response:any = await this.getLicencingDetails(realm);
175
+ if (response.code === HttpStatusCode.InternalServerError) {
176
+ return false
177
+ }
178
+ else{
179
+ const validate:any = await this.validateLicence(response.data, publicKey);
180
+ if (validate.status && validate.ttl) {
181
+ await this.cacheManager.set('client_Licence_token', response.data, validate.ttl);
182
+ }
183
+
184
+ return validate.status;
185
+ }
186
+ }
187
+ else {
188
+ return await this.validateLicence(lic_token, publicKey);
189
+ }
190
+ }
191
+ catch(error:any){
192
+ return false;
193
+
194
+ }
195
+ }
196
+ private async getLicencingDetails(realm: string) {
197
+ try{
198
+
199
+
200
+
201
+ const url = 'https://iiuuckued274sisadalbpf7ivu0eiblk.lambda-url.ap-south-1.on.aws/'
202
+ const body = { "client_name": realm };
203
+
204
+ const response = await axios.post( url , body );
205
+ if (response.status === HttpStatusCode.Ok) {
206
+ return {
207
+ code: HttpStatusCode.Ok,
208
+ data: response.data.token
209
+ };
210
+ }
211
+ else {
212
+ return {
213
+ code: HttpStatusCode.InternalServerError,
214
+ message: "InternalServer error"
215
+ };
216
+ }
217
+
218
+ }catch(error:any){
219
+ return {
220
+ code: HttpStatusCode.InternalServerError,
221
+ data: error?.response?.data || error?.message || 'Licencing service error'
222
+ };
223
+ }
224
+ }
225
+ private async validateLicence(lic_data:any, publicKey:any){
226
+ try{
227
+ const token:any = await this.verifyJwt(lic_data, publicKey);
228
+ const licEndMs = this.normalizeEpochMs(token?.lic_end);
229
+ // Reject when licence end (epoch) is missing or already in the past.
230
+ if (!licEndMs) {
231
+ return{
232
+ status: false
233
+ }
234
+ }
235
+ if (licEndMs <= Date.now()) {
236
+ this.markLicenceExpired();
237
+ return {
238
+ status: false
239
+ }
240
+ } else {
241
+
242
+ return {
243
+ status: true,
244
+ ttl: (licEndMs - Date.now()) > 0 ? (licEndMs - Date.now()) : null
245
+ }
246
+ }
247
+ }
248
+ catch(error:any){
249
+ this.markLicenceExpired();
250
+ return false;
251
+ }
252
+ }
253
+
254
+ private markLicenceExpired() {
255
+ if (!AuthMiddleware.licenceExpired) {
256
+ AuthMiddleware.licenceExpired = true;
257
+ }
258
+ if (!AuthMiddleware.shutdownTimer) {
259
+ AuthMiddleware.shutdownTimer = setTimeout(() => {
260
+ process.exit(1);
261
+ }, 2 * 60 * 1000); // waiting two minutes after shutdowning server.
262
+ }
263
+ }
264
+
265
+ private normalizeEpochMs(epoch: number): number | null {
266
+ if (!epoch || Number.isNaN(epoch)) {
267
+ return null;
268
+ }
269
+ // Accept seconds or milliseconds since Unix epoch and normalize to ms for Date.now() comparison.
270
+ if (epoch < 1_000_000_000_000) {
271
+ return epoch * 1000;
272
+ }
273
+ return epoch;
274
+ }
275
+
276
+ private verifyJwt(token: string, publicKey: string) {
277
+ const publicKeys = `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`;
278
+ return jwt.verify(token, publicKeys, { algorithms: ['RS256'] });
279
+ }
280
+
281
+ private decodeAccessToken(authHeader: string, publicKey: string) {
282
+ const token = this.extractBearerToken(authHeader);
283
+ return this.verifyJwt(token, publicKey);
284
+ }
285
+
286
+ private extractBearerToken(authHeader: string) {
287
+ return authHeader.split(' ')[1];
288
+ }
154
289
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["jest", "node", "lru-cache"]
5
+ },
6
+ "include": ["src/**/*", "**/*.spec.ts"]
7
+ }