@63klabs/cache-data 1.2.2
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 +234 -0
- package/LICENSE.txt +21 -0
- package/README.md +1265 -0
- package/SECURITY.md +5 -0
- package/package.json +58 -0
- package/src/index.js +9 -0
- package/src/lib/dao-cache.js +2024 -0
- package/src/lib/dao-endpoint.js +186 -0
- package/src/lib/tools/APIRequest.class.js +673 -0
- package/src/lib/tools/AWS.classes.js +250 -0
- package/src/lib/tools/CachedParametersSecrets.classes.js +492 -0
- package/src/lib/tools/ClientRequest.class.js +567 -0
- package/src/lib/tools/Connections.classes.js +466 -0
- package/src/lib/tools/DebugAndLog.class.js +416 -0
- package/src/lib/tools/ImmutableObject.class.js +71 -0
- package/src/lib/tools/RequestInfo.class.js +323 -0
- package/src/lib/tools/Response.class.js +547 -0
- package/src/lib/tools/ResponseDataModel.class.js +183 -0
- package/src/lib/tools/Timer.class.js +189 -0
- package/src/lib/tools/generic.response.html.js +88 -0
- package/src/lib/tools/generic.response.json.js +102 -0
- package/src/lib/tools/generic.response.rss.js +88 -0
- package/src/lib/tools/generic.response.text.js +86 -0
- package/src/lib/tools/generic.response.xml.js +82 -0
- package/src/lib/tools/index.js +318 -0
- package/src/lib/tools/utils.js +305 -0
- package/src/lib/tools/vars.js +34 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
|
|
2
|
+
const crypto = require("crypto"); // included by aws so don't need to add to package.json
|
|
3
|
+
|
|
4
|
+
/* *****************************************************************************
|
|
5
|
+
-----------------------------------------------------------------------------
|
|
6
|
+
HELPER FUNCTIONS
|
|
7
|
+
-----------------------------------------------------------------------------
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const printMsg = function() {
|
|
11
|
+
console.log("This is a message from the demo package");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Given a secret string, returns a string padded out at the beginning
|
|
16
|
+
* with * or passed character leaving only the specified number of characters unobfuscated.
|
|
17
|
+
*
|
|
18
|
+
* For example, if 123456789123456 was passed with default keep, padding character, and length,
|
|
19
|
+
* ******3456 would be returned.
|
|
20
|
+
*
|
|
21
|
+
* No more than 25% of the string, or 6 characters may be kept, whichever is lesser.
|
|
22
|
+
* @param {string} str The secret string to obfuscate
|
|
23
|
+
* @param {Object} options
|
|
24
|
+
* @param {number} options.keep The number of characters to keep unobfuscated on the end. 4 is default
|
|
25
|
+
* @param {string} options.char The character to pad out with. '*' is default
|
|
26
|
+
* @param {number} options.len Length of the result string
|
|
27
|
+
* @returns Last few characters padded by * or (passed character) from start
|
|
28
|
+
*/
|
|
29
|
+
const obfuscate = function(str, options = {}) {
|
|
30
|
+
if ( !( "keep" in options) ) { options.keep = 4; }
|
|
31
|
+
if ( !( "char" in options) ) { options.char = '*'; }
|
|
32
|
+
if ( !( "len" in options) ) { options.len = 10; }
|
|
33
|
+
|
|
34
|
+
// don't show more than 25% of the string, and show no more than a max of 6;
|
|
35
|
+
if ((options.keep / str.length) > .25 || str.length <= 6) { options.keep = Math.min(Math.ceil(str.length * .25), 6); }
|
|
36
|
+
|
|
37
|
+
// we allow any length greater than padding of 4
|
|
38
|
+
if ( options.keep + 4 > options.len ) { options.len = options.keep + 4; }
|
|
39
|
+
|
|
40
|
+
return str.slice(-options.keep).padStart(options.len, options.char);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const SANITIZE_MAX_INPUT_LENGTH = 200000; // Adjustable
|
|
44
|
+
|
|
45
|
+
const sanitizeInput = function (strObj) {
|
|
46
|
+
|
|
47
|
+
if (typeof strObj !== 'string') {
|
|
48
|
+
throw new Error('Invalid input');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Early length check to prevent ReDoS
|
|
52
|
+
if (strObj.length > SANITIZE_MAX_INPUT_LENGTH) {
|
|
53
|
+
let trunc = strObj.substring(0, SANITIZE_MAX_INPUT_LENGTH);
|
|
54
|
+
strObj = JSON.stringify({message: 'Input exceeds maximum allowed length', truncated_input: trunc});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return strObj;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Given an object such as a Lambda event which may hold secret keys in the query string or
|
|
62
|
+
* Authorization headers, it will attempt to find and obfuscate them. It searches for any object keys,
|
|
63
|
+
* string patterns that have 'key', 'secret', or 'token' in the label and obfuscates its value.
|
|
64
|
+
* @param {Object} obj The object to sanitize
|
|
65
|
+
* @returns {Object} A sanitized object
|
|
66
|
+
*/
|
|
67
|
+
const sanitize = function (obj) {
|
|
68
|
+
|
|
69
|
+
let sanitizedObj = {};
|
|
70
|
+
|
|
71
|
+
// If obj is already a string, convert it to an object
|
|
72
|
+
if (typeof obj === 'string') {
|
|
73
|
+
try {
|
|
74
|
+
obj = JSON.parse(obj);
|
|
75
|
+
} catch(e) {
|
|
76
|
+
// If it's not JSON, wrap it in an object
|
|
77
|
+
obj = { value: obj };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
|
|
83
|
+
// convert object to a string which is much easier to perform a search/replace on and we avoid changing original
|
|
84
|
+
let strObj = JSON.stringify(obj);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Find and replace secret values for secrets, keys, tokens, and authorization headers
|
|
88
|
+
* @param {string} strObj
|
|
89
|
+
* @returns stringified object with secret values replaced (except arrays)
|
|
90
|
+
*/
|
|
91
|
+
const sanitizeRoundOne = function (strObj) {
|
|
92
|
+
|
|
93
|
+
strObj = sanitizeInput(strObj);
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
This regex will produce 2 groups for each match.
|
|
97
|
+
Group 1 will have object key/values and = param value pairs from strings such as query strings.
|
|
98
|
+
Group 2 will have authorization header keys
|
|
99
|
+
View/Edit this regex: https://regex101.com/r/IJp35p/3
|
|
100
|
+
*/
|
|
101
|
+
const regex1 = new RegExp(/(?:"?[a-z0-9_\-]{0,256}(?:key|secret|token)[a-z0-9_\-]{0,256}"?\s{0,10}(?::|=)\s{0,10}\"?(?!null|true|false)([a-z0-9+_:\.\-\/]{1,1024})|"Authorization":"[a-z0-9+:_\-\/]{1,1024}\s(.{1,1024}?(?<!\\)(?=")))/, "gi");
|
|
102
|
+
//const _regex1 = new RegExp(/(?:"?[a-z0-9_\-]{0,256}(?:key|secret|token)[a-z0-9_\-]{0,256}"?\s*(?::|=)\s*\"?(?!null|true|false)([a-z0-9+_:\.\-\/]{1,1024})|"Authorization":"[a-z0-9+:_\-\/]{1,1024}\s([^"]{1,1024}))/, "gi");
|
|
103
|
+
|
|
104
|
+
// find matches
|
|
105
|
+
let matches = strObj.matchAll(regex1);
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
We will do a loop, sort, then another loop,
|
|
109
|
+
but we don't expect 100s of matches anyway.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
// simplify the array of matches
|
|
113
|
+
let matchList = [];
|
|
114
|
+
for (const match of matches) {
|
|
115
|
+
let segment = match[0];
|
|
116
|
+
let secret = (match[1] !== undefined) ? match[1] : match[2]; // we only expect a result in Group 1 or Group 2, not both
|
|
117
|
+
matchList.push({ segment, secret});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// sort so we are replacing the largest strings first
|
|
121
|
+
matchList.sort(function (a, b) {
|
|
122
|
+
return b.segment.length - a.segment.length;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Perform replacecements
|
|
126
|
+
for (const match of matchList) {
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
Determine if we should obfuscate as string or number
|
|
130
|
+
If we have an object such as: { pin:37832481234 }
|
|
131
|
+
We will get a JSON parse error if we replace a number as *****1234
|
|
132
|
+
So we need to replace it as a number such as 99999991234 so that
|
|
133
|
+
when it parses from a string back to an object it looks like: { pin:99999991234 }
|
|
134
|
+
However, we want to treat strings as strings:
|
|
135
|
+
{ pin:"3783281234" } => { pin:"**********1234" }
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
// see if character right before secret is : (stringify will place a number right after : without quotes, and we'll ignore =)
|
|
139
|
+
let obf = (match.segment.charAt(match.segment.length - match.secret.length-1) === ':')
|
|
140
|
+
? obfuscate(match.secret, {char: 9}) // pad with 9
|
|
141
|
+
: obfuscate(match.secret); // pad normally
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
2 steps. Replace secret in match, then replace match in strObj
|
|
145
|
+
This ensures we keep the stringified object true to form for
|
|
146
|
+
converting back to obj
|
|
147
|
+
*/
|
|
148
|
+
let str = match.segment.replace(match.secret, obf); // replace secret in match
|
|
149
|
+
strObj = strObj.replace(match.segment, str); // find the old match and replace it with the new one
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return strObj;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Find secret, key, and token arrays in stringified object
|
|
158
|
+
* @param {string} strObj
|
|
159
|
+
* @returns stringified object with array of secrets replaced
|
|
160
|
+
*/
|
|
161
|
+
const sanitizeRoundTwo = function(strObj) {
|
|
162
|
+
|
|
163
|
+
strObj = sanitizeInput(strObj);
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
This regex will grab object keys matching the key|secret|token names which have arrays
|
|
167
|
+
https://regex101.com/r/dFNu4x/3
|
|
168
|
+
*/
|
|
169
|
+
const regex2 = new RegExp(/\"[a-z0-9_\-]{0,256}(?:key|secret|token)[a-z0-9_\-]{0,256}\":\[([a-z0-9+_:\.\-\/\",]{1,1024})\]/, "gi");
|
|
170
|
+
const regex3 = new RegExp(/[^,\"]{1,1024}/, "gi");
|
|
171
|
+
|
|
172
|
+
// find matches
|
|
173
|
+
let arrayMatches = strObj.matchAll(regex2);
|
|
174
|
+
|
|
175
|
+
// simplify the array of matches
|
|
176
|
+
let matchList2 = [];
|
|
177
|
+
for (const match of arrayMatches) {
|
|
178
|
+
let segment = match[0];
|
|
179
|
+
let secrets = match[1];
|
|
180
|
+
matchList2.push({ segment, secrets});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// sort so we are replacing the largest strings first
|
|
184
|
+
matchList2.sort(function (a, b) {
|
|
185
|
+
return b.segment.length - a.segment.length;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
for (const match of matchList2) {
|
|
189
|
+
let secrets = match.secrets.matchAll(regex3);
|
|
190
|
+
let list = [];
|
|
191
|
+
for (const secret of secrets) {
|
|
192
|
+
list.push(obfuscate(secret[0]));
|
|
193
|
+
}
|
|
194
|
+
let csv = `"${list.join('","')}"`;
|
|
195
|
+
let str = match.segment.replace(match.secrets, csv);
|
|
196
|
+
strObj = strObj.replace(match.segment, str);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return strObj;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// convert back to object
|
|
203
|
+
sanitizedObj = JSON.parse(sanitizeRoundTwo(sanitizeRoundOne(strObj)));
|
|
204
|
+
|
|
205
|
+
} catch (error) {
|
|
206
|
+
//DebugAndLog.error(`Error sanitizing object. Skipping: ${error.message}`, error.stack);
|
|
207
|
+
sanitizedObj = {"message": "Error sanitizing object"};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return sanitizedObj;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Hash JSON objects and arrays to determine matches (contain
|
|
215
|
+
* the same keys, values, and nesting.
|
|
216
|
+
*
|
|
217
|
+
* Works best with JSON data objects that survive JSON.stringify().
|
|
218
|
+
* If the data object passed to it contains classes or specialized
|
|
219
|
+
* objects (like Date), JSON.stringify() will attempt to use a
|
|
220
|
+
* .toJSON() method to convert the object. DataTypes of Symbols and
|
|
221
|
+
* Functions will not survive this process.
|
|
222
|
+
|
|
223
|
+
* @param {string} algorithm
|
|
224
|
+
* @param {Object|Array|BigInt|Number|String|Boolean} data to hash
|
|
225
|
+
* @param {{salt: string, iterations: number}} options
|
|
226
|
+
* @returns {string} Reproducible hash in hex
|
|
227
|
+
*/
|
|
228
|
+
const hashThisData = function(algorithm, data, options = {}) {
|
|
229
|
+
|
|
230
|
+
// set default values for options
|
|
231
|
+
if ( !( "salt" in options) ) { options.salt = ""; }
|
|
232
|
+
if ( !( "iterations" in options) || options.iterations < 1 ) { options.iterations = 1; }
|
|
233
|
+
if ( !( "skipParse" in options) ) { options.skipParse = false; } // used so we don't parse during recursion
|
|
234
|
+
|
|
235
|
+
// if it is an object or array, then parse it to remove non-data elements (functions, etc)
|
|
236
|
+
if ( !options.skipParse && (typeof data === "object" || Array.isArray(data))) {
|
|
237
|
+
data = JSON.parse(JSON.stringify(data, (key, value) => {
|
|
238
|
+
switch (typeof value) {
|
|
239
|
+
case 'bigint':
|
|
240
|
+
return value.toString();
|
|
241
|
+
case 'undefined':
|
|
242
|
+
return 'undefined';
|
|
243
|
+
default:
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
}));
|
|
247
|
+
options.skipParse = true; // set to true so we don't parse during recursion
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
|
|
251
|
+
const dataType = (data !== null && !Array.isArray(data)) ? typeof data : (Array.isArray(data)) ? "array" : "null";
|
|
252
|
+
|
|
253
|
+
if (data === null) { data = "null" }
|
|
254
|
+
if (data === undefined) { data = "undefined" }
|
|
255
|
+
|
|
256
|
+
let valueStr = "";
|
|
257
|
+
|
|
258
|
+
if (dataType === "array" || dataType === "object") {
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
We will iterate through the keys and values and generate a reproducible data string.
|
|
262
|
+
(sorted by object key or array value)
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
let arrayOfStuff = [];
|
|
266
|
+
|
|
267
|
+
// copy the named keys and alphabetize (or generate index for array) .
|
|
268
|
+
let keys = (dataType === "array")
|
|
269
|
+
? Array.from({ length: data.length }, (value, index) => index)
|
|
270
|
+
: Object.keys(data).sort();
|
|
271
|
+
|
|
272
|
+
// iterate through the keys alphabetically and add the key and value to the arrayOfStuff
|
|
273
|
+
keys.forEach((key) => {
|
|
274
|
+
// clone options
|
|
275
|
+
const opts = JSON.parse(JSON.stringify(options));
|
|
276
|
+
opts.iterations = 1; // don't iterate during recursion, only at end
|
|
277
|
+
|
|
278
|
+
const value = hashThisData(algorithm, data[key], opts);
|
|
279
|
+
arrayOfStuff.push( `${(dataType !== "array" ? key : "$array")}:::${dataType}:::${value}` );
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
valueStr = arrayOfStuff.sort().join("|||");
|
|
283
|
+
|
|
284
|
+
} else {
|
|
285
|
+
valueStr = `-:::${dataType}:::${data.toString()}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const hash = crypto.createHash(algorithm);
|
|
289
|
+
let hashOfData = "";
|
|
290
|
+
|
|
291
|
+
// hash for the number of iterations
|
|
292
|
+
for (let i = 0; i < options.iterations; i++) {
|
|
293
|
+
hash.update(valueStr + hashOfData + options.salt);
|
|
294
|
+
hashOfData = hash.digest('hex');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return hashOfData;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
module.exports = {
|
|
301
|
+
printMsg,
|
|
302
|
+
sanitize,
|
|
303
|
+
obfuscate,
|
|
304
|
+
hashThisData
|
|
305
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
const {AWS} = require('./AWS.classes');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Node version in 0.0.0 format retrieved from process.versions.node if present. '0.0.0' if not present.
|
|
6
|
+
* @type {string}
|
|
7
|
+
*/
|
|
8
|
+
const nodeVer = AWS.NODE_VER;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Node Major version. This is the first number in the version string. '20.1.6' would return 20 as a number.
|
|
12
|
+
* @type {number}
|
|
13
|
+
*/
|
|
14
|
+
const nodeVerMajor = AWS.NODE_VER_MAJOR;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Node Minor version. This is the second number in the version string. '20.31.6' would return 31 as a number.
|
|
18
|
+
* @type {number}
|
|
19
|
+
*/
|
|
20
|
+
const nodeVerMinor = AWS.NODE_VER_MINOR;
|
|
21
|
+
|
|
22
|
+
const nodeVerMajorMinor = AWS.NODE_VER_MAJOR_MINOR;
|
|
23
|
+
|
|
24
|
+
if (nodeVerMajor < 16) {
|
|
25
|
+
console.error(`Node.js version 16 or higher is required for @chadkluck/cache-data. Version ${nodeVer} detected. Please install at least Node version 16 (>18 preferred) in your environment.`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
nodeVer,
|
|
31
|
+
nodeVerMajor,
|
|
32
|
+
nodeVerMinor,
|
|
33
|
+
nodeVerMajorMinor
|
|
34
|
+
}
|