@catbee/utils 2.0.5 → 2.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.
- package/README.md +1 -0
- package/context-store/index.cjs +97 -76
- package/context-store/index.d.ts +74 -57
- package/context-store/index.mjs +97 -77
- package/healthz-server/index.cjs +356 -0
- package/healthz-server/index.d.ts +291 -0
- package/healthz-server/index.mjs +352 -0
- package/index.cjs +7 -0
- package/index.d.ts +1 -0
- package/index.mjs +1 -0
- package/logger/index.cjs +174 -76
- package/logger/index.d.ts +29 -18
- package/logger/index.mjs +174 -77
- package/package.json +12 -7
- package/server/index.cjs +216 -74
- package/server/index.d.ts +72 -14
- package/server/index.mjs +215 -74
package/logger/index.mjs
CHANGED
|
@@ -51,57 +51,140 @@ var defaultSensitiveFields = [
|
|
|
51
51
|
"passphrase",
|
|
52
52
|
"otp",
|
|
53
53
|
"api_secret",
|
|
54
|
-
"token_secret"
|
|
54
|
+
"token_secret",
|
|
55
|
+
"cookie",
|
|
56
|
+
"cookies",
|
|
57
|
+
"set-cookie",
|
|
58
|
+
"session_id",
|
|
59
|
+
"credit_card",
|
|
60
|
+
"card_number",
|
|
61
|
+
"cvv",
|
|
62
|
+
"cvc",
|
|
63
|
+
"ssn",
|
|
64
|
+
"pin"
|
|
55
65
|
];
|
|
56
|
-
var
|
|
57
|
-
"
|
|
58
|
-
"req.headers.authorization",
|
|
59
|
-
"req.headers.cookie",
|
|
60
|
-
'req.headers["x-api-key"]',
|
|
61
|
-
'req.headers["x-auth-token"]',
|
|
62
|
-
'req.headers["x-access-token"]',
|
|
63
|
-
"req.body.password",
|
|
64
|
-
"req.body.token",
|
|
65
|
-
"req.body.secret",
|
|
66
|
-
"req.query.token",
|
|
67
|
-
"req.query.api_key",
|
|
68
|
-
"req.query.apiKey",
|
|
69
|
-
"res.authorization",
|
|
70
|
-
"res.headers.authorization",
|
|
71
|
-
'res.headers["set-cookie"]',
|
|
66
|
+
var requestSubPaths = [
|
|
67
|
+
"authorization",
|
|
72
68
|
"headers.authorization",
|
|
69
|
+
"headers.cookie",
|
|
73
70
|
"headers.cookies",
|
|
74
71
|
'headers["set-cookie"]',
|
|
75
72
|
'headers["x-api-key"]',
|
|
76
73
|
'headers["x-auth-token"]',
|
|
77
74
|
'headers["x-access-token"]',
|
|
75
|
+
"body.password",
|
|
76
|
+
"body.token",
|
|
77
|
+
"body.secret",
|
|
78
|
+
"query.token",
|
|
79
|
+
"query.api_key",
|
|
80
|
+
"query.apiKey",
|
|
81
|
+
"url",
|
|
82
|
+
"originalUrl"
|
|
83
|
+
];
|
|
84
|
+
var responseSubPaths = [
|
|
85
|
+
"authorization",
|
|
86
|
+
"headers.authorization",
|
|
87
|
+
'headers["set-cookie"]'
|
|
88
|
+
];
|
|
89
|
+
var commonHeaderPaths = [
|
|
90
|
+
"headers.authorization",
|
|
91
|
+
"headers.cookie",
|
|
92
|
+
"headers.cookies",
|
|
93
|
+
'headers["set-cookie"]',
|
|
94
|
+
'headers["x-api-key"]',
|
|
95
|
+
'headers["x-auth-token"]',
|
|
96
|
+
'headers["x-access-token"]'
|
|
97
|
+
];
|
|
98
|
+
var defaultRedactPaths = [
|
|
99
|
+
...[
|
|
100
|
+
"req",
|
|
101
|
+
"request"
|
|
102
|
+
].flatMap((p) => requestSubPaths.map((sp) => `${p}.${sp}`)),
|
|
103
|
+
...[
|
|
104
|
+
"res",
|
|
105
|
+
"response"
|
|
106
|
+
].flatMap((p) => responseSubPaths.map((sp) => `${p}.${sp}`)),
|
|
107
|
+
...commonHeaderPaths,
|
|
78
108
|
"url",
|
|
79
109
|
"uri",
|
|
80
110
|
"href",
|
|
81
111
|
"redirect_uri",
|
|
82
112
|
"redirectUri"
|
|
83
113
|
];
|
|
84
|
-
var
|
|
114
|
+
var escapeRegExp = /* @__PURE__ */ __name((value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "escapeRegExp");
|
|
115
|
+
function buildUrlRedactRegex(lowerSensitiveFields) {
|
|
116
|
+
if (lowerSensitiveFields.length === 0) return null;
|
|
117
|
+
const pattern = lowerSensitiveFields.map(escapeRegExp).join("|");
|
|
118
|
+
return new RegExp(`([?&](${pattern})=)[^&#]*`, "gi");
|
|
119
|
+
}
|
|
120
|
+
__name(buildUrlRedactRegex, "buildUrlRedactRegex");
|
|
121
|
+
var URL_PATH_KEYS = /* @__PURE__ */ new Set([
|
|
122
|
+
"url",
|
|
123
|
+
"uri",
|
|
124
|
+
"href",
|
|
125
|
+
"originalurl",
|
|
126
|
+
"redirect_uri",
|
|
127
|
+
"redirecturi"
|
|
128
|
+
]);
|
|
129
|
+
var cachedExpandedFields = null;
|
|
130
|
+
var cachedLowerSensitiveFields = null;
|
|
131
|
+
var cachedLowerSensitiveSet = null;
|
|
132
|
+
var cachedNormalizedMultiWordFields = null;
|
|
133
|
+
var cachedUrlRegex = null;
|
|
134
|
+
function invalidateSensitiveFieldsCache() {
|
|
135
|
+
cachedExpandedFields = null;
|
|
136
|
+
cachedLowerSensitiveFields = null;
|
|
137
|
+
cachedLowerSensitiveSet = null;
|
|
138
|
+
cachedNormalizedMultiWordFields = null;
|
|
139
|
+
cachedUrlRegex = null;
|
|
140
|
+
}
|
|
141
|
+
__name(invalidateSensitiveFieldsCache, "invalidateSensitiveFieldsCache");
|
|
142
|
+
function extractNormalizedMultiWordFields(fields) {
|
|
143
|
+
return Array.from(new Set(fields.map((f) => f.replace(/[-]/g, "_")).filter((f) => f.includes("_")).map((f) => `_${f}_`)));
|
|
144
|
+
}
|
|
145
|
+
__name(extractNormalizedMultiWordFields, "extractNormalizedMultiWordFields");
|
|
146
|
+
function isSensitivePathSegment(segment, lowerFieldsSet, multiWordNormalizedList) {
|
|
147
|
+
if (!segment || typeof segment !== "string") return false;
|
|
148
|
+
const lower = segment.toLowerCase();
|
|
149
|
+
if (lowerFieldsSet.has(lower)) return true;
|
|
150
|
+
const tokens = segment.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])/g).filter(Boolean);
|
|
151
|
+
if (tokens.some((t) => lowerFieldsSet.has(t.toLowerCase()))) return true;
|
|
152
|
+
if (multiWordNormalizedList.length === 0) return false;
|
|
153
|
+
const normalized = `_${segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[-]/g, "_")}_`;
|
|
154
|
+
return multiWordNormalizedList.some((fNorm) => normalized.includes(fNorm));
|
|
155
|
+
}
|
|
156
|
+
__name(isSensitivePathSegment, "isSensitivePathSegment");
|
|
157
|
+
function getExpandedSensitiveFields() {
|
|
158
|
+
if (!cachedExpandedFields) {
|
|
159
|
+
cachedExpandedFields = expandSensitiveFields(defaultSensitiveFields);
|
|
160
|
+
cachedLowerSensitiveFields = cachedExpandedFields.map((f) => f.toLowerCase());
|
|
161
|
+
cachedLowerSensitiveSet = new Set(cachedLowerSensitiveFields);
|
|
162
|
+
cachedNormalizedMultiWordFields = extractNormalizedMultiWordFields(cachedLowerSensitiveFields);
|
|
163
|
+
cachedUrlRegex = buildUrlRedactRegex(cachedLowerSensitiveFields);
|
|
164
|
+
}
|
|
165
|
+
return cachedExpandedFields;
|
|
166
|
+
}
|
|
167
|
+
__name(getExpandedSensitiveFields, "getExpandedSensitiveFields");
|
|
168
|
+
var globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields) => {
|
|
85
169
|
if (typeof value !== "string") return "***";
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
170
|
+
const stringPath = path.filter((p) => typeof p === "string");
|
|
171
|
+
const lowerPath = stringPath.map((p) => p.toLowerCase());
|
|
172
|
+
const isCustomFields = sensitiveFields !== void 0;
|
|
173
|
+
if (!isCustomFields) {
|
|
174
|
+
getExpandedSensitiveFields();
|
|
91
175
|
}
|
|
92
|
-
|
|
93
|
-
|
|
176
|
+
const lowerFieldsList = isCustomFields ? sensitiveFields.map((f) => f.toLowerCase()) : cachedLowerSensitiveFields;
|
|
177
|
+
const lowerFieldsSet = isCustomFields ? new Set(lowerFieldsList) : cachedLowerSensitiveSet;
|
|
178
|
+
const multiWordList = isCustomFields ? extractNormalizedMultiWordFields(lowerFieldsList) : cachedNormalizedMultiWordFields;
|
|
179
|
+
if (lowerPath.some((p) => lowerFieldsSet.has(p))) return "***";
|
|
180
|
+
if (lowerPath.some((p) => URL_PATH_KEYS.has(p) || p.includes("redirect_uri") || p.includes("redirecturi"))) {
|
|
181
|
+
if (lowerFieldsList.length === 0) return value;
|
|
182
|
+
const urlRegex = isCustomFields ? buildUrlRedactRegex(lowerFieldsList) : cachedUrlRegex;
|
|
183
|
+
return urlRegex ? value.replace(urlRegex, "$1***") : value;
|
|
94
184
|
}
|
|
95
|
-
if (
|
|
185
|
+
if (stringPath.some((p) => isSensitivePathSegment(p, lowerFieldsSet, multiWordList))) {
|
|
96
186
|
return "***";
|
|
97
187
|
}
|
|
98
|
-
if (path.length > 0 && ![
|
|
99
|
-
"req",
|
|
100
|
-
"res",
|
|
101
|
-
"headers"
|
|
102
|
-
].includes(lowerPath[0])) {
|
|
103
|
-
return value;
|
|
104
|
-
}
|
|
105
188
|
return value;
|
|
106
189
|
}, "globalRedactCensor");
|
|
107
190
|
function setRedactCensor(fn) {
|
|
@@ -117,38 +200,36 @@ function redact(value, path, sensitiveFields) {
|
|
|
117
200
|
}
|
|
118
201
|
__name(redact, "redact");
|
|
119
202
|
function addRedactFields(fields) {
|
|
203
|
+
addSensitiveFields(fields);
|
|
120
204
|
const prev = globalRedactCensor;
|
|
121
205
|
globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields = getExpandedSensitiveFields()) => prev(value, path, [
|
|
122
206
|
...sensitiveFields || [],
|
|
123
207
|
...fields
|
|
124
208
|
]), "globalRedactCensor");
|
|
125
|
-
cachedExpandedFields = null;
|
|
126
209
|
}
|
|
127
210
|
__name(addRedactFields, "addRedactFields");
|
|
128
|
-
var cachedExpandedFields = null;
|
|
129
|
-
function getExpandedSensitiveFields() {
|
|
130
|
-
cachedExpandedFields ??= expandSensitiveFields(defaultSensitiveFields);
|
|
131
|
-
return cachedExpandedFields;
|
|
132
|
-
}
|
|
133
|
-
__name(getExpandedSensitiveFields, "getExpandedSensitiveFields");
|
|
134
211
|
function setSensitiveFields(fields) {
|
|
135
212
|
defaultSensitiveFields.splice(0, defaultSensitiveFields.length, ...fields);
|
|
136
|
-
|
|
213
|
+
invalidateSensitiveFieldsCache();
|
|
137
214
|
}
|
|
138
215
|
__name(setSensitiveFields, "setSensitiveFields");
|
|
139
216
|
function addSensitiveFields(fields) {
|
|
140
217
|
defaultSensitiveFields.push(...fields);
|
|
141
|
-
|
|
218
|
+
invalidateSensitiveFieldsCache();
|
|
142
219
|
}
|
|
143
220
|
__name(addSensitiveFields, "addSensitiveFields");
|
|
144
221
|
var _globalThis = typeof globalThis === "object" ? globalThis : global;
|
|
145
222
|
var _global = _globalThis;
|
|
223
|
+
function resetLogger() {
|
|
224
|
+
delete _global[GLOBAL_LOGGER_KEY];
|
|
225
|
+
}
|
|
226
|
+
__name(resetLogger, "resetLogger");
|
|
146
227
|
function setupLogger(isGlobal = true) {
|
|
147
228
|
const sensitiveFields = getExpandedSensitiveFields();
|
|
148
229
|
const { logger: loggerConfig } = getCatbeeGlobalConfig();
|
|
149
230
|
const paths = /* @__PURE__ */ new Set([
|
|
150
231
|
...defaultRedactPaths,
|
|
151
|
-
...sensitiveFields.flatMap((field) => generateDeepPaths(field,
|
|
232
|
+
...sensitiveFields.flatMap((field) => generateDeepPaths(field, 5))
|
|
152
233
|
]);
|
|
153
234
|
const logParams = {
|
|
154
235
|
name: loggerConfig?.name ?? "@catbee/utils",
|
|
@@ -170,56 +251,50 @@ function setupLogger(isGlobal = true) {
|
|
|
170
251
|
let logger;
|
|
171
252
|
const logDir = loggerConfig?.dir?.trim();
|
|
172
253
|
const hasFileLogging = Boolean(logDir);
|
|
254
|
+
const destination = logDir ? `${logDir.replace(/[/\\]+$/, "")}/app.log` : "app.log";
|
|
255
|
+
const prettyOptions = {
|
|
256
|
+
colorize: loggerConfig?.colorize,
|
|
257
|
+
translateTime: "SYS:standard",
|
|
258
|
+
ignore: "pid,hostname",
|
|
259
|
+
singleLine: loggerConfig?.singleLine,
|
|
260
|
+
levelFirst: true
|
|
261
|
+
};
|
|
262
|
+
const fileOptions = {
|
|
263
|
+
destination,
|
|
264
|
+
mkdir: true
|
|
265
|
+
};
|
|
173
266
|
if (hasFileLogging && loggerConfig?.pretty) {
|
|
174
267
|
logger = pino(logParams, pino.transport({
|
|
175
268
|
targets: [
|
|
176
269
|
{
|
|
177
270
|
target: "pino-pretty",
|
|
178
271
|
level: loggerConfig?.level ?? "info",
|
|
179
|
-
options:
|
|
180
|
-
colorize: loggerConfig?.colorize,
|
|
181
|
-
translateTime: "SYS:standard",
|
|
182
|
-
ignore: "pid,hostname",
|
|
183
|
-
singleLine: loggerConfig?.singleLine,
|
|
184
|
-
levelFirst: true
|
|
185
|
-
}
|
|
272
|
+
options: prettyOptions
|
|
186
273
|
},
|
|
187
274
|
{
|
|
188
275
|
target: "pino/file",
|
|
189
276
|
level: loggerConfig?.level ?? "info",
|
|
190
|
-
options:
|
|
191
|
-
destination: `${logDir}/app.log`,
|
|
192
|
-
mkdir: true
|
|
193
|
-
}
|
|
277
|
+
options: fileOptions
|
|
194
278
|
}
|
|
195
279
|
]
|
|
196
280
|
}));
|
|
197
281
|
} else if (hasFileLogging) {
|
|
198
282
|
logger = pino(logParams, pino.transport({
|
|
199
283
|
target: "pino/file",
|
|
200
|
-
options:
|
|
201
|
-
destination: `${logDir}/app.log`,
|
|
202
|
-
mkdir: true
|
|
203
|
-
}
|
|
284
|
+
options: fileOptions
|
|
204
285
|
}));
|
|
205
286
|
} else if (loggerConfig?.pretty) {
|
|
206
287
|
logger = pino(logParams, pino.transport({
|
|
207
288
|
target: "pino-pretty",
|
|
208
|
-
options:
|
|
209
|
-
colorize: loggerConfig?.colorize,
|
|
210
|
-
translateTime: "SYS:standard",
|
|
211
|
-
ignore: "pid,hostname",
|
|
212
|
-
singleLine: loggerConfig?.singleLine,
|
|
213
|
-
levelFirst: true
|
|
214
|
-
}
|
|
289
|
+
options: prettyOptions
|
|
215
290
|
}));
|
|
216
291
|
} else {
|
|
217
292
|
logger = pino(logParams);
|
|
218
293
|
}
|
|
219
294
|
if (isGlobal) {
|
|
220
295
|
_global[GLOBAL_LOGGER_KEY] = logger;
|
|
221
|
-
|
|
222
|
-
logDir
|
|
296
|
+
logger.debug({
|
|
297
|
+
logDir: logDir || "none"
|
|
223
298
|
}, "Global Logger Initialized");
|
|
224
299
|
}
|
|
225
300
|
return logger;
|
|
@@ -247,9 +322,15 @@ function createRequestLogger(requestId, additionalContext = {}) {
|
|
|
247
322
|
requestId,
|
|
248
323
|
...additionalContext
|
|
249
324
|
});
|
|
325
|
+
let stored = false;
|
|
250
326
|
try {
|
|
251
|
-
ContextStore.
|
|
327
|
+
if (typeof ContextStore.getAll !== "function" || ContextStore.getAll() !== void 0) {
|
|
328
|
+
ContextStore.set(StoreKeys.LOGGER, logger);
|
|
329
|
+
stored = true;
|
|
330
|
+
}
|
|
252
331
|
} catch {
|
|
332
|
+
}
|
|
333
|
+
if (!stored) {
|
|
253
334
|
logger.debug("Failed to store logger in context - AsyncLocalStorage not initialized");
|
|
254
335
|
}
|
|
255
336
|
return logger;
|
|
@@ -276,13 +357,29 @@ function expandSensitiveFields(fields) {
|
|
|
276
357
|
}
|
|
277
358
|
__name(expandSensitiveFields, "expandSensitiveFields");
|
|
278
359
|
function expandSensitiveField(field) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
360
|
+
if (!field || typeof field !== "string") {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
const trimmed = field.trim();
|
|
364
|
+
if (!trimmed) {
|
|
365
|
+
return [
|
|
366
|
+
field
|
|
367
|
+
];
|
|
368
|
+
}
|
|
369
|
+
const parts = trimmed.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])/g).filter(Boolean);
|
|
370
|
+
if (parts.length === 0) {
|
|
371
|
+
return [
|
|
372
|
+
field
|
|
373
|
+
];
|
|
374
|
+
}
|
|
375
|
+
const lowerParts = parts.map((p) => p.toLowerCase());
|
|
376
|
+
const upperParts = parts.map((p) => p.toUpperCase());
|
|
377
|
+
const camel = lowerParts[0] + lowerParts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
378
|
+
const mergedLower = lowerParts.join("");
|
|
379
|
+
const mergedUpper = upperParts.join("");
|
|
380
|
+
const kebab = lowerParts.join("-");
|
|
381
|
+
const snakeLower = lowerParts.join("_");
|
|
382
|
+
const snakeUpper = upperParts.join("_");
|
|
286
383
|
return Array.from(/* @__PURE__ */ new Set([
|
|
287
384
|
field,
|
|
288
385
|
camel,
|
|
@@ -294,7 +391,7 @@ function expandSensitiveField(field) {
|
|
|
294
391
|
]));
|
|
295
392
|
}
|
|
296
393
|
__name(expandSensitiveField, "expandSensitiveField");
|
|
297
|
-
function generateDeepPaths(field, depth) {
|
|
394
|
+
function generateDeepPaths(field, depth = 5) {
|
|
298
395
|
const set = /* @__PURE__ */ new Set([
|
|
299
396
|
field
|
|
300
397
|
]);
|
|
@@ -309,4 +406,4 @@ function generateDeepPaths(field, depth) {
|
|
|
309
406
|
}
|
|
310
407
|
__name(generateDeepPaths, "generateDeepPaths");
|
|
311
408
|
|
|
312
|
-
export { _globalThis, addRedactFields, addSensitiveFields, createChildLogger, createRequestLogger, defaultSensitiveFields, expandSensitiveField, expandSensitiveFields, generateDeepPaths, getExpandedSensitiveFields, getLogger, getRedactCensor, logError, redact, setRedactCensor, setSensitiveFields };
|
|
409
|
+
export { _globalThis, addRedactFields, addSensitiveFields, createChildLogger, createRequestLogger, defaultSensitiveFields, expandSensitiveField, expandSensitiveFields, generateDeepPaths, getExpandedSensitiveFields, getLogger, getRedactCensor, logError, redact, resetLogger, setRedactCensor, setSensitiveFields };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catbee/utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -20,17 +20,17 @@
|
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"pino": "^10.
|
|
23
|
+
"pino": "^10.4.0"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"@scalar/express-api-reference": "^0.
|
|
26
|
+
"@scalar/express-api-reference": "^0.10.26",
|
|
27
27
|
"abort-controller": "^3.0.0",
|
|
28
|
-
"compression": "^1.8.
|
|
28
|
+
"compression": "^1.8.2",
|
|
29
29
|
"cookie-parser": "^1.4.7",
|
|
30
30
|
"cors": "^2.8.6",
|
|
31
|
-
"dotenv": "^
|
|
32
|
-
"express-rate-limit": "^8.
|
|
33
|
-
"helmet": "^8.
|
|
31
|
+
"dotenv": "^18.0.5",
|
|
32
|
+
"express-rate-limit": "^8.7.0",
|
|
33
|
+
"helmet": "^8.3.0",
|
|
34
34
|
"pino-pretty": "^13.1.3",
|
|
35
35
|
"reflect-metadata": "^0.2.2"
|
|
36
36
|
},
|
|
@@ -145,6 +145,11 @@
|
|
|
145
145
|
"require": "./fs/index.cjs",
|
|
146
146
|
"types": "./fs/index.d.ts"
|
|
147
147
|
},
|
|
148
|
+
"./healthz-server": {
|
|
149
|
+
"import": "./healthz-server/index.mjs",
|
|
150
|
+
"require": "./healthz-server/index.cjs",
|
|
151
|
+
"types": "./healthz-server/index.d.ts"
|
|
152
|
+
},
|
|
148
153
|
"./http-status-codes": {
|
|
149
154
|
"import": "./http-status-codes/index.mjs",
|
|
150
155
|
"require": "./http-status-codes/index.cjs",
|