@devilsdev/rag-pipeline-utils 2.2.0 → 2.3.0
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 +323 -722
- package/contracts/contract-schema.json +189 -0
- package/contracts/reranker-contract.json +69 -0
- package/dist/index.cjs +19 -0
- package/dist/index.mjs +15 -1
- package/package.json +4 -2
- package/src/cli/commands/wizard.js +627 -0
- package/src/cli/input-sanitization-middleware.js +356 -0
- package/src/cli/templates/development.template.json +64 -0
- package/src/cli/templates/minimal.template.json +33 -0
- package/src/cli/templates/production.template.json +104 -0
- package/src/cli/templates/testing.template.json +45 -0
- package/src/config/connection-pool-config.json +259 -0
- package/src/config/memory-optimization.js +414 -0
- package/src/config/redaction-patterns.json +150 -0
- package/src/config/sanitization-rules.json +323 -0
- package/src/core/performance/streaming-safeguards.js +10 -1
- package/src/core/plugin-registry.js +275 -0
- package/src/dag/core/error-context.js +391 -0
- package/src/dag/core/execution-scheduler.js +350 -0
- package/src/dag/core/topology-validation.js +415 -0
- package/src/dag/dag-engine.js +26 -424
- package/src/dev/README.md +126 -0
- package/src/dev/dev-server.js +356 -0
- package/src/dev/examples/hot-reload-usage.js +355 -0
- package/src/dev/hot-reload.js +807 -0
- package/src/enterprise/sso-integration.js +184 -15
- package/src/index.js +19 -0
- package/src/observability/event-logger.js +62 -2
- package/src/plugins/optimized-openai-embedder.js +169 -0
- package/src/reranker/llm-reranker.js +30 -4
- package/src/security/jwt-config.js +431 -0
- package/src/security/jwt-validator.js +701 -0
- package/src/utils/api-client-pool-integration.js +436 -0
- package/src/utils/batch-processor.js +710 -0
- package/src/utils/connection-pool.js +605 -0
- package/src/utils/contract-validator.js +460 -0
- package/src/utils/error-formatter.js +603 -0
- package/src/utils/examples/error-formatter-usage.js +351 -0
- package/src/utils/input-sanitizer.js +967 -0
- package/src/utils/memory-monitor.js +800 -0
- package/src/utils/rate-limiter.js +244 -0
- package/src/utils/secure-logger-config.js +352 -0
- package/src/utils/secure-logger.js +576 -0
- package/src/utils/secure-logging.js +338 -0
- package/src/utils/structured-logger.js +32 -5
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter Utility
|
|
3
|
+
* Protects against brute force attacks and abuse
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Sliding window rate limiting
|
|
7
|
+
* - Per-identifier tracking (IP, user, tenant)
|
|
8
|
+
* - Automatic cleanup of expired entries
|
|
9
|
+
* - Configurable thresholds and windows
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
|
|
14
|
+
class RateLimiter {
|
|
15
|
+
/**
|
|
16
|
+
* Create a rate limiter instance
|
|
17
|
+
* @param {Object} options - Configuration options
|
|
18
|
+
* @param {number} options.maxAttempts - Maximum attempts allowed (default: 5)
|
|
19
|
+
* @param {number} options.windowMs - Time window in milliseconds (default: 15 minutes)
|
|
20
|
+
* @param {number} options.blockDurationMs - How long to block after exceeding limit (default: 1 hour)
|
|
21
|
+
* @param {number} options.cleanupIntervalMs - How often to cleanup expired entries (default: 5 minutes)
|
|
22
|
+
*/
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.maxAttempts = options.maxAttempts || 5;
|
|
25
|
+
this.windowMs = options.windowMs || 15 * 60 * 1000; // 15 minutes
|
|
26
|
+
this.blockDurationMs = options.blockDurationMs || 60 * 60 * 1000; // 1 hour
|
|
27
|
+
this.cleanupIntervalMs = options.cleanupIntervalMs || 5 * 60 * 1000; // 5 minutes
|
|
28
|
+
|
|
29
|
+
// Map of identifier -> { attempts: [], blockedUntil: timestamp }
|
|
30
|
+
this.store = new Map();
|
|
31
|
+
|
|
32
|
+
// Start automatic cleanup
|
|
33
|
+
this._startCleanup();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a request should be allowed
|
|
38
|
+
* @param {string} identifier - Unique identifier (IP, userId, tenantId, etc.)
|
|
39
|
+
* @returns {Object} { allowed: boolean, remaining: number, resetAt: Date, blockedUntil: Date|null }
|
|
40
|
+
*/
|
|
41
|
+
allowRequest(identifier) {
|
|
42
|
+
const key = this._hashIdentifier(identifier);
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
|
|
45
|
+
// Get or create record
|
|
46
|
+
let record = this.store.get(key);
|
|
47
|
+
if (!record) {
|
|
48
|
+
record = { attempts: [], blockedUntil: null };
|
|
49
|
+
this.store.set(key, record);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if currently blocked
|
|
53
|
+
if (record.blockedUntil && record.blockedUntil > now) {
|
|
54
|
+
return {
|
|
55
|
+
allowed: false,
|
|
56
|
+
remaining: 0,
|
|
57
|
+
resetAt: new Date(record.blockedUntil),
|
|
58
|
+
blockedUntil: new Date(record.blockedUntil),
|
|
59
|
+
retryAfter: Math.ceil((record.blockedUntil - now) / 1000), // seconds
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Clear expired block
|
|
64
|
+
if (record.blockedUntil && record.blockedUntil <= now) {
|
|
65
|
+
record.blockedUntil = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Remove expired attempts
|
|
69
|
+
const windowStart = now - this.windowMs;
|
|
70
|
+
record.attempts = record.attempts.filter(
|
|
71
|
+
(timestamp) => timestamp > windowStart,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Check if exceeded limit
|
|
75
|
+
if (record.attempts.length >= this.maxAttempts) {
|
|
76
|
+
// Block the identifier
|
|
77
|
+
record.blockedUntil = now + this.blockDurationMs;
|
|
78
|
+
return {
|
|
79
|
+
allowed: false,
|
|
80
|
+
remaining: 0,
|
|
81
|
+
resetAt: new Date(record.blockedUntil),
|
|
82
|
+
blockedUntil: new Date(record.blockedUntil),
|
|
83
|
+
retryAfter: Math.ceil(this.blockDurationMs / 1000), // seconds
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Allow request and record attempt
|
|
88
|
+
record.attempts.push(now);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
allowed: true,
|
|
92
|
+
remaining: this.maxAttempts - record.attempts.length,
|
|
93
|
+
resetAt: new Date(now + this.windowMs),
|
|
94
|
+
blockedUntil: null,
|
|
95
|
+
retryAfter: 0,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Manually reset rate limit for an identifier
|
|
101
|
+
* @param {string} identifier - Identifier to reset
|
|
102
|
+
*/
|
|
103
|
+
reset(identifier) {
|
|
104
|
+
const key = this._hashIdentifier(identifier);
|
|
105
|
+
this.store.delete(key);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get current status for an identifier
|
|
110
|
+
* @param {string} identifier - Identifier to check
|
|
111
|
+
* @returns {Object} Current rate limit status
|
|
112
|
+
*/
|
|
113
|
+
getStatus(identifier) {
|
|
114
|
+
const key = this._hashIdentifier(identifier);
|
|
115
|
+
const record = this.store.get(key);
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
|
|
118
|
+
if (!record) {
|
|
119
|
+
return {
|
|
120
|
+
attempts: 0,
|
|
121
|
+
remaining: this.maxAttempts,
|
|
122
|
+
blocked: false,
|
|
123
|
+
blockedUntil: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Remove expired attempts
|
|
128
|
+
const windowStart = now - this.windowMs;
|
|
129
|
+
const validAttempts = record.attempts.filter(
|
|
130
|
+
(timestamp) => timestamp > windowStart,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const blocked = !!(record.blockedUntil && record.blockedUntil > now);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
attempts: validAttempts.length,
|
|
137
|
+
remaining: Math.max(0, this.maxAttempts - validAttempts.length),
|
|
138
|
+
blocked,
|
|
139
|
+
blockedUntil: blocked ? new Date(record.blockedUntil) : null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Clean up expired entries
|
|
145
|
+
*/
|
|
146
|
+
cleanup() {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const expiredKeys = [];
|
|
149
|
+
|
|
150
|
+
for (const [key, record] of this.store.entries()) {
|
|
151
|
+
// Check if block has expired and no recent attempts
|
|
152
|
+
const blockExpired = !record.blockedUntil || record.blockedUntil < now;
|
|
153
|
+
const windowStart = now - this.windowMs;
|
|
154
|
+
const hasRecentAttempts = record.attempts.some(
|
|
155
|
+
(timestamp) => timestamp > windowStart,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (blockExpired && !hasRecentAttempts) {
|
|
159
|
+
expiredKeys.push(key);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Delete expired entries
|
|
164
|
+
expiredKeys.forEach((key) => this.store.delete(key));
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
cleaned: expiredKeys.length,
|
|
168
|
+
remaining: this.store.size,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get statistics about rate limiter usage
|
|
174
|
+
* @returns {Object} Statistics
|
|
175
|
+
*/
|
|
176
|
+
getStats() {
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
let totalAttempts = 0;
|
|
179
|
+
let blockedIdentifiers = 0;
|
|
180
|
+
let activeIdentifiers = 0;
|
|
181
|
+
|
|
182
|
+
for (const record of this.store.values()) {
|
|
183
|
+
const windowStart = now - this.windowMs;
|
|
184
|
+
const validAttempts = record.attempts.filter(
|
|
185
|
+
(timestamp) => timestamp > windowStart,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (validAttempts.length > 0) {
|
|
189
|
+
activeIdentifiers++;
|
|
190
|
+
totalAttempts += validAttempts.length;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (record.blockedUntil && record.blockedUntil > now) {
|
|
194
|
+
blockedIdentifiers++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
totalIdentifiers: this.store.size,
|
|
200
|
+
activeIdentifiers,
|
|
201
|
+
blockedIdentifiers,
|
|
202
|
+
totalAttempts,
|
|
203
|
+
averageAttemptsPerIdentifier:
|
|
204
|
+
activeIdentifiers > 0 ? totalAttempts / activeIdentifiers : 0,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Stop the rate limiter and cleanup
|
|
210
|
+
*/
|
|
211
|
+
destroy() {
|
|
212
|
+
if (this._cleanupInterval) {
|
|
213
|
+
clearInterval(this._cleanupInterval);
|
|
214
|
+
this._cleanupInterval = null;
|
|
215
|
+
}
|
|
216
|
+
this.store.clear();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Hash identifier for storage (privacy)
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
_hashIdentifier(identifier) {
|
|
224
|
+
return crypto.createHash('sha256').update(identifier).digest('hex');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Start automatic cleanup timer
|
|
229
|
+
* @private
|
|
230
|
+
*/
|
|
231
|
+
_startCleanup() {
|
|
232
|
+
this._cleanupInterval = setInterval(() => {
|
|
233
|
+
this.cleanup();
|
|
234
|
+
}, this.cleanupIntervalMs);
|
|
235
|
+
|
|
236
|
+
// Don't prevent process exit
|
|
237
|
+
if (this._cleanupInterval.unref) {
|
|
238
|
+
this._cleanupInterval.unref();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = RateLimiter;
|
|
244
|
+
module.exports.default = module.exports;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Secure Logger Configuration Loader
|
|
5
|
+
*
|
|
6
|
+
* Loads and applies redaction pattern configurations from JSON files
|
|
7
|
+
* or JavaScript objects to create configured SecureLogger instances.
|
|
8
|
+
*
|
|
9
|
+
* @module secure-logger-config
|
|
10
|
+
* @since 2.2.5
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { SecureLogger } = require('./secure-logger');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load configuration from JSON file
|
|
19
|
+
*
|
|
20
|
+
* @param {string} configPath - Path to configuration file
|
|
21
|
+
* @returns {Object} Configuration object
|
|
22
|
+
* @throws {Error} If file cannot be read or parsed
|
|
23
|
+
*
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
function loadConfigFile(configPath) {
|
|
27
|
+
try {
|
|
28
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
29
|
+
return JSON.parse(content);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Failed to load redaction config from ${configPath}: ${error.message}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Convert regex string to RegExp object
|
|
39
|
+
*
|
|
40
|
+
* @param {string} regexStr - Regex pattern as string
|
|
41
|
+
* @param {string} flags - Regex flags
|
|
42
|
+
* @returns {RegExp} Compiled regular expression
|
|
43
|
+
*
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
function compileRegex(regexStr, flags = 'g') {
|
|
47
|
+
try {
|
|
48
|
+
return new RegExp(regexStr, flags);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`Invalid regex pattern: ${regexStr} - ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Apply environment-specific configuration overrides
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} config - Base configuration
|
|
58
|
+
* @param {string} [environment] - Environment name (defaults to NODE_ENV)
|
|
59
|
+
* @returns {Object} Configuration with environment overrides applied
|
|
60
|
+
*
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
function applyEnvironmentOverrides(config, environment) {
|
|
64
|
+
const env = environment || process.env.NODE_ENV || 'development';
|
|
65
|
+
|
|
66
|
+
if (config.environments && config.environments[env]) {
|
|
67
|
+
return {
|
|
68
|
+
...config,
|
|
69
|
+
...config.environments[env],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Convert custom pattern configuration to SecureLogger format
|
|
78
|
+
*
|
|
79
|
+
* @param {Object} customPatterns - Custom patterns from config
|
|
80
|
+
* @returns {Object} Patterns in SecureLogger format
|
|
81
|
+
*
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
function convertCustomPatterns(customPatterns) {
|
|
85
|
+
const converted = {};
|
|
86
|
+
|
|
87
|
+
for (const [name, pattern] of Object.entries(customPatterns)) {
|
|
88
|
+
converted[name] = {
|
|
89
|
+
regex: compileRegex(pattern.regex, pattern.flags || 'g'),
|
|
90
|
+
replacement: pattern.replacement,
|
|
91
|
+
description: pattern.description || '',
|
|
92
|
+
enabled: pattern.enabled !== false,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return converted;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create SecureLogger from configuration
|
|
101
|
+
*
|
|
102
|
+
* @param {Object|string} config - Configuration object or path to config file
|
|
103
|
+
* @param {string} [environment] - Environment name for overrides
|
|
104
|
+
* @returns {SecureLogger} Configured SecureLogger instance
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Load from file
|
|
108
|
+
* const logger = createLoggerFromConfig('./redaction-config.json');
|
|
109
|
+
*
|
|
110
|
+
* // Load from object
|
|
111
|
+
* const logger = createLoggerFromConfig({
|
|
112
|
+
* enabled: true,
|
|
113
|
+
* redactEmails: true,
|
|
114
|
+
* customPatterns: {
|
|
115
|
+
* myPattern: {
|
|
116
|
+
* regex: '\\bMY-[A-Z0-9]+\\b',
|
|
117
|
+
* replacement: '[MY_TOKEN]'
|
|
118
|
+
* }
|
|
119
|
+
* }
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* // Load with environment override
|
|
123
|
+
* const logger = createLoggerFromConfig(config, 'production');
|
|
124
|
+
*/
|
|
125
|
+
function createLoggerFromConfig(config, environment) {
|
|
126
|
+
let configuration;
|
|
127
|
+
|
|
128
|
+
// Load from file if string path provided
|
|
129
|
+
if (typeof config === 'string') {
|
|
130
|
+
configuration = loadConfigFile(config);
|
|
131
|
+
} else {
|
|
132
|
+
configuration = config;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Apply environment-specific overrides
|
|
136
|
+
configuration = applyEnvironmentOverrides(configuration, environment);
|
|
137
|
+
|
|
138
|
+
// Build SecureLogger options
|
|
139
|
+
const options = {
|
|
140
|
+
enabled: configuration.enabled !== false,
|
|
141
|
+
trackStats: configuration.trackStats !== false,
|
|
142
|
+
redactionMarker: configuration.redactionMarker,
|
|
143
|
+
redactEmails: configuration.redactEmails || false,
|
|
144
|
+
redactIPs: configuration.redactIPs || false,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Add custom patterns
|
|
148
|
+
if (configuration.customPatterns) {
|
|
149
|
+
options.patterns = convertCustomPatterns(configuration.customPatterns);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Add sensitive fields
|
|
153
|
+
if (
|
|
154
|
+
configuration.sensitiveFields &&
|
|
155
|
+
Array.isArray(configuration.sensitiveFields)
|
|
156
|
+
) {
|
|
157
|
+
options.sensitiveFields = configuration.sensitiveFields;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Create logger instance
|
|
161
|
+
const logger = new SecureLogger(options);
|
|
162
|
+
|
|
163
|
+
// Handle enabled/disabled patterns
|
|
164
|
+
if (
|
|
165
|
+
configuration.disabledPatterns &&
|
|
166
|
+
Array.isArray(configuration.disabledPatterns)
|
|
167
|
+
) {
|
|
168
|
+
configuration.disabledPatterns.forEach((patternName) => {
|
|
169
|
+
logger.removePattern(patternName);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
configuration.enabledPatterns &&
|
|
175
|
+
Array.isArray(configuration.enabledPatterns)
|
|
176
|
+
) {
|
|
177
|
+
// If specific patterns are listed, disable all others
|
|
178
|
+
const activePatterns = logger.getActivePatterns();
|
|
179
|
+
activePatterns.forEach((pattern) => {
|
|
180
|
+
if (!configuration.enabledPatterns.includes(pattern.name)) {
|
|
181
|
+
logger.removePattern(pattern.name);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return logger;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Load default configuration
|
|
191
|
+
*
|
|
192
|
+
* Attempts to load configuration from default locations:
|
|
193
|
+
* 1. ./redaction-config.json (current directory)
|
|
194
|
+
* 2. ./config/redaction-patterns.json
|
|
195
|
+
* 3. src/config/redaction-patterns.json
|
|
196
|
+
*
|
|
197
|
+
* @param {string} [environment] - Environment name for overrides
|
|
198
|
+
* @returns {SecureLogger} Configured logger or default logger if no config found
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* const logger = loadDefaultConfig();
|
|
202
|
+
* const prodLogger = loadDefaultConfig('production');
|
|
203
|
+
*/
|
|
204
|
+
function loadDefaultConfig(environment) {
|
|
205
|
+
const defaultPaths = [
|
|
206
|
+
path.join(process.cwd(), 'redaction-config.json'),
|
|
207
|
+
path.join(process.cwd(), 'config', 'redaction-patterns.json'),
|
|
208
|
+
path.join(__dirname, '..', 'config', 'redaction-patterns.json'),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
for (const configPath of defaultPaths) {
|
|
212
|
+
if (fs.existsSync(configPath)) {
|
|
213
|
+
try {
|
|
214
|
+
return createLoggerFromConfig(configPath, environment);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.warn(
|
|
217
|
+
`Failed to load config from ${configPath}:`,
|
|
218
|
+
error.message,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Return default logger if no config found
|
|
225
|
+
return new SecureLogger();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Validate configuration against schema
|
|
230
|
+
*
|
|
231
|
+
* @param {Object} config - Configuration to validate
|
|
232
|
+
* @returns {Object} Validation result { valid: boolean, errors: string[] }
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* const result = validateConfig(myConfig);
|
|
236
|
+
* if (!result.valid) {
|
|
237
|
+
* console.error('Config errors:', result.errors);
|
|
238
|
+
* }
|
|
239
|
+
*/
|
|
240
|
+
function validateConfig(config) {
|
|
241
|
+
const errors = [];
|
|
242
|
+
|
|
243
|
+
// Check required fields
|
|
244
|
+
if (config.enabled !== undefined && typeof config.enabled !== 'boolean') {
|
|
245
|
+
errors.push('enabled must be a boolean');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (
|
|
249
|
+
config.trackStats !== undefined &&
|
|
250
|
+
typeof config.trackStats !== 'boolean'
|
|
251
|
+
) {
|
|
252
|
+
errors.push('trackStats must be a boolean');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (
|
|
256
|
+
config.redactionMarker !== undefined &&
|
|
257
|
+
typeof config.redactionMarker !== 'string'
|
|
258
|
+
) {
|
|
259
|
+
errors.push('redactionMarker must be a string');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Validate custom patterns
|
|
263
|
+
if (config.customPatterns && typeof config.customPatterns === 'object') {
|
|
264
|
+
for (const [name, pattern] of Object.entries(config.customPatterns)) {
|
|
265
|
+
if (!pattern.regex) {
|
|
266
|
+
errors.push(`Custom pattern '${name}' missing regex field`);
|
|
267
|
+
}
|
|
268
|
+
if (!pattern.replacement) {
|
|
269
|
+
errors.push(`Custom pattern '${name}' missing replacement field`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Try to compile regex
|
|
273
|
+
if (pattern.regex) {
|
|
274
|
+
try {
|
|
275
|
+
compileRegex(pattern.regex, pattern.flags || 'g');
|
|
276
|
+
} catch (error) {
|
|
277
|
+
errors.push(
|
|
278
|
+
`Custom pattern '${name}' has invalid regex: ${error.message}`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Validate sensitive fields
|
|
286
|
+
if (config.sensitiveFields !== undefined) {
|
|
287
|
+
if (!Array.isArray(config.sensitiveFields)) {
|
|
288
|
+
errors.push('sensitiveFields must be an array');
|
|
289
|
+
} else {
|
|
290
|
+
config.sensitiveFields.forEach((field, index) => {
|
|
291
|
+
if (typeof field !== 'string') {
|
|
292
|
+
errors.push(`sensitiveFields[${index}] must be a string`);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
valid: errors.length === 0,
|
|
300
|
+
errors,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Create configuration template
|
|
306
|
+
*
|
|
307
|
+
* @returns {Object} Template configuration object
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* const template = createConfigTemplate();
|
|
311
|
+
* // Modify template as needed
|
|
312
|
+
* const logger = createLoggerFromConfig(template);
|
|
313
|
+
*/
|
|
314
|
+
function createConfigTemplate() {
|
|
315
|
+
return {
|
|
316
|
+
enabled: true,
|
|
317
|
+
trackStats: true,
|
|
318
|
+
redactionMarker: '[REDACTED]',
|
|
319
|
+
redactEmails: false,
|
|
320
|
+
redactIPs: false,
|
|
321
|
+
customPatterns: {},
|
|
322
|
+
sensitiveFields: [],
|
|
323
|
+
disabledPatterns: [],
|
|
324
|
+
enabledPatterns: [],
|
|
325
|
+
environments: {
|
|
326
|
+
development: {
|
|
327
|
+
enabled: false,
|
|
328
|
+
trackStats: false,
|
|
329
|
+
},
|
|
330
|
+
production: {
|
|
331
|
+
enabled: true,
|
|
332
|
+
trackStats: true,
|
|
333
|
+
redactEmails: true,
|
|
334
|
+
redactIPs: true,
|
|
335
|
+
},
|
|
336
|
+
test: {
|
|
337
|
+
enabled: false,
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
module.exports = {
|
|
344
|
+
createLoggerFromConfig,
|
|
345
|
+
loadDefaultConfig,
|
|
346
|
+
validateConfig,
|
|
347
|
+
createConfigTemplate,
|
|
348
|
+
loadConfigFile,
|
|
349
|
+
compileRegex,
|
|
350
|
+
applyEnvironmentOverrides,
|
|
351
|
+
convertCustomPatterns,
|
|
352
|
+
};
|