@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,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
|
+
};
|