@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.
Files changed (46) hide show
  1. package/README.md +323 -722
  2. package/contracts/contract-schema.json +189 -0
  3. package/contracts/reranker-contract.json +69 -0
  4. package/dist/index.cjs +19 -0
  5. package/dist/index.mjs +15 -1
  6. package/package.json +4 -2
  7. package/src/cli/commands/wizard.js +627 -0
  8. package/src/cli/input-sanitization-middleware.js +356 -0
  9. package/src/cli/templates/development.template.json +64 -0
  10. package/src/cli/templates/minimal.template.json +33 -0
  11. package/src/cli/templates/production.template.json +104 -0
  12. package/src/cli/templates/testing.template.json +45 -0
  13. package/src/config/connection-pool-config.json +259 -0
  14. package/src/config/memory-optimization.js +414 -0
  15. package/src/config/redaction-patterns.json +150 -0
  16. package/src/config/sanitization-rules.json +323 -0
  17. package/src/core/performance/streaming-safeguards.js +10 -1
  18. package/src/core/plugin-registry.js +275 -0
  19. package/src/dag/core/error-context.js +391 -0
  20. package/src/dag/core/execution-scheduler.js +350 -0
  21. package/src/dag/core/topology-validation.js +415 -0
  22. package/src/dag/dag-engine.js +26 -424
  23. package/src/dev/README.md +126 -0
  24. package/src/dev/dev-server.js +356 -0
  25. package/src/dev/examples/hot-reload-usage.js +355 -0
  26. package/src/dev/hot-reload.js +807 -0
  27. package/src/enterprise/sso-integration.js +184 -15
  28. package/src/index.js +19 -0
  29. package/src/observability/event-logger.js +62 -2
  30. package/src/plugins/optimized-openai-embedder.js +169 -0
  31. package/src/reranker/llm-reranker.js +30 -4
  32. package/src/security/jwt-config.js +431 -0
  33. package/src/security/jwt-validator.js +701 -0
  34. package/src/utils/api-client-pool-integration.js +436 -0
  35. package/src/utils/batch-processor.js +710 -0
  36. package/src/utils/connection-pool.js +605 -0
  37. package/src/utils/contract-validator.js +460 -0
  38. package/src/utils/error-formatter.js +603 -0
  39. package/src/utils/examples/error-formatter-usage.js +351 -0
  40. package/src/utils/input-sanitizer.js +967 -0
  41. package/src/utils/memory-monitor.js +800 -0
  42. package/src/utils/rate-limiter.js +244 -0
  43. package/src/utils/secure-logger-config.js +352 -0
  44. package/src/utils/secure-logger.js +576 -0
  45. package/src/utils/secure-logging.js +338 -0
  46. package/src/utils/structured-logger.js +32 -5
