soajs.controller 4.2.40 → 4.2.41

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/README.md CHANGED
@@ -35,6 +35,91 @@ Enabling CORS allows different domains to communicate with SOAJS via its gateway
35
35
 
36
36
  ---
37
37
 
38
+ ## Environment Variables
39
+
40
+ The SOAJS Controller can be configured using the following environment variables:
41
+
42
+ ### Core Configuration
43
+
44
+ | Variable | Type | Default | Description |
45
+ |----------|------|---------|-------------|
46
+ | `SOAJS_ENV` | string | `"dev"` | Environment code (e.g., dev, staging, production) |
47
+ | `SOAJS_PROFILE` | string | `./profiles/single.js` (mongo mode)<br>`./profiles/solo.js` (solo mode) | Path to the SOAJS profile configuration file |
48
+ | `SOAJS_SOLO` | boolean | `false` | Enable solo mode (uses local registry instead of MongoDB) |
49
+ | `SOAJS_SRVIP` | string | auto-detected | Service IP address for registration |
50
+ | `SOAJS_SRV_AUTOREGISTERHOST` | boolean | `true` | Automatically register host with the service |
51
+
52
+ ### Deployment & High Availability
53
+
54
+ | Variable | Type | Default | Description |
55
+ |----------|------|---------|-------------|
56
+ | `SOAJS_DEPLOY_HA` | string | - | High availability deployment type (`kubernetes`, `swarm`, or empty for non-HA) |
57
+ | `SOAJS_DEPLOY_MANUAL` | number | - | Manual deployment mode flag (1 = enabled) |
58
+ | `SOAJS_MANUAL` | boolean | - | Skip automatic deployment operations |
59
+ | `SOAJS_SWARM_UNIX_PORT` | string | `/var/run/docker.sock` | Docker Swarm Unix socket path |
60
+
61
+ ### Security & Tenant Configuration
62
+
63
+ | Variable | Type | Default | Description |
64
+ |----------|------|---------|-------------|
65
+ | `SOAJS_SENSITIVE_ENVS` | JSON array | `["dashboard"]` | List of sensitive environment codes requiring additional security checks |
66
+
67
+ ### Performance & Resource Management
68
+
69
+ | Variable | Type | Default | Description |
70
+ |----------|------|---------|-------------|
71
+ | `SOAJS_MAX_BODY_SIZE` | number | `10485760` (10MB) | Maximum request body size in bytes for monitoring. Prevents memory exhaustion from large request bodies |
72
+ | `SOAJS_THROTTLE_CLEANUP_INTERVAL` | number | `60000` (1 min) | Interval in milliseconds for cleaning up expired throttling cache entries |
73
+
74
+ ### Database & Connection
75
+
76
+ | Variable | Type | Default | Description |
77
+ |----------|------|---------|-------------|
78
+ | `SOAJS_MONGO_CON_KEEPALIVE` | boolean | `false` | Enable MongoDB connection keepalive |
79
+
80
+ ### Testing & Development
81
+
82
+ | Variable | Type | Default | Description |
83
+ |----------|------|---------|-------------|
84
+ | `SOAJS_TEST` | boolean | - | Enable test mode (affects registry loading and service queries) |
85
+ | `SOAJS_IMPORTER_DROPDB` | boolean | `false` | Drop database before importing (test data only) |
86
+ | `NODE_ENV` | string | - | Node.js environment mode (development, production) |
87
+ | `APP_DIR_FOR_CODE_COVERAGE` | string | `../` | Directory path for code coverage testing |
88
+ | `SHOW_LOGS` | boolean | - | Enable/disable log output for testing |
89
+
90
+ ### Example Configurations
91
+
92
+ #### Production Kubernetes Deployment
93
+ ```bash
94
+ export SOAJS_ENV=production
95
+ export SOAJS_DEPLOY_HA=kubernetes
96
+ export SOAJS_SRVIP=10.0.1.50
97
+ export SOAJS_PROFILE=/etc/soajs/profile.js
98
+ export SOAJS_SENSITIVE_ENVS='["dashboard","production"]'
99
+ export SOAJS_MAX_BODY_SIZE=20971520 # 20MB
100
+ export SOAJS_THROTTLE_CLEANUP_INTERVAL=120000 # 2 minutes
101
+ export SOAJS_MONGO_CON_KEEPALIVE=true
102
+ export NODE_ENV=production
103
+ ```
104
+
105
+ #### Solo Development Mode
106
+ ```bash
107
+ export SOAJS_ENV=dev
108
+ export SOAJS_SOLO=true
109
+ export SOAJS_SRVIP=127.0.0.1
110
+ export SOAJS_PROFILE=/opt/soajs/profiles/dev.js
111
+ ```
112
+
113
+ #### Docker Swarm Deployment
114
+ ```bash
115
+ export SOAJS_ENV=staging
116
+ export SOAJS_DEPLOY_HA=swarm
117
+ export SOAJS_SWARM_UNIX_PORT=/var/run/docker.sock
118
+ export SOAJS_PROFILE=/config/soajs_profile.js
119
+ ```
120
+
121
+ ---
122
+
38
123
  More information on the Controller is available on the website section [Controller](https://soajsorg.atlassian.net/wiki/x/loAwb)
39
124
 
40
125
 
package/lib/request.js CHANGED
@@ -16,10 +16,12 @@ function checkStatus(uri) {
16
16
  let onResponse = false;
17
17
 
18
18
  const req = http.get(uri, (res) => {
19
+ onResponse = true;
20
+ res.resume();
19
21
  if (res.statusCode === 200) {
20
- onResponse = true;
21
- res.resume();
22
- resolve(res.statusCode === 200); // Resolve with true if 200, false otherwise
22
+ resolve(true);
23
+ } else {
24
+ reject(new Error(`Status Code: ${res.statusCode}`));
23
25
  }
24
26
  // res.on('data', () => { }); // Consume data to avoid issues (important!)
25
27
 
@@ -109,19 +111,21 @@ function httpRequestLight({ uri, data = null, method = 'GET', headers = null, js
109
111
  return reject(new Error(`Status Code: ${res.statusCode}`));
110
112
  }
111
113
 
112
- let responseData = '';
114
+ const chunks = [];
113
115
 
114
116
  res.on('data', (chunk) => {
115
- responseData += chunk;
117
+ chunks.push(chunk);
116
118
  });
117
119
 
118
120
  res.on('end', () => {
119
121
  if (!onResponse) {
120
122
  onResponse = true;
121
123
  try {
124
+ const responseData = Buffer.concat(chunks).toString();
122
125
  const parsedData = json ? JSON.parse(responseData) : responseData;
123
126
  return resolve(parsedData);
124
127
  } catch (error) {
128
+ const responseData = Buffer.concat(chunks).toString();
125
129
  return resolve(responseData);
126
130
  }
127
131
  }
@@ -235,15 +239,17 @@ function httpRequest({ uri, data = null, body = null, qs = null, method = 'GET',
235
239
  }
236
240
 
237
241
  req.on('response', (res) => { // Listen for the 'response' event
238
- let responseData = '';
242
+ const chunks = [];
239
243
 
240
244
  res.on('data', (chunk) => {
241
- responseData += chunk;
245
+ chunks.push(chunk);
242
246
  });
243
247
 
244
248
  res.on('end', () => {
245
249
  if (!onResponse) {
246
250
  onResponse = true;
251
+ const responseData = Buffer.concat(chunks).toString();
252
+
247
253
  if (res.statusCode < 200 || res.statusCode >= 300) {
248
254
  const error = new Error(`Status Code: ${res.statusCode}`);
249
255
  try {
@@ -295,7 +301,7 @@ function httpRequest({ uri, data = null, body = null, qs = null, method = 'GET',
295
301
  req.destroy(); // IMPORTANT: Forcefully end the request
296
302
  if (!onResponse) {
297
303
  onResponse = true;
298
- return reject(new Error('Request timed out'));
304
+ return reject({ error: new Error('Request timed out'), body: null });
299
305
  }
300
306
  });
301
307
  // Set the timeout on the request
@@ -34,7 +34,7 @@ if (process.env.SOAJS_SENSITIVE_ENVS) {
34
34
  } catch (e) {
35
35
  temp_sensitiveEnvCodes = null;
36
36
  }
37
- if (Array.isArray(temp_sensitiveEnvCodes) && temp_sensitiveEnvCodes > 0) {
37
+ if (Array.isArray(temp_sensitiveEnvCodes) && temp_sensitiveEnvCodes.length > 0) {
38
38
  sensitiveEnvCodes = temp_sensitiveEnvCodes;
39
39
  }
40
40
  }
@@ -496,15 +496,25 @@ function getRegistry(param, options, cb) {
496
496
  registry_struct[options.envCode] = registry;
497
497
  }
498
498
  if (registry && registry.serviceConfig.awareness.autoRelaodRegistry) {
499
+ if (!autoReloadTimeout[options.envCode]) {
500
+ autoReloadTimeout[options.envCode] = {};
501
+ }
502
+
503
+ // Performance: Prevent overlapping reloads with status tracking
504
+ if (autoReloadTimeout[options.envCode].reloading) {
505
+ // Already reloading, skip scheduling another
506
+ return cb(null, registry_struct[options.envCode]);
507
+ }
508
+
499
509
  let autoReload = () => {
510
+ autoReloadTimeout[options.envCode].reloading = true;
500
511
  options.setBy = "autoReload";
501
512
  getRegistry(param, options, () => {
513
+ autoReloadTimeout[options.envCode].reloading = false;
502
514
  // cb(err, reg);
503
515
  });
504
516
  };
505
- if (!autoReloadTimeout[options.envCode]) {
506
- autoReloadTimeout[options.envCode] = {};
507
- }
517
+
508
518
  if (autoReloadTimeout[options.envCode].timeout) {
509
519
  clearTimeout(autoReloadTimeout[options.envCode].timeout);
510
520
  }
@@ -76,10 +76,17 @@ module.exports = (configuration) => {
76
76
  };
77
77
 
78
78
  proxyRequest(req, res, requestConfig, extraOptions)
79
- .then(() => { })
79
+ .then(() => {
80
+ // Performance: Clean up proxy request reference after success
81
+ if (req.soajs.controller.redirectedRequest) {
82
+ req.soajs.controller.redirectedRequest.removeAllListeners();
83
+ req.soajs.controller.redirectedRequest = null;
84
+ }
85
+ })
80
86
  .catch((error) => {
81
87
  req.soajs.log.error(error.message);
82
88
  if (req.soajs.controller.redirectedRequest) {
89
+ req.soajs.controller.redirectedRequest.removeAllListeners();
83
90
  req.soajs.controller.redirectedRequest.destroy();
84
91
  req.soajs.controller.redirectedRequest = null;
85
92
  }
@@ -116,6 +116,11 @@ module.exports = (configuration) => {
116
116
  (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH' || req.method === 'DELETE')) {
117
117
  let allowedContentType = false;
118
118
  let isStream = false;
119
+ // Performance: Prevent memory exhaustion from large request bodies
120
+ const MAX_BODY_SIZE = process.env.SOAJS_MAX_BODY_SIZE || 10 * 1024 * 1024; // 10MB default
121
+ let bodySize = 0;
122
+ let bodyExceeded = false;
123
+
119
124
  req.on("data", (chunk) => {
120
125
  let resContentType = req.headers['content-type'];
121
126
  if (resContentType) {
@@ -127,11 +132,19 @@ module.exports = (configuration) => {
127
132
  }
128
133
  }
129
134
  if (!isStream && allowedContentType) {
130
- if (!monitoObj.body) {
131
- monitoObj.time.req_body_start = new Date().getTime();
132
- monitoObj.body = chunk;
133
- } else {
134
- monitoObj.body += chunk;
135
+ bodySize += chunk.length;
136
+ if (bodySize > MAX_BODY_SIZE) {
137
+ if (!bodyExceeded) {
138
+ bodyExceeded = true;
139
+ monitoObj.body = "{\"error\": \"Request body exceeds maximum size limit\"}";
140
+ }
141
+ } else if (!bodyExceeded) {
142
+ if (!monitoObj.body) {
143
+ monitoObj.time.req_body_start = new Date().getTime();
144
+ monitoObj.body = chunk;
145
+ } else {
146
+ monitoObj.body += chunk;
147
+ }
135
148
  }
136
149
  } else {
137
150
  monitoObj.body = "{\"contentType\": \"is stream or not allowed\", \"value': \"" + resContentType + "\"}";
package/mw/mt/index.js CHANGED
@@ -339,15 +339,30 @@ module.exports = (configuration) => {
339
339
  if (err) {
340
340
  req.soajs.log.error(err.message);
341
341
  }
342
+
343
+ // Security: Validate and limit soajsinjectobj header size
342
344
  let _h = JSON.stringify(injectObj);
343
- // req.headers.soajsinjectobj = Buffer.from(_h, 'ascii').toString('utf-8');
344
- req.headers.soajsinjectobj = _h; //encodeHeaderValue(_h);
345
+ const MAX_HEADER_SIZE = process.env.SOAJS_MAX_INJECT_HEADER_SIZE || 65536; // 64KB default
346
+
347
+ if (_h.length > MAX_HEADER_SIZE) {
348
+ req.soajs.log.error('soajsinjectobj header exceeds maximum size:', _h.length);
349
+ return next(135);
350
+ }
351
+
352
+ req.headers.soajsinjectobj = _h;
345
353
  return next();
346
354
  });
347
355
  } else {
356
+ // Security: Validate and limit soajsinjectobj header size
348
357
  let _h = JSON.stringify(injectObj);
349
- // req.headers.soajsinjectobj = Buffer.from(_h, 'ascii').toString('utf-8');
350
- req.headers.soajsinjectobj = _h; //encodeHeaderValue(_h);
358
+ const MAX_HEADER_SIZE = process.env.SOAJS_MAX_INJECT_HEADER_SIZE || 65536; // 64KB default
359
+
360
+ if (_h.length > MAX_HEADER_SIZE) {
361
+ req.soajs.log.error('soajsinjectobj header exceeds maximum size:', _h.length);
362
+ return next(135);
363
+ }
364
+
365
+ req.headers.soajsinjectobj = _h;
351
366
  return next();
352
367
  }
353
368
  }
package/mw/mt/utils.js CHANGED
@@ -20,6 +20,35 @@ const merge = require('merge');
20
20
 
21
21
  const UracDriver = require("./urac.js");
22
22
 
23
+ // Performance: Configuration accessor utility for deep property access with defaults
24
+ function getConfig(obj, path, defaultValue = undefined) {
25
+ const keys = path.split('.');
26
+ let result = obj;
27
+ for (const key of keys) {
28
+ if (result && typeof result === 'object' && key in result) {
29
+ result = result[key];
30
+ } else {
31
+ return defaultValue;
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+
37
+ // Performance: Cache Netmask objects to avoid repeated parsing
38
+ const netmaskCache = new Map();
39
+ function getCachedNetmask(addr) {
40
+ if (!netmaskCache.has(addr)) {
41
+ try {
42
+ netmaskCache.set(addr, new Netmask(addr));
43
+ } catch (err) {
44
+ // Store null for invalid addresses to avoid repeated parsing attempts
45
+ netmaskCache.set(addr, null);
46
+ throw err;
47
+ }
48
+ }
49
+ return netmaskCache.get(addr);
50
+ }
51
+
23
52
  let sensitiveEnvCodes = ["dashboard"];
24
53
  if (process.env.SOAJS_SENSITIVE_ENVS) {
25
54
  let temp_sensitiveEnvCodes = null;
@@ -28,24 +57,36 @@ if (process.env.SOAJS_SENSITIVE_ENVS) {
28
57
  } catch (e) {
29
58
  temp_sensitiveEnvCodes = null;
30
59
  }
31
- if (Array.isArray(temp_sensitiveEnvCodes) && temp_sensitiveEnvCodes > 0) {
60
+ if (Array.isArray(temp_sensitiveEnvCodes) && temp_sensitiveEnvCodes.length > 0) {
32
61
  sensitiveEnvCodes = temp_sensitiveEnvCodes;
33
62
  }
34
63
  }
35
64
 
36
65
  const { pathToRegexp } = require("path-to-regexp");
37
66
 
67
+ // Performance: Cache compiled RegExp objects for route matching
68
+ const regexpCache = new Map();
69
+ const MAX_REGEXP_CACHE_SIZE = 500; // Prevent unbounded growth
70
+
38
71
  function constructRegExp(route) {
39
- // let keys = [];
40
- // let out = pathToRegexp(route, keys, { sensitive: true });
72
+ // Check cache first
73
+ if (regexpCache.has(route)) {
74
+ return regexpCache.get(route);
75
+ }
76
+
77
+ // Compile the route pattern
41
78
  let out = pathToRegexp(route, { sensitive: true });
42
- if (out.regexp) {
43
- return out.regexp;
79
+ let regexp = out.regexp || out;
80
+
81
+ // Implement simple LRU by clearing cache when it gets too large
82
+ if (regexpCache.size >= MAX_REGEXP_CACHE_SIZE) {
83
+ // Clear oldest 25% of entries
84
+ const keysToDelete = Array.from(regexpCache.keys()).slice(0, Math.floor(MAX_REGEXP_CACHE_SIZE * 0.25));
85
+ keysToDelete.forEach(key => regexpCache.delete(key));
44
86
  }
45
- // if (out && out.keys && out.keys.length > 0) {
46
- // out = new RegExp(out.toString());
47
- // }
48
- return out;
87
+
88
+ regexpCache.set(route, regexp);
89
+ return regexp;
49
90
  }
50
91
 
51
92
  /**
@@ -139,20 +180,15 @@ let utils = {
139
180
  }
140
181
  *
141
182
  */
142
- if (obj.req.soajs.registry &&
143
- obj.req.soajs.registry.custom &&
144
- obj.req.soajs.registry.custom.gateway &&
145
- obj.req.soajs.registry.custom.gateway.value &&
146
- obj.req.soajs.registry.custom.gateway.value.mt &&
147
- obj.req.soajs.registry.custom.gateway.value.mt.whitelist &&
148
- obj.req.soajs.registry.custom.gateway.value.mt.whitelist.ips) {
183
+ // Performance: Use config accessor to simplify deeply nested property access
184
+ const whitelistIps = getConfig(obj.req, 'soajs.registry.custom.gateway.value.mt.whitelist.ips');
185
+ if (whitelistIps && Array.isArray(whitelistIps)) {
149
186
  let clientIp = obj.req.getClientIP();
150
- let geoAccess = obj.req.soajs.registry.custom.gateway.value.mt.whitelist.ips; //["127.0.0.0/8"];
151
187
  let checkAccess = (geoAccessArr, ip) => {
152
188
  return (geoAccessArr.some(function (addr) {
153
189
  try {
154
- let block = new Netmask(addr);
155
- return block.contains(ip);
190
+ let block = getCachedNetmask(addr);
191
+ return block && block.contains(ip);
156
192
  } catch (err) {
157
193
  obj.req.soajs.log.error('IP whitelist security configuration failed: ', addr);
158
194
  obj.req.soajs.log.error(err);
@@ -160,23 +196,22 @@ let utils = {
160
196
  return false;
161
197
  }));
162
198
  };
163
- if (clientIp && geoAccess && geoAccess && Array.isArray(geoAccess)) {
164
- let matched = checkAccess(geoAccess, clientIp);
199
+ if (clientIp) {
200
+ let matched = checkAccess(whitelistIps, clientIp);
165
201
  if (matched) {
166
202
  obj.req.soajs.log.debug("ACL skip detected for ip: " + clientIp);
167
- if (obj.req.soajs.registry.custom.gateway.value.mt.whitelist.acl) {
203
+ const whitelistConfig = getConfig(obj.req, 'soajs.registry.custom.gateway.value.mt.whitelist', {});
204
+ if (whitelistConfig.acl) {
168
205
  obj.skipACL = true;
169
206
  }
170
- if (obj.req.soajs.registry.custom.gateway.value.mt.whitelist.oauth) {
207
+ if (whitelistConfig.oauth) {
171
208
  obj.skipOAUTH = true;
172
209
  }
173
210
  }
174
211
  }
175
-
176
- return cb(null, obj);
177
- } else {
178
- return cb(null, obj);
179
212
  }
213
+
214
+ return cb(null, obj);
180
215
  },
181
216
 
182
217
  "aclUrackCheck": (obj, cb) => {
@@ -282,8 +317,8 @@ let utils = {
282
317
  let checkAccess = (geoAccessArr, ip) => {
283
318
  return (geoAccessArr.some(function (addr) {
284
319
  try {
285
- let block = new Netmask(addr);
286
- return block.contains(ip);
320
+ let block = getCachedNetmask(addr);
321
+ return block && block.contains(ip);
287
322
  } catch (err) {
288
323
  obj.req.soajs.log.error('Geographic security configuration failed: ', addr);
289
324
  obj.req.soajs.log.error(err.message);
@@ -35,7 +35,7 @@ Response.prototype.addErrorCode = function (code, message) {
35
35
  }
36
36
  let errorCode = {
37
37
  "code": code,
38
- "message": message.trim()
38
+ "message": message ? message.trim() : ''
39
39
  };
40
40
 
41
41
  if (!this.errors) {
@@ -9,12 +9,56 @@
9
9
  */
10
10
 
11
11
  let dataHolder = {};
12
+ let cleanupInterval = null;
12
13
 
13
14
  let model = {
14
15
  'log': null,
15
16
  'initThrottling': (log) => {
16
17
  model.log = log;
17
18
  dataHolder = {};
19
+
20
+ // Performance: Implement periodic cleanup of expired throttling entries
21
+ if (cleanupInterval) {
22
+ clearInterval(cleanupInterval);
23
+ }
24
+ const CLEANUP_INTERVAL = process.env.SOAJS_THROTTLE_CLEANUP_INTERVAL || 60000; // 1 minute default
25
+ cleanupInterval = setInterval(() => {
26
+ model.cleanupExpiredEntries();
27
+ }, CLEANUP_INTERVAL);
28
+ },
29
+
30
+ 'cleanupExpiredEntries': () => {
31
+ const now = Date.now();
32
+ const MAX_AGE = 3600000; // 1 hour - entries older than this are removed
33
+ let removedCount = 0;
34
+
35
+ for (const l1Key in dataHolder) {
36
+ if (dataHolder.hasOwnProperty(l1Key)) {
37
+ const entry = dataHolder[l1Key];
38
+
39
+ // Check if l1 level entry is expired
40
+ if (entry.firstReqTime && (now - entry.firstReqTime.getTime()) > MAX_AGE) {
41
+ delete dataHolder[l1Key];
42
+ removedCount++;
43
+ continue;
44
+ }
45
+
46
+ // Check l2 level entries (if any)
47
+ for (const l2Key in entry) {
48
+ if (entry.hasOwnProperty(l2Key) && l2Key !== 'firstReqTime' && l2Key !== 'count') {
49
+ const l2Entry = entry[l2Key];
50
+ if (l2Entry.firstReqTime && (now - l2Entry.firstReqTime.getTime()) > MAX_AGE) {
51
+ delete entry[l2Key];
52
+ removedCount++;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ if (removedCount > 0 && model.log) {
60
+ model.log.debug(`Throttling cache cleanup: removed ${removedCount} expired entries`);
61
+ }
18
62
  },
19
63
 
20
64
  'getThrottling': (throttling, trafficKey) => {
@@ -66,8 +110,28 @@ let model = {
66
110
 
67
111
  'logTooManyRequests': (trafficKey, throttlingStrategy) => {
68
112
  model.log.error('Throttling [' + throttlingStrategy + ']rejected:', 'using tenant id [' + trafficKey.l1 + '] from ip [' + trafficKey.l2 + ']');
113
+ },
114
+
115
+ 'shutdown': () => {
116
+ // Performance: Clean up interval on shutdown to prevent memory leaks
117
+ if (cleanupInterval) {
118
+ clearInterval(cleanupInterval);
119
+ cleanupInterval = null;
120
+ }
121
+ dataHolder = {};
122
+ if (model.log) {
123
+ model.log.info('Traffic throttling model shutdown complete');
124
+ }
69
125
  }
70
126
  };
71
127
 
128
+ // Performance: Register shutdown handlers
129
+ if (typeof process !== 'undefined') {
130
+ const shutdownHandler = () => {
131
+ model.shutdown();
132
+ };
133
+ process.once('SIGTERM', shutdownHandler);
134
+ process.once('SIGINT', shutdownHandler);
135
+ }
72
136
 
73
137
  module.exports = model;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "soajs.controller",
3
3
  "description": "soajs multi tenant API gateway",
4
- "version": "4.2.40",
4
+ "version": "4.2.41",
5
5
  "author": {
6
6
  "name": "soajs team",
7
7
  "email": "team@soajs.org"
@@ -29,14 +29,14 @@
29
29
  "oauth2-server": "2.4.1",
30
30
  "path-to-regexp": "8.3.0",
31
31
  "soajs.core.libs": "1.2.1",
32
- "soajs.core.modules": "5.2.16",
33
- "soajs.urac.driver": "3.2.13",
32
+ "soajs.core.modules": "5.2.17",
33
+ "soajs.urac.driver": "3.2.14",
34
34
  "useragent": "2.3.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@babel/core": "7.28.4",
38
- "@babel/plugin-transform-runtime": "7.28.3",
39
- "@babel/preset-env": "7.28.3",
37
+ "@babel/core": "7.28.5",
38
+ "@babel/plugin-transform-runtime": "7.28.5",
39
+ "@babel/preset-env": "7.28.5",
40
40
  "@babel/runtime": "7.28.4",
41
41
  "express": "4.21.1",
42
42
  "grunt": "1.6.1",
@@ -49,7 +49,7 @@
49
49
  "grunt-istanbul": "0.8.0",
50
50
  "grunt-jsdoc": "2.4.1",
51
51
  "grunt-mocha-test": "0.13.3",
52
- "mocha": "11.7.2",
52
+ "mocha": "11.7.5",
53
53
  "nock": "14.0.10",
54
54
  "request": "2.88.2",
55
55
  "sinon": "21.0.0"