@63klabs/cache-data 1.3.8 → 1.3.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.
- package/CHANGELOG.md +109 -5
- package/CONTRIBUTING.md +58 -5
- package/README.md +20 -34
- package/package.json +16 -27
- package/src/lib/dao-cache.js +55 -47
- package/src/lib/dao-endpoint.js +68 -38
- package/src/lib/tools/AWS.classes.js +58 -5
- package/src/lib/tools/{APIRequest.class.js → ApiRequest.class.js} +164 -51
- package/src/lib/tools/CachedParametersSecrets.classes.js +10 -9
- package/src/lib/tools/ClientRequest.class.js +987 -44
- package/src/lib/tools/Connections.classes.js +5 -5
- package/src/lib/tools/Response.class.js +25 -0
- package/src/lib/tools/generic.response.html.js +8 -113
- package/src/lib/tools/generic.response.js +73 -0
- package/src/lib/tools/generic.response.json.js +5 -135
- package/src/lib/tools/generic.response.rss.js +10 -114
- package/src/lib/tools/generic.response.text.js +5 -115
- package/src/lib/tools/generic.response.xml.js +10 -114
- package/src/lib/tools/index.js +113 -40
- package/src/lib/utils/ValidationExecutor.class.js +70 -0
- package/src/lib/utils/ValidationMatcher.class.js +417 -0
- package/AGENTS.md +0 -1012
|
@@ -1,121 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
const { createGenericResponseModule } = require("./generic.response");
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
"Content-Type": contentType
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
xml = (body) => {
|
|
3
|
+
const xml = (body) => {
|
|
8
4
|
return `<?xml version="1.0" encoding="UTF-8" ?>${body}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
response200 = {
|
|
12
|
-
statusCode: 200,
|
|
13
|
-
headers: headers,
|
|
14
|
-
body: xml("<hello>Success</hello>")
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
response400 = {
|
|
18
|
-
statusCode: 400,
|
|
19
|
-
headers: headers,
|
|
20
|
-
body: xml("<error>Bad Request</error>")
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
response401 = {
|
|
24
|
-
statusCode: 401,
|
|
25
|
-
headers: headers,
|
|
26
|
-
body: xml("<error>Unauthorized</error>")
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
response403 = {
|
|
30
|
-
statusCode: 403,
|
|
31
|
-
headers: headers,
|
|
32
|
-
body: xml("<error>Forbidden</error>")
|
|
33
5
|
};
|
|
34
6
|
|
|
35
|
-
|
|
36
|
-
statusCode
|
|
37
|
-
|
|
38
|
-
body: xml("<error>Not Found</error>")
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
response405 = {
|
|
42
|
-
statusCode: 405,
|
|
43
|
-
headers: headers,
|
|
44
|
-
body: xml("<error>Method Not Allowed</error>")
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
response408 = {
|
|
48
|
-
statusCode: 408,
|
|
49
|
-
headers: headers,
|
|
50
|
-
body: xml("<error>Request Timeout</error>")
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
response418 = {
|
|
54
|
-
statusCode: 418,
|
|
55
|
-
headers: headers,
|
|
56
|
-
body: xml("<error>418 I'm a teapot</error>")
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
response427 = {
|
|
60
|
-
statusCode: 427,
|
|
61
|
-
headers: headers,
|
|
62
|
-
body: xml("<error>Too Many Requests</error>")
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
response500 = {
|
|
66
|
-
statusCode: 500,
|
|
67
|
-
headers: headers,
|
|
68
|
-
body: xml("<error>Internal Server Error</error>")
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
*
|
|
73
|
-
* @param {number|string} statusCode
|
|
74
|
-
* @returns {{statusCode: number, headers: object, body: Array|Object|string}}
|
|
75
|
-
*/
|
|
76
|
-
response = function (statusCode) {
|
|
77
|
-
// convert to int
|
|
78
|
-
statusCode = parseInt(statusCode, 10);
|
|
79
|
-
|
|
80
|
-
switch (statusCode) {
|
|
81
|
-
case 200:
|
|
82
|
-
return this.response200;
|
|
83
|
-
case 400:
|
|
84
|
-
return this.response400;
|
|
85
|
-
case 401:
|
|
86
|
-
return this.response401;
|
|
87
|
-
case 403:
|
|
88
|
-
return this.response403;
|
|
89
|
-
case 404:
|
|
90
|
-
return this.response404;
|
|
91
|
-
case 405:
|
|
92
|
-
return this.response405;
|
|
93
|
-
case 408:
|
|
94
|
-
return this.response408;
|
|
95
|
-
case 418:
|
|
96
|
-
return this.response418;
|
|
97
|
-
case 427:
|
|
98
|
-
return this.response427;
|
|
99
|
-
case 500:
|
|
100
|
-
return this.response500;
|
|
101
|
-
default:
|
|
102
|
-
return this.response500;
|
|
7
|
+
const xmlBodyFormatter = (statusCode, message) => {
|
|
8
|
+
if (statusCode === 200) {
|
|
9
|
+
return xml("<hello>" + message + "</hello>");
|
|
103
10
|
}
|
|
11
|
+
const msg = statusCode === 418 ? "418 " + message : message;
|
|
12
|
+
return xml("<error>" + msg + "</error>");
|
|
104
13
|
};
|
|
105
14
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
xml,
|
|
110
|
-
response200,
|
|
111
|
-
response400,
|
|
112
|
-
response401,
|
|
113
|
-
response403,
|
|
114
|
-
response404,
|
|
115
|
-
response405,
|
|
116
|
-
response408,
|
|
117
|
-
response418,
|
|
118
|
-
response427,
|
|
119
|
-
response500,
|
|
120
|
-
response
|
|
121
|
-
}
|
|
15
|
+
const mod = createGenericResponseModule("application/xml", xmlBodyFormatter);
|
|
16
|
+
|
|
17
|
+
module.exports = { ...mod, xml };
|
package/src/lib/tools/index.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
const { nodeVer, nodeVerMajor, nodeVerMinor, nodeVerMajorMinor } = require('./vars');
|
|
22
22
|
const { AWS, AWSXRay } = require('./AWS.classes');
|
|
23
|
-
const
|
|
23
|
+
const ApiRequest = require("./ApiRequest.class");
|
|
24
24
|
const RequestInfo = require("./RequestInfo.class");
|
|
25
25
|
const ClientRequest = require("./ClientRequest.class");
|
|
26
26
|
const ResponseDataModel = require("./ResponseDataModel.class");
|
|
@@ -34,7 +34,7 @@ const xmlGenericResponse = require('./generic.response.xml');
|
|
|
34
34
|
const rssGenericResponse = require('./generic.response.rss');
|
|
35
35
|
const textGenericResponse = require('./generic.response.text');
|
|
36
36
|
const { printMsg, sanitize, obfuscate, hashThisData} = require('./utils');
|
|
37
|
-
const { CachedParameterSecrets, CachedParameterSecret,
|
|
37
|
+
const { CachedParameterSecrets, CachedParameterSecret, CachedSsmParameter, CachedSecret } = require('./CachedParametersSecrets.classes')
|
|
38
38
|
const { Connections, Connection, ConnectionRequest, ConnectionAuthentication } = require('./Connections.classes')
|
|
39
39
|
|
|
40
40
|
/*
|
|
@@ -107,7 +107,11 @@ class AppConfig {
|
|
|
107
107
|
static _ssmParameters = null;
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
* Initialize the Config class
|
|
110
|
+
* Initialize the Config class with asynchronous parallel execution.
|
|
111
|
+
*
|
|
112
|
+
* This method returns immediately (synchronously) while initialization operations
|
|
113
|
+
* execute asynchronously in parallel. Use AppConfig.promise() to wait for all
|
|
114
|
+
* initialization to complete before accessing initialized configuration.
|
|
111
115
|
*
|
|
112
116
|
* @param {object} options Configuration options
|
|
113
117
|
* @param {object} options.settings Application settings retrieved by Config.settings()
|
|
@@ -124,8 +128,10 @@ class AppConfig {
|
|
|
124
128
|
* @param {object} options.responses.rssResponses
|
|
125
129
|
* @param {object} options.responses.textResponses
|
|
126
130
|
* @param {object} options.ssmParameters Parameter Store
|
|
127
|
-
* @
|
|
131
|
+
* @param {boolean} [options.debug=false] Enable debug logging
|
|
132
|
+
* @returns {boolean} True if initialization started successfully, false on synchronous error
|
|
128
133
|
* @example
|
|
134
|
+
* // Initialize configuration (returns immediately)
|
|
129
135
|
* const { Config } = require("./config");
|
|
130
136
|
* Config.init({
|
|
131
137
|
* settings: {
|
|
@@ -143,48 +149,100 @@ class AppConfig {
|
|
|
143
149
|
* }
|
|
144
150
|
* }
|
|
145
151
|
* });
|
|
152
|
+
*
|
|
153
|
+
* // Wait for all initialization to complete
|
|
154
|
+
* await Config.promise();
|
|
155
|
+
*
|
|
156
|
+
* // Now safe to access initialized configuration
|
|
157
|
+
* const settings = Config.settings();
|
|
158
|
+
* const conn = Config.getConn('myConnection');
|
|
146
159
|
*/
|
|
147
160
|
static init(options = {}) {
|
|
148
161
|
|
|
149
|
-
|
|
162
|
+
try {
|
|
150
163
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
const debug = (options?.debug === true);
|
|
165
|
+
if (debug) {
|
|
166
|
+
DebugAndLog.debug("Config Init in debug mode");
|
|
167
|
+
}
|
|
155
168
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
if (options.settings) {
|
|
170
|
+
const settingsPromise = new Promise((resolve) => {
|
|
171
|
+
try {
|
|
172
|
+
AppConfig._settings = options.settings;
|
|
173
|
+
if (debug) {
|
|
174
|
+
DebugAndLog.debug("Settings initialized", AppConfig._settings);
|
|
175
|
+
}
|
|
176
|
+
resolve(true);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
DebugAndLog.error(`Settings initialization failed: ${error.message}`, error.stack);
|
|
179
|
+
resolve(false);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
AppConfig.add(settingsPromise);
|
|
183
|
+
}
|
|
160
184
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
185
|
+
if (options.connections) {
|
|
186
|
+
const connectionsPromise = new Promise((resolve) => {
|
|
187
|
+
try {
|
|
188
|
+
AppConfig._connections = new Connections(options.connections);
|
|
189
|
+
if (debug) {
|
|
190
|
+
DebugAndLog.debug("Connections initialized", AppConfig._connections.info());
|
|
191
|
+
}
|
|
192
|
+
resolve(true);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
DebugAndLog.error(`Connections initialization failed: ${error.message}`, error.stack);
|
|
195
|
+
resolve(false);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
AppConfig.add(connectionsPromise);
|
|
199
|
+
}
|
|
165
200
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
201
|
+
if (options.validations) {
|
|
202
|
+
const validationsPromise = new Promise((resolve) => {
|
|
203
|
+
try {
|
|
204
|
+
ClientRequest.init(options.validations);
|
|
205
|
+
if (debug) {
|
|
206
|
+
DebugAndLog.debug("ClientRequest initialized", ClientRequest.info());
|
|
207
|
+
}
|
|
208
|
+
resolve(true);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
DebugAndLog.error(`ClientRequest initialization failed: ${error.message}`, error.stack);
|
|
211
|
+
resolve(false);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
AppConfig.add(validationsPromise);
|
|
215
|
+
}
|
|
170
216
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
217
|
+
if (options.responses) {
|
|
218
|
+
const responsesPromise = new Promise((resolve) => {
|
|
219
|
+
try {
|
|
220
|
+
Response.init(options.responses);
|
|
221
|
+
if (debug) {
|
|
222
|
+
DebugAndLog.debug("Response initialized", Response.info());
|
|
223
|
+
}
|
|
224
|
+
resolve(true);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
DebugAndLog.error(`Response initialization failed: ${error.message}`, error.stack);
|
|
227
|
+
resolve(false);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
AppConfig.add(responsesPromise);
|
|
231
|
+
}
|
|
175
232
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
233
|
+
if (options.ssmParameters) {
|
|
234
|
+
AppConfig._ssmParameters = AppConfig._initParameters(options.ssmParameters);
|
|
235
|
+
AppConfig.add(AppConfig._ssmParameters);
|
|
236
|
+
}
|
|
180
237
|
|
|
181
|
-
|
|
238
|
+
return true;
|
|
182
239
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
240
|
+
} catch (error) {
|
|
241
|
+
DebugAndLog.error(`Could not initialize Config ${error.message}`, error.stack);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
186
244
|
}
|
|
187
|
-
|
|
245
|
+
;
|
|
188
246
|
|
|
189
247
|
/**
|
|
190
248
|
* Add a promise to AppConfig. Use AppConfig.promise() to ensure all are resolved.
|
|
@@ -237,7 +295,7 @@ class AppConfig {
|
|
|
237
295
|
* const conn = Config.getConn('myConnection');
|
|
238
296
|
* const cacheObj = await CacheableDataAccess.getData(
|
|
239
297
|
* cacheProfile,
|
|
240
|
-
* endpoint.
|
|
298
|
+
* endpoint.send
|
|
241
299
|
* conn
|
|
242
300
|
* )
|
|
243
301
|
* */
|
|
@@ -264,7 +322,7 @@ class AppConfig {
|
|
|
264
322
|
* const { conn, cacheProfile } = Config.getConnCacheProfile('myConnection', 'myCacheProfile');
|
|
265
323
|
* const cacheObj = await CacheableDataAccess.getData(
|
|
266
324
|
* cacheProfile,
|
|
267
|
-
* endpoint.
|
|
325
|
+
* endpoint.send
|
|
268
326
|
* conn
|
|
269
327
|
* )
|
|
270
328
|
*/
|
|
@@ -299,13 +357,13 @@ class AppConfig {
|
|
|
299
357
|
|
|
300
358
|
/**
|
|
301
359
|
*
|
|
302
|
-
* @returns {Promise} A promise that resolves when the Config class has finished initializing
|
|
360
|
+
* @returns {Promise<array>} A promise that resolves when the Config class has finished initializing
|
|
303
361
|
*/
|
|
304
362
|
static promise() {
|
|
305
363
|
if (AppConfig._promise !== null ) { // Backwards compatibility
|
|
306
364
|
AppConfig._promises.push(AppConfig._promise);
|
|
307
365
|
}
|
|
308
|
-
return Promise.all
|
|
366
|
+
return Promise.all(AppConfig._promises);
|
|
309
367
|
};
|
|
310
368
|
|
|
311
369
|
|
|
@@ -405,6 +463,14 @@ class AppConfig {
|
|
|
405
463
|
// put the parameter into its group
|
|
406
464
|
const obj = parameters.find(o => o.path === groupPath);
|
|
407
465
|
const group = obj.group;
|
|
466
|
+
|
|
467
|
+
// >! Guard against prototype pollution (CWE-471)
|
|
468
|
+
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
469
|
+
if (DANGEROUS_KEYS.includes(group) || DANGEROUS_KEYS.includes(name)) {
|
|
470
|
+
DebugAndLog.warn(`Skipping dangerous parameter key: group="${group}", name="${name}"`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
408
474
|
if ( !(group in paramstore)) {
|
|
409
475
|
paramstore[group] = {};
|
|
410
476
|
}
|
|
@@ -473,8 +539,12 @@ module.exports = {
|
|
|
473
539
|
nodeVerMinor,
|
|
474
540
|
nodeVerMajorMinor,
|
|
475
541
|
AWS,
|
|
542
|
+
Aws: AWS,
|
|
476
543
|
AWSXRay,
|
|
477
|
-
|
|
544
|
+
AwsXRay: AWSXRay, // Alias
|
|
545
|
+
ApiRequest,
|
|
546
|
+
/** @deprecated Use ApiRequest instead */
|
|
547
|
+
APIRequest: ApiRequest, // Alias
|
|
478
548
|
ImmutableObject,
|
|
479
549
|
Timer,
|
|
480
550
|
DebugAndLog,
|
|
@@ -487,8 +557,11 @@ module.exports = {
|
|
|
487
557
|
ResponseDataModel,
|
|
488
558
|
Response,
|
|
489
559
|
AppConfig,
|
|
560
|
+
/** @deprecated Use AppConfig instead */
|
|
490
561
|
_ConfigSuperClass: AppConfig, // Alias
|
|
491
|
-
|
|
562
|
+
CachedSsmParameter,
|
|
563
|
+
/** @deprecated Use CachedSsmParameter instead */
|
|
564
|
+
CachedSSMParameter: CachedSsmParameter, // Alias
|
|
492
565
|
CachedSecret,
|
|
493
566
|
CachedParameterSecret,
|
|
494
567
|
CachedParameterSecrets,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationExecutor - Executes validation functions with appropriate interfaces
|
|
3
|
+
*
|
|
4
|
+
* This class provides static methods for executing validation functions with either
|
|
5
|
+
* single-parameter or multi-parameter interfaces. It handles errors gracefully and
|
|
6
|
+
* logs validation failures.
|
|
7
|
+
*
|
|
8
|
+
* The interface is determined by the params array length:
|
|
9
|
+
* - params.length === 1: Pass value directly (single-parameter interface)
|
|
10
|
+
* - params.length > 1: Pass object (multi-parameter interface)
|
|
11
|
+
*
|
|
12
|
+
* @private
|
|
13
|
+
* @class ValidationExecutor
|
|
14
|
+
*/
|
|
15
|
+
class ValidationExecutor {
|
|
16
|
+
/**
|
|
17
|
+
* Execute validation function with appropriate interface.
|
|
18
|
+
*
|
|
19
|
+
* Determines whether to pass a single value or an object based on the number
|
|
20
|
+
* of parameters in the params array. Handles validation errors gracefully by
|
|
21
|
+
* catching exceptions and logging them.
|
|
22
|
+
*
|
|
23
|
+
* @param {Function} validateFn - Validation function to execute
|
|
24
|
+
* @param {Array<string>} paramNames - Parameter names to validate
|
|
25
|
+
* @param {Object} paramValues - All parameter values available
|
|
26
|
+
* @returns {boolean} True if validation passes, false if fails or throws
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Single parameter validation (params.length === 1)
|
|
30
|
+
* const isValid = ValidationExecutor.execute(
|
|
31
|
+
* (value) => value.length > 0,
|
|
32
|
+
* ['id'],
|
|
33
|
+
* { id: '123' }
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // Multi-parameter validation (params.length > 1)
|
|
38
|
+
* const isValid = ValidationExecutor.execute(
|
|
39
|
+
* ({page, limit}) => page >= 1 && limit >= 1 && limit <= 100,
|
|
40
|
+
* ['page', 'limit'],
|
|
41
|
+
* { page: 1, limit: 10 }
|
|
42
|
+
* );
|
|
43
|
+
*/
|
|
44
|
+
static execute(validateFn, paramNames, paramValues) {
|
|
45
|
+
try {
|
|
46
|
+
if (paramNames.length === 1) {
|
|
47
|
+
// Single parameter: pass value directly
|
|
48
|
+
const value = paramValues[paramNames[0]];
|
|
49
|
+
return validateFn(value);
|
|
50
|
+
} else {
|
|
51
|
+
// Multiple parameters: pass object
|
|
52
|
+
const paramObj = {};
|
|
53
|
+
for (const name of paramNames) {
|
|
54
|
+
paramObj[name] = paramValues[name];
|
|
55
|
+
}
|
|
56
|
+
return validateFn(paramObj);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Log error and treat as validation failure
|
|
60
|
+
const tools = require("../tools/index.js");
|
|
61
|
+
tools.DebugAndLog.error(
|
|
62
|
+
`Validation function threw error for parameters [${paramNames.join(", ")}]: ${error?.message || "Unknown error"}`,
|
|
63
|
+
error?.stack
|
|
64
|
+
);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = ValidationExecutor;
|