@@ -0,0 +1,431 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * JWT Security Configuration
5
+ *
6
+ * Centralized configuration for JWT validation and signing
7
+ * with environment-specific settings and security best practices.
8
+ *
9
+ * @module security/jwt-config
10
+ * @since 2.3.0
11
+ */
12
+
13
+ const crypto = require('crypto');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ /**
18
+ * Load JWT configuration from environment or defaults
19
+ *
20
+ * Environment variables:
21
+ * - JWT_SECRET: Secret for HMAC algorithms (min 32 bytes)
22
+ * - JWT_ALGORITHM: Algorithm to use (default: HS256)
23
+ * - JWT_ISSUER: Token issuer (default: rag-pipeline-utils)
24
+ * - JWT_AUDIENCE: Token audience (default: rag-pipeline-api)
25
+ * - JWT_EXPIRY: Token expiration in seconds (default: 3600)
26
+ * - JWT_CLOCK_TOLERANCE: Clock skew tolerance in seconds (default: 30)
27
+ * - JWT_PUBLIC_KEY_PATH: Path to public key file (for asymmetric algorithms)
28
+ * - JWT_PRIVATE_KEY_PATH: Path to private key file (for asymmetric algorithms)
29
+ * - JWT_ENABLE_JTI_TRACKING: Enable JTI replay protection (default: true)
30
+ *
31
+ * @returns {Object} JWT configuration
32
+ */
33
+ function loadJWTConfig() {
34
+ const isProduction = process.env.NODE_ENV === 'production';
35
+ const isDevelopment = process.env.NODE_ENV === 'development';
36
+ const isTest = process.env.NODE_ENV === 'test';
37
+ const isUndefined = !process.env.NODE_ENV; // Treat undefined as development
38
+
39
+ // Load algorithm
40
+ const algorithm = process.env.JWT_ALGORITHM || 'HS256';
41
+ const isSymmetric = algorithm.startsWith('HS');
42
+ const isAsymmetric =
43
+ algorithm.startsWith('RS') ||
44
+ algorithm.startsWith('ES') ||
45
+ algorithm.startsWith('PS');
46
+
47
+ // Load secret for symmetric algorithms
48
+ let secret = null;
49
+ if (isSymmetric) {
50
+ secret = process.env.JWT_SECRET;
51
+
52
+ if (!secret) {
53
+ if (isProduction) {
54
+ throw new Error(
55
+ 'JWT_SECRET environment variable is required in production. ' +
56
+ 'Generate a secure secret with: node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"',
57
+ );
58
+ } else if (isDevelopment || isTest || isUndefined) {
59
+ // Generate a random secret for development/test/undefined environments
60
+ secret = crypto.randomBytes(64).toString('hex');
61
+ if (!isUndefined) {
62
+ console.warn(
63
+ '[JWT_CONFIG] Warning: Using generated JWT_SECRET for development. ' +
64
+ 'Set JWT_SECRET environment variable for consistent tokens.',
65
+ );
66
+ }
67
+ }
68
+ }
69
+
70
+ // Validate secret strength
71
+ if (secret) {
72
+ const secretBits = Buffer.byteLength(secret, 'utf8') * 8;
73
+ const minBits =
74
+ algorithm === 'HS256' ? 256 : algorithm === 'HS384' ? 384 : 512;
75
+
76
+ if (secretBits < minBits) {
77
+ throw new Error(
78
+ `JWT_SECRET is too weak for ${algorithm}. ` +
79
+ `Minimum ${minBits} bits (${Math.ceil(minBits / 8)} bytes) required, got ${secretBits} bits. ` +
80
+ `Generate a stronger secret with: node -e "console.log(require('crypto').randomBytes(${Math.ceil(minBits / 8)}).toString('hex'))"`,
81
+ );
82
+ }
83
+ }
84
+ }
85
+
86
+ // Load keys for asymmetric algorithms
87
+ let publicKey = null;
88
+ let privateKey = null;
89
+
90
+ if (isAsymmetric) {
91
+ const publicKeyPath = process.env.JWT_PUBLIC_KEY_PATH;
92
+ const privateKeyPath = process.env.JWT_PRIVATE_KEY_PATH;
93
+
94
+ if (!publicKeyPath) {
95
+ if (isProduction) {
96
+ throw new Error(
97
+ 'JWT_PUBLIC_KEY_PATH environment variable is required for asymmetric algorithms in production',
98
+ );
99
+ } else {
100
+ console.warn(
101
+ '[JWT_CONFIG] Warning: No JWT_PUBLIC_KEY_PATH set. Token verification will not be available.',
102
+ );
103
+ }
104
+ } else {
105
+ try {
106
+ publicKey = fs.readFileSync(path.resolve(publicKeyPath), 'utf8');
107
+ } catch (error) {
108
+ throw new Error(
109
+ `Failed to read public key from ${publicKeyPath}: ${error.message}`,
110
+ );
111
+ }
112
+ }
113
+
114
+ if (!privateKeyPath) {
115
+ console.warn(
116
+ '[JWT_CONFIG] Warning: No JWT_PRIVATE_KEY_PATH set. Token signing will not be available.',
117
+ );
118
+ } else {
119
+ try {
120
+ privateKey = fs.readFileSync(path.resolve(privateKeyPath), 'utf8');
121
+ } catch (error) {
122
+ throw new Error(
123
+ `Failed to read private key from ${privateKeyPath}: ${error.message}`,
124
+ );
125
+ }
126
+ }
127
+ }
128
+
129
+ // Build configuration
130
+ const config = {
131
+ // Algorithm configuration
132
+ algorithm,
133
+ allowedAlgorithms: process.env.JWT_ALLOWED_ALGORITHMS
134
+ ? process.env.JWT_ALLOWED_ALGORITHMS.split(',').map((a) => a.trim())
135
+ : [algorithm],
136
+
137
+ // Secret/Key configuration
138
+ secret,
139
+ publicKey,
140
+ privateKey,
141
+
142
+ // Issuer and audience
143
+ issuer: process.env.JWT_ISSUER || 'rag-pipeline-utils',
144
+ audience: process.env.JWT_AUDIENCE || 'rag-pipeline-api',
145
+
146
+ // Timing configuration
147
+ clockTolerance: parseInt(process.env.JWT_CLOCK_TOLERANCE, 10) || 30,
148
+ maxTokenAge: parseInt(process.env.JWT_EXPIRY, 10) || 3600, // 1 hour
149
+ notBeforeTolerance: parseInt(process.env.JWT_NBF_TOLERANCE, 10) || 30,
150
+
151
+ // Security options
152
+ requireJti: process.env.JWT_REQUIRE_JTI !== 'false',
153
+ requireSub: process.env.JWT_REQUIRE_SUB !== 'false',
154
+ strictValidation: process.env.JWT_STRICT_VALIDATION !== 'false',
155
+
156
+ // JTI tracking for replay protection
157
+ enableJtiTracking: process.env.JWT_ENABLE_JTI_TRACKING !== 'false',
158
+ jtiExpirationMs:
159
+ parseInt(process.env.JWT_JTI_EXPIRATION_MS, 10) ||
160
+ 7 * 24 * 60 * 60 * 1000,
161
+
162
+ // Audit logging
163
+ enableAuditLog: process.env.JWT_ENABLE_AUDIT_LOG !== 'false',
164
+
165
+ // Environment-specific defaults
166
+ environment: process.env.NODE_ENV || 'development',
167
+ };
168
+
169
+ // Validate configuration
170
+ validateJWTConfig(config);
171
+
172
+ return config;
173
+ }
174
+
175
+ /**
176
+ * Validate JWT configuration
177
+ *
178
+ * @param {Object} config - JWT configuration to validate
179
+ * @throws {Error} If configuration is invalid
180
+ */
181
+ function validateJWTConfig(config) {
182
+ // Check algorithm
183
+ const validAlgorithms = [
184
+ 'HS256',
185
+ 'HS384',
186
+ 'HS512',
187
+ 'RS256',
188
+ 'RS384',
189
+ 'RS512',
190
+ 'ES256',
191
+ 'ES384',
192
+ 'ES512',
193
+ 'PS256',
194
+ 'PS384',
195
+ 'PS512',
196
+ ];
197
+
198
+ if (!validAlgorithms.includes(config.algorithm)) {
199
+ throw new Error(
200
+ `Invalid JWT algorithm: ${config.algorithm}. ` +
201
+ `Must be one of: ${validAlgorithms.join(', ')}`,
202
+ );
203
+ }
204
+
205
+ // Check allowed algorithms
206
+ if (
207
+ !Array.isArray(config.allowedAlgorithms) ||
208
+ config.allowedAlgorithms.length === 0
209
+ ) {
210
+ throw new Error('allowedAlgorithms must be a non-empty array');
211
+ }
212
+
213
+ for (const alg of config.allowedAlgorithms) {
214
+ // Check for "none" algorithm first (security critical)
215
+ if (
216
+ alg === 'none' ||
217
+ (typeof alg === 'string' && alg.toLowerCase() === 'none')
218
+ ) {
219
+ throw new Error('Algorithm "none" is not allowed');
220
+ }
221
+ if (!validAlgorithms.includes(alg)) {
222
+ throw new Error(`Invalid algorithm in allowedAlgorithms: ${alg}`);
223
+ }
224
+ }
225
+
226
+ // Check issuer and audience
227
+ if (!config.issuer || typeof config.issuer !== 'string') {
228
+ throw new Error('issuer must be a non-empty string');
229
+ }
230
+
231
+ if (!config.audience || typeof config.audience !== 'string') {
232
+ throw new Error('audience must be a non-empty string');
233
+ }
234
+
235
+ // Check timing values
236
+ if (config.clockTolerance < 0 || config.clockTolerance > 300) {
237
+ throw new Error('clockTolerance must be between 0 and 300 seconds');
238
+ }
239
+
240
+ if (config.maxTokenAge < 60 || config.maxTokenAge > 86400) {
241
+ throw new Error(
242
+ 'maxTokenAge must be between 60 seconds (1 minute) and 86400 seconds (24 hours)',
243
+ );
244
+ }
245
+
246
+ // Check secret/key availability
247
+ const isSymmetric = config.algorithm.startsWith('HS');
248
+ const isAsymmetric =
249
+ config.algorithm.startsWith('RS') ||
250
+ config.algorithm.startsWith('ES') ||
251
+ config.algorithm.startsWith('PS');
252
+
253
+ if (isSymmetric && !config.secret) {
254
+ throw new Error('secret is required for HMAC algorithms');
255
+ }
256
+
257
+ if (
258
+ isAsymmetric &&
259
+ !config.publicKey &&
260
+ process.env.NODE_ENV === 'production'
261
+ ) {
262
+ throw new Error(
263
+ 'publicKey is required for asymmetric algorithms in production',
264
+ );
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Get environment-specific JWT configuration presets
270
+ *
271
+ * @param {string} environment - Environment name (development, production, test)
272
+ * @returns {Object} Environment-specific configuration
273
+ */
274
+ function getEnvironmentPreset(environment) {
275
+ const presets = {
276
+ development: {
277
+ clockTolerance: 60, // More lenient in dev
278
+ maxTokenAge: 3600, // 1 hour
279
+ strictValidation: false,
280
+ enableAuditLog: false,
281
+ requireJti: false,
282
+ requireSub: false,
283
+ },
284
+ production: {
285
+ clockTolerance: 30,
286
+ maxTokenAge: 3600, // 1 hour
287
+ strictValidation: true,
288
+ enableAuditLog: true,
289
+ requireJti: true,
290
+ requireSub: true,
291
+ enableJtiTracking: true,
292
+ },
293
+ test: {
294
+ clockTolerance: 30,
295
+ maxTokenAge: 300, // 5 minutes for tests
296
+ strictValidation: true,
297
+ enableAuditLog: false,
298
+ requireJti: false,
299
+ requireSub: false,
300
+ enableJtiTracking: false,
301
+ },
302
+ };
303
+
304
+ return presets[environment] || presets.development;
305
+ }
306
+
307
+ /**
308
+ * Create JWT configuration with overrides
309
+ *
310
+ * @param {Object} overrides - Configuration overrides
311
+ * @returns {Object} Merged configuration
312
+ *
313
+ * @example
314
+ * const config = createJWTConfig({
315
+ * algorithm: 'RS256',
316
+ * publicKey: fs.readFileSync('public.pem'),
317
+ * privateKey: fs.readFileSync('private.pem'),
318
+ * });
319
+ */
320
+ function createJWTConfig(overrides = {}) {
321
+ const baseConfig = loadJWTConfig();
322
+ const environmentPreset = getEnvironmentPreset(baseConfig.environment);
323
+
324
+ const config = {
325
+ ...baseConfig,
326
+ ...environmentPreset,
327
+ ...overrides,
328
+ };
329
+
330
+ validateJWTConfig(config);
331
+
332
+ return config;
333
+ }
334
+
335
+ /**
336
+ * Generate a secure JWT secret
337
+ *
338
+ * @param {number} bytes - Number of bytes (default: 64)
339
+ * @returns {string} Hex-encoded secret
340
+ *
341
+ * @example
342
+ * const secret = generateJWTSecret();
343
+ * console.log('Add to .env: JWT_SECRET=' + secret);
344
+ */
345
+ function generateJWTSecret(bytes = 64) {
346
+ return crypto.randomBytes(bytes).toString('hex');
347
+ }
348
+
349
+ /**
350
+ * Generate RSA key pair for JWT signing
351
+ *
352
+ * @param {number} modulusLength - Key size in bits (default: 2048)
353
+ * @returns {Promise<Object>} Key pair { publicKey, privateKey }
354
+ *
355
+ * @example
356
+ * const { publicKey, privateKey } = await generateRSAKeyPair();
357
+ * fs.writeFileSync('jwt-public.pem', publicKey);
358
+ * fs.writeFileSync('jwt-private.pem', privateKey);
359
+ */
360
+ async function generateRSAKeyPair(modulusLength = 2048) {
361
+ return new Promise((resolve, reject) => {
362
+ crypto.generateKeyPair(
363
+ 'rsa',
364
+ {
365
+ modulusLength,
366
+ publicKeyEncoding: {
367
+ type: 'spki',
368
+ format: 'pem',
369
+ },
370
+ privateKeyEncoding: {
371
+ type: 'pkcs8',
372
+ format: 'pem',
373
+ },
374
+ },
375
+ (err, publicKey, privateKey) => {
376
+ if (err) {
377
+ reject(err);
378
+ } else {
379
+ resolve({ publicKey, privateKey });
380
+ }
381
+ },
382
+ );
383
+ });
384
+ }
385
+
386
+ /**
387
+ * Generate ECDSA key pair for JWT signing
388
+ *
389
+ * @param {string} namedCurve - Curve name (default: 'prime256v1' for ES256)
390
+ * @returns {Promise<Object>} Key pair { publicKey, privateKey }
391
+ *
392
+ * @example
393
+ * const { publicKey, privateKey } = await generateECDSAKeyPair('prime256v1');
394
+ * fs.writeFileSync('jwt-public-ec.pem', publicKey);
395
+ * fs.writeFileSync('jwt-private-ec.pem', privateKey);
396
+ */
397
+ async function generateECDSAKeyPair(namedCurve = 'prime256v1') {
398
+ return new Promise((resolve, reject) => {
399
+ crypto.generateKeyPair(
400
+ 'ec',
401
+ {
402
+ namedCurve, // prime256v1 (ES256), secp384r1 (ES384), secp521r1 (ES512)
403
+ publicKeyEncoding: {
404
+ type: 'spki',
405
+ format: 'pem',
406
+ },
407
+ privateKeyEncoding: {
408
+ type: 'pkcs8',
409
+ format: 'pem',
410
+ },
411
+ },
412
+ (err, publicKey, privateKey) => {
413
+ if (err) {
414
+ reject(err);
415
+ } else {
416
+ resolve({ publicKey, privateKey });
417
+ }
418
+ },
419
+ );
420
+ });
421
+ }
422
+
423
+ module.exports = {
424
+ loadJWTConfig,
425
+ createJWTConfig,
426
+ validateJWTConfig,
427
+ getEnvironmentPreset,
428
+ generateJWTSecret,
429
+ generateRSAKeyPair,
430
+ generateECDSAKeyPair,
431
+ };