@tamyla/clodo-framework 2.0.19 → 3.0.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +206 -62
  2. package/bin/clodo-service.js +32 -56
  3. package/bin/database/README.md +33 -0
  4. package/bin/database/deployment-db-manager.js +527 -0
  5. package/bin/database/enterprise-db-manager.js +736 -0
  6. package/bin/database/wrangler-d1-manager.js +775 -0
  7. package/bin/shared/cloudflare/domain-discovery.js +636 -0
  8. package/bin/shared/cloudflare/domain-manager.js +952 -0
  9. package/bin/shared/cloudflare/index.js +8 -0
  10. package/bin/shared/cloudflare/ops.js +359 -0
  11. package/bin/shared/config/index.js +1 -1
  12. package/bin/shared/database/connection-manager.js +374 -0
  13. package/bin/shared/database/index.js +7 -0
  14. package/bin/shared/database/orchestrator.js +726 -0
  15. package/bin/shared/deployment/auditor.js +969 -0
  16. package/bin/shared/deployment/index.js +10 -0
  17. package/bin/shared/deployment/rollback-manager.js +570 -0
  18. package/bin/shared/deployment/validator.js +779 -0
  19. package/bin/shared/index.js +32 -0
  20. package/bin/shared/monitoring/health-checker.js +484 -0
  21. package/bin/shared/monitoring/index.js +8 -0
  22. package/bin/shared/monitoring/memory-manager.js +387 -0
  23. package/bin/shared/monitoring/production-monitor.js +391 -0
  24. package/bin/shared/production-tester/api-tester.js +82 -0
  25. package/bin/shared/production-tester/auth-tester.js +132 -0
  26. package/bin/shared/production-tester/core.js +197 -0
  27. package/bin/shared/production-tester/database-tester.js +109 -0
  28. package/bin/shared/production-tester/index.js +77 -0
  29. package/bin/shared/production-tester/load-tester.js +131 -0
  30. package/bin/shared/production-tester/performance-tester.js +103 -0
  31. package/bin/shared/security/api-token-manager.js +312 -0
  32. package/bin/shared/security/index.js +8 -0
  33. package/bin/shared/security/secret-generator.js +937 -0
  34. package/bin/shared/security/secure-token-manager.js +398 -0
  35. package/bin/shared/utils/error-recovery.js +225 -0
  36. package/bin/shared/utils/graceful-shutdown-manager.js +390 -0
  37. package/bin/shared/utils/index.js +9 -0
  38. package/bin/shared/utils/interactive-prompts.js +146 -0
  39. package/bin/shared/utils/interactive-utils.js +530 -0
  40. package/bin/shared/utils/rate-limiter.js +246 -0
  41. package/dist/database/database-orchestrator.js +34 -12
  42. package/dist/deployment/index.js +2 -2
  43. package/dist/orchestration/multi-domain-orchestrator.js +8 -6
  44. package/dist/service-management/GenerationEngine.js +76 -28
  45. package/dist/service-management/ServiceInitializer.js +5 -3
  46. package/dist/shared/cloudflare/domain-manager.js +1 -1
  47. package/dist/shared/cloudflare/ops.js +27 -12
  48. package/dist/shared/config/index.js +1 -1
  49. package/dist/shared/deployment/index.js +2 -2
  50. package/dist/shared/security/secret-generator.js +4 -2
  51. package/dist/shared/utils/error-recovery.js +1 -1
  52. package/dist/shared/utils/graceful-shutdown-manager.js +4 -3
  53. package/dist/utils/deployment/secret-generator.js +19 -6
  54. package/package.json +7 -6
  55. package/bin/shared/config/customer-cli.js +0 -182
  56. package/dist/shared/config/customer-cli.js +0 -175
@@ -0,0 +1,937 @@
1
+ /**
2
+ * Enhanced Secret Generation Module
3
+ * Enterprise-grade cryptographic secret management with multi-domain and cross-environment capabilities
4
+ *
5
+ * Enhanced from deprecated smart-deployment.js with advanced features:
6
+ * - Multi-domain secret coordination
7
+ * - Cross-environment secret management
8
+ * - Multiple output formats (env, JSON, wrangler)
9
+ * - Secret rotation planning
10
+ * - Audit logging and validation
11
+ */
12
+
13
+ import { randomBytes, createHash } from 'crypto';
14
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, appendFileSync } from 'fs';
15
+ import { join, dirname } from 'path';
16
+ import { execSync } from 'child_process';
17
+ import { fileURLToPath } from 'url';
18
+
19
+ const __dirname = (() => {
20
+ const filename = fileURLToPath(import.meta.url);
21
+ return dirname(filename);
22
+ })();
23
+
24
+ const SECRET_CONFIGS = {
25
+ 'AUTH_JWT_SECRET': { length: 64, description: 'JWT token signing secret', scope: 'critical' },
26
+ 'X_SERVICE_KEY': { length: 64, description: 'Service authentication key', scope: 'critical' },
27
+ 'AUTH_SERVICE_API_KEY': { length: 48, description: 'Auth service API key', scope: 'standard' },
28
+ 'LOGGER_SERVICE_API_KEY': { length: 48, description: 'Logger service API key', scope: 'standard' },
29
+ 'CONTENT_SKIMMER_API_KEY': { length: 48, description: 'Content service API key', scope: 'standard' },
30
+ 'CROSS_DOMAIN_AUTH_KEY': { length: 64, description: 'Cross-domain authentication key', scope: 'critical' },
31
+ 'WEBHOOK_SIGNATURE_KEY': { length: 32, description: 'Webhook signature verification key', scope: 'standard' },
32
+ 'FILE_ENCRYPTION_KEY': { length: 64, description: 'File encryption key', scope: 'critical' },
33
+ 'SESSION_ENCRYPTION_KEY': { length: 48, description: 'Session encryption key', scope: 'standard' },
34
+ 'API_RATE_LIMIT_KEY': { length: 32, description: 'API rate limiting key', scope: 'standard' }
35
+ };
36
+
37
+ /**
38
+ * Enhanced Secret Manager Class
39
+ * Provides enterprise-grade secret management with multi-domain coordination
40
+ */
41
+ export class EnhancedSecretManager {
42
+ constructor(options = {}) {
43
+ this.projectRoot = options.projectRoot || join(__dirname, '..', '..');
44
+ this.dryRun = options.dryRun || false;
45
+ this.options = options;
46
+ this.config = null;
47
+
48
+ // Multi-domain and environment tracking
49
+ this.domainSecrets = new Map();
50
+ this.environmentConfigs = new Map();
51
+ this.rotationSchedule = new Map();
52
+
53
+ // Paths for secret management - will be updated with framework config in initialize()
54
+ this.secretPaths = {
55
+ root: join(this.projectRoot, 'secrets'),
56
+ distribution: join(this.projectRoot, 'secrets', 'distribution'),
57
+ backups: join(this.projectRoot, 'secrets', 'backups'),
58
+ audit: join(this.projectRoot, 'logs', 'secrets-audit.log'),
59
+ templates: join(this.projectRoot, 'secrets', 'templates')
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Initialize with framework configuration
65
+ */
66
+ async initialize() {
67
+ // Import framework config for consistent timing and retry settings
68
+ const { frameworkConfig } = await import('../../../src/utils/framework-config.js');
69
+ const timing = frameworkConfig.getTiming();
70
+ const security = frameworkConfig.getSecurity();
71
+ const configPaths = frameworkConfig.getPaths();
72
+
73
+ // Update paths with framework configuration
74
+ if (this.projectRoot) {
75
+ this.secretPaths = {
76
+ root: join(this.projectRoot, configPaths.secureTokens.replace('generated/cache/tokens', 'secrets')),
77
+ distribution: join(this.projectRoot, configPaths.secureTokens.replace('generated/cache/tokens', 'secrets'), 'distribution'),
78
+ backups: join(this.projectRoot, configPaths.backups, 'secrets'),
79
+ audit: join(this.projectRoot, configPaths.logs, 'secrets-audit.log'),
80
+ templates: join(this.projectRoot, configPaths.secureTokens.replace('generated/cache/tokens', 'secrets'), 'templates')
81
+ };
82
+ console.log(`🔐 Secret paths updated with framework config`);
83
+ }
84
+
85
+ this.config = {
86
+ retryAttempts: this.options.retryAttempts || timing.retryAttempts,
87
+ retryDelay: this.options.retryDelay || timing.retryDelay,
88
+ secretRotationInterval: this.options.secretRotationInterval || security.secretRotationInterval,
89
+ ...this.options
90
+ };
91
+
92
+ // Supported output formats
93
+ this.outputFormats = {
94
+ env: { extension: '.env', description: 'Environment variables' },
95
+ json: { extension: '.json', description: 'JSON format' },
96
+ wrangler: { extension: '.sh', description: 'Wrangler CLI commands' },
97
+ powershell: { extension: '.ps1', description: 'PowerShell commands' },
98
+ docker: { extension: '.env', description: 'Docker environment file' },
99
+ kubernetes: { extension: '.yaml', description: 'Kubernetes secrets' }
100
+ };
101
+
102
+ this.initializeSecretManager();
103
+ }
104
+
105
+ /**
106
+ * Initialize enhanced secret manager
107
+ */
108
+ initializeSecretManager() {
109
+ console.log('🔐 Enhanced Secret Manager v2.0');
110
+ console.log('===============================');
111
+ console.log(`📁 Secret Root: ${this.secretPaths.root}`);
112
+ console.log(`🔍 Mode: ${this.dryRun ? 'DRY RUN' : 'LIVE OPERATIONS'}`);
113
+ console.log(`📊 Formats: ${Object.keys(this.outputFormats).join(', ')}`);
114
+ console.log('');
115
+
116
+ // Create directories
117
+ Object.values(this.secretPaths).forEach(path => {
118
+ if (!path.endsWith('.log')) {
119
+ this.ensureDirectory(path);
120
+ }
121
+ });
122
+
123
+ this.logSecretEvent('MANAGER_INITIALIZED', 'SYSTEM', {
124
+ formats: Object.keys(this.outputFormats),
125
+ mode: this.dryRun ? 'DRY_RUN' : 'LIVE'
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Generate domain-specific secrets with environment coordination
131
+ * @param {string} domain - Domain name
132
+ * @param {string} environment - Environment (production, staging, development)
133
+ * @param {Object} options - Generation options
134
+ * @returns {Promise<Object>} Generated secrets and metadata
135
+ */
136
+ async generateDomainSpecificSecrets(domain, environment = 'production', options = {}) {
137
+ const {
138
+ customConfigs = {},
139
+ reuseExisting = true,
140
+ rotateAll = false,
141
+ formats = ['env', 'json', 'wrangler']
142
+ } = options;
143
+
144
+ console.log(`🔑 Generating secrets for ${domain} (${environment})`);
145
+ console.log(` 🔄 Reuse Existing: ${reuseExisting}`);
146
+ console.log(` 🔁 Rotate All: ${rotateAll}`);
147
+ console.log(` 📋 Formats: ${formats.join(', ')}`);
148
+
149
+ try {
150
+ // Load existing secrets if requested
151
+ let existingSecrets = {};
152
+ if (reuseExisting && !rotateAll) {
153
+ const existing = await this.loadDomainSecrets(domain, environment);
154
+ if (existing) {
155
+ existingSecrets = existing.secrets;
156
+ console.log(` 📂 Loaded ${Object.keys(existingSecrets).length} existing secrets`);
157
+ }
158
+ }
159
+
160
+ // Merge configurations
161
+ const configs = { ...SECRET_CONFIGS, ...customConfigs };
162
+ const secrets = { ...existingSecrets };
163
+ let generated = 0;
164
+
165
+ // Generate missing or rotated secrets
166
+ for (const [key, config] of Object.entries(configs)) {
167
+ if (!secrets[key] || rotateAll) {
168
+ secrets[key] = this.generateSecretValue(config.length);
169
+ generated++;
170
+ }
171
+ }
172
+
173
+ console.log(` 🔑 Generated ${generated} new secrets, reused ${Object.keys(secrets).length - generated}`);
174
+
175
+ // Create secret metadata
176
+ const secretData = {
177
+ domain,
178
+ environment,
179
+ generated: new Date().toISOString(),
180
+ generatedBy: 'EnhancedSecretManager v2.0',
181
+ secretCount: Object.keys(secrets).length,
182
+ newSecrets: generated,
183
+ formats: formats,
184
+ ...secrets
185
+ };
186
+
187
+ // Save secrets
188
+ const savedFile = await this.saveDomainSecrets(domain, environment, secretData);
189
+
190
+ // Generate distribution files
191
+ const distribution = await this.generateSecretDistribution(domain, environment, secrets, formats);
192
+
193
+ // Deploy secrets if not dry run
194
+ let deployment = null;
195
+ if (!this.dryRun && options.deploy) {
196
+ deployment = await this.deploySecretsToCloudflare(domain, environment, secrets);
197
+ }
198
+
199
+ // Update domain tracking
200
+ this.domainSecrets.set(`${domain}-${environment}`, {
201
+ secrets,
202
+ generated: new Date(),
203
+ formats,
204
+ deployment
205
+ });
206
+
207
+ this.logSecretEvent('SECRETS_GENERATED', domain, {
208
+ environment,
209
+ secretCount: Object.keys(secrets).length,
210
+ newSecrets: generated,
211
+ formats
212
+ });
213
+
214
+ return {
215
+ domain,
216
+ environment,
217
+ secrets,
218
+ metadata: secretData,
219
+ savedFile,
220
+ distribution,
221
+ deployment,
222
+ summary: {
223
+ total: Object.keys(secrets).length,
224
+ generated,
225
+ reused: Object.keys(secrets).length - generated
226
+ }
227
+ };
228
+
229
+ } catch (error) {
230
+ this.logSecretEvent('SECRET_GENERATION_FAILED', domain, {
231
+ environment,
232
+ error: error.message
233
+ });
234
+ throw new Error(`Secret generation failed for ${domain}: ${error.message}`);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Coordinate secrets across multiple environments for a domain
240
+ * @param {string} domain - Domain name
241
+ * @param {Array} environments - Environments to coordinate
242
+ * @param {Object} options - Coordination options
243
+ * @returns {Promise<Object>} Coordination results
244
+ */
245
+ async coordinateSecretsAcrossEnvironments(domain, environments = ['development', 'staging', 'production'], options = {}) {
246
+ console.log(`🌐 Cross-Environment Secret Coordination for ${domain}`);
247
+ console.log(` 🌍 Environments: ${environments.join(', ')}`);
248
+
249
+ const {
250
+ syncCriticalSecrets = true,
251
+ generateUniquePerEnv = true,
252
+ formats = ['env', 'json', 'wrangler']
253
+ } = options;
254
+
255
+ const results = {
256
+ domain,
257
+ environments: {},
258
+ sharedSecrets: {},
259
+ coordinationId: this.generateCoordinationId(domain),
260
+ startTime: new Date()
261
+ };
262
+
263
+ try {
264
+ // Generate base secrets for production first
265
+ const productionSecrets = await this.generateDomainSpecificSecrets(domain, 'production', {
266
+ ...options,
267
+ formats
268
+ });
269
+
270
+ results.environments.production = productionSecrets;
271
+
272
+ // Extract critical secrets to share across environments
273
+ if (syncCriticalSecrets) {
274
+ const criticalConfigs = Object.entries(SECRET_CONFIGS)
275
+ .filter(([, config]) => config.scope === 'critical');
276
+
277
+ for (const [key, ] of criticalConfigs) {
278
+ if (productionSecrets.secrets[key]) {
279
+ results.sharedSecrets[key] = productionSecrets.secrets[key];
280
+ }
281
+ }
282
+
283
+ console.log(` 🔗 Sharing ${Object.keys(results.sharedSecrets).length} critical secrets across environments`);
284
+ }
285
+
286
+ // Process other environments
287
+ for (const env of environments) {
288
+ if (env === 'production') continue;
289
+
290
+ console.log(`\n 🌍 Processing ${env} environment...`);
291
+
292
+ const envOptions = {
293
+ ...options,
294
+ customConfigs: generateUniquePerEnv ? {} : results.sharedSecrets,
295
+ reuseExisting: !generateUniquePerEnv,
296
+ formats
297
+ };
298
+
299
+ const envSecrets = await this.generateDomainSpecificSecrets(domain, env, envOptions);
300
+
301
+ // Override with shared secrets if coordinating
302
+ if (syncCriticalSecrets) {
303
+ Object.assign(envSecrets.secrets, results.sharedSecrets);
304
+
305
+ // Re-save with coordinated secrets
306
+ await this.saveDomainSecrets(domain, env, {
307
+ ...envSecrets.metadata,
308
+ ...envSecrets.secrets,
309
+ coordinated: true,
310
+ coordinationId: results.coordinationId
311
+ });
312
+
313
+ // Re-generate distribution with coordinated secrets
314
+ envSecrets.distribution = await this.generateSecretDistribution(
315
+ domain, env, envSecrets.secrets, formats
316
+ );
317
+ }
318
+
319
+ results.environments[env] = envSecrets;
320
+ }
321
+
322
+ results.endTime = new Date();
323
+ results.duration = (results.endTime - results.startTime) / 1000;
324
+
325
+ this.logSecretEvent('CROSS_ENVIRONMENT_COORDINATION_COMPLETED', domain, {
326
+ coordinationId: results.coordinationId,
327
+ environments: environments,
328
+ sharedSecrets: Object.keys(results.sharedSecrets).length,
329
+ duration: results.duration
330
+ });
331
+
332
+ console.log(`\n✅ Cross-environment coordination completed (${results.duration.toFixed(1)}s)`);
333
+ console.log(` 🔗 Shared secrets: ${Object.keys(results.sharedSecrets).length}`);
334
+ console.log(` 🌍 Environments: ${Object.keys(results.environments).length}`);
335
+
336
+ return results;
337
+
338
+ } catch (error) {
339
+ this.logSecretEvent('CROSS_ENVIRONMENT_COORDINATION_FAILED', domain, {
340
+ coordinationId: results.coordinationId,
341
+ error: error.message
342
+ });
343
+ throw error;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Deploy secrets to Cloudflare Workers across environments
349
+ * @param {string} domain - Domain name
350
+ * @param {string} environment - Environment
351
+ * @param {Object} secrets - Secrets to deploy
352
+ * @returns {Promise<Object>} Deployment results
353
+ */
354
+ async deploySecretsToCloudflare(domain, environment, secrets) {
355
+ console.log(` ☁️ Deploying ${Object.keys(secrets).length} secrets to Cloudflare (${environment})...`);
356
+
357
+ if (this.dryRun) {
358
+ console.log(` 🔍 DRY RUN: Would deploy secrets to ${environment}`);
359
+ return { status: 'dry-run', secretCount: Object.keys(secrets).length };
360
+ }
361
+
362
+ const results = {
363
+ domain,
364
+ environment,
365
+ deployed: [],
366
+ failed: [],
367
+ startTime: new Date()
368
+ };
369
+
370
+ try {
371
+ for (const [key, value] of Object.entries(secrets)) {
372
+ try {
373
+ await this.deploySingleSecret(key, value, environment);
374
+ results.deployed.push(key);
375
+ console.log(` ✅ ${key} deployed`);
376
+ } catch (error) {
377
+ results.failed.push({ key, error: error.message });
378
+ console.log(` ❌ ${key} failed: ${error.message}`);
379
+ }
380
+ }
381
+
382
+ results.endTime = new Date();
383
+ results.duration = (results.endTime - results.startTime) / 1000;
384
+
385
+ this.logSecretEvent('SECRETS_DEPLOYED', domain, {
386
+ environment,
387
+ deployed: results.deployed.length,
388
+ failed: results.failed.length,
389
+ duration: results.duration
390
+ });
391
+
392
+ return results;
393
+
394
+ } catch (error) {
395
+ this.logSecretEvent('SECRET_DEPLOYMENT_FAILED', domain, {
396
+ environment,
397
+ error: error.message
398
+ });
399
+ throw error;
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Deploy single secret with retry logic
405
+ * @param {string} key - Secret key
406
+ * @param {string} value - Secret value
407
+ * @param {string} environment - Environment
408
+ */
409
+ async deploySingleSecret(key, value, environment) {
410
+ for (let attempt = 1; attempt <= (this.config ? this.config.retryAttempts : 3); attempt++) {
411
+ try {
412
+ const command = process.platform === 'win32'
413
+ ? `powershell -Command "Write-Output '${value}' | npx wrangler secret put ${key} --env ${environment}"`
414
+ : `echo "${value}" | npx wrangler secret put ${key} --env ${environment}`;
415
+
416
+ execSync(command, {
417
+ shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash',
418
+ stdio: 'pipe',
419
+ encoding: 'utf8',
420
+ timeout: this.config ? this.config.deploymentTimeout || 30000 : 30000
421
+ });
422
+
423
+ return;
424
+ } catch (error) {
425
+ if (attempt === this.retryAttempts) {
426
+ throw error;
427
+ }
428
+
429
+ console.log(` ⚠️ Attempt ${attempt} failed for ${key}, retrying...`);
430
+ await new Promise(resolve => setTimeout(resolve, this.config ? this.config.retryDelay : 2000));
431
+ }
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Generate enhanced secret distribution files
437
+ * @param {string} domain - Domain name
438
+ * @param {string} environment - Environment
439
+ * @param {Object} secrets - Secrets to distribute
440
+ * @param {Array} formats - Output formats
441
+ * @returns {Promise<Object>} Distribution results
442
+ */
443
+ async generateSecretDistribution(domain, environment, secrets, formats) {
444
+ const distDir = join(this.secretPaths.distribution, domain, environment);
445
+ this.ensureDirectory(distDir);
446
+
447
+ const distribution = {
448
+ domain,
449
+ environment,
450
+ directory: distDir,
451
+ files: {},
452
+ formats: formats,
453
+ generated: new Date()
454
+ };
455
+
456
+ console.log(` 📤 Generating distribution files in ${formats.join(', ')} formats...`);
457
+
458
+ try {
459
+ for (const format of formats) {
460
+ if (this.outputFormats[format]) {
461
+ const file = await this.generateFormatFile(distDir, domain, environment, secrets, format);
462
+ distribution.files[format] = file;
463
+ console.log(` 📄 ${format}: ${file.filename}`);
464
+ }
465
+ }
466
+
467
+ // Generate comprehensive README
468
+ const readme = await this.generateDistributionReadme(domain, environment, secrets, formats);
469
+ distribution.files.readme = readme;
470
+
471
+ this.logSecretEvent('DISTRIBUTION_GENERATED', domain, {
472
+ environment,
473
+ formats,
474
+ fileCount: Object.keys(distribution.files).length
475
+ });
476
+
477
+ return distribution;
478
+
479
+ } catch (error) {
480
+ this.logSecretEvent('DISTRIBUTION_GENERATION_FAILED', domain, {
481
+ environment,
482
+ error: error.message
483
+ });
484
+ throw error;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Generate specific format file
490
+ * @param {string} distDir - Distribution directory
491
+ * @param {string} domain - Domain name
492
+ * @param {string} environment - Environment
493
+ * @param {Object} secrets - Secrets
494
+ * @param {string} format - Format type
495
+ * @returns {Promise<Object>} File information
496
+ */
497
+ async generateFormatFile(distDir, domain, environment, secrets, format) {
498
+ const formatConfig = this.outputFormats[format];
499
+ const filename = `secrets-${environment}${formatConfig.extension}`;
500
+ const filepath = join(distDir, filename);
501
+
502
+ let content = '';
503
+
504
+ switch (format) {
505
+ case 'env':
506
+ content = Object.entries(secrets)
507
+ .map(([key, value]) => `${key}=${value}`)
508
+ .join('\n');
509
+ break;
510
+
511
+ case 'json':
512
+ content = JSON.stringify(secrets, null, 2);
513
+ break;
514
+
515
+ case 'wrangler':
516
+ content = [
517
+ '#!/bin/bash',
518
+ `# Wrangler secret deployment for ${domain} (${environment})`,
519
+ `# Generated: ${new Date().toISOString()}`,
520
+ '',
521
+ ...Object.entries(secrets).map(([key, value]) =>
522
+ `echo "${value}" | npx wrangler secret put ${key} --env ${environment}`)
523
+ ].join('\n');
524
+ break;
525
+
526
+ case 'powershell':
527
+ content = [
528
+ `# PowerShell secret deployment for ${domain} (${environment})`,
529
+ `# Generated: ${new Date().toISOString()}`,
530
+ '',
531
+ ...Object.entries(secrets).map(([key, value]) =>
532
+ `"${value}" | npx wrangler secret put ${key} --env ${environment}`)
533
+ ].join('\n');
534
+ break;
535
+
536
+ case 'docker':
537
+ content = Object.entries(secrets)
538
+ .map(([key, value]) => `${key}=${value}`)
539
+ .join('\n');
540
+ break;
541
+
542
+ case 'kubernetes': {
543
+ const encodedSecrets = {};
544
+ Object.entries(secrets).forEach(([key, value]) => {
545
+ encodedSecrets[key] = Buffer.from(value).toString('base64');
546
+ });
547
+
548
+ content = [
549
+ 'apiVersion: v1',
550
+ 'kind: Secret',
551
+ 'metadata:',
552
+ ` name: ${domain.replace(/\./g, '-')}-secrets-${environment}`,
553
+ ` namespace: ${environment}`,
554
+ 'type: Opaque',
555
+ 'data:',
556
+ ...Object.entries(encodedSecrets).map(([key, value]) => ` ${key}: ${value}`)
557
+ ].join('\n');
558
+ break;
559
+ }
560
+
561
+ default:
562
+ throw new Error(`Unsupported format: ${format}`);
563
+ }
564
+
565
+ writeFileSync(filepath, content);
566
+
567
+ return {
568
+ format,
569
+ filename,
570
+ filepath,
571
+ description: formatConfig.description,
572
+ size: content.length
573
+ };
574
+ }
575
+
576
+ /**
577
+ * Generate comprehensive distribution README
578
+ * @param {string} domain - Domain name
579
+ * @param {string} environment - Environment
580
+ * @param {Object} secrets - Secrets
581
+ * @param {Array} formats - Formats
582
+ * @returns {Promise<Object>} README file info
583
+ */
584
+ async generateDistributionReadme(domain, environment, secrets, formats) {
585
+ const readmeContent = `# Secret Distribution for ${domain} (${environment})
586
+
587
+ Generated: ${new Date().toISOString()}
588
+ Secret Count: ${Object.keys(secrets).length}
589
+ Formats: ${formats.join(', ')}
590
+
591
+ ## 🔐 Generated Files
592
+
593
+ ${formats.map(format => {
594
+ const config = this.outputFormats[format];
595
+ const filename = `secrets-${environment}${config.extension}`;
596
+ return `- **${filename}** - ${config.description}`;
597
+ }).join('\n')}
598
+
599
+ ## 🚀 Usage Instructions
600
+
601
+ ### Environment Variables (.env)
602
+ \`\`\`bash
603
+ # For Node.js applications
604
+ cp secrets-${environment}.env /path/to/your/app/.env
605
+ source secrets-${environment}.env
606
+ \`\`\`
607
+
608
+ ### JSON Format
609
+ \`\`\`javascript
610
+ // For API consumption
611
+ const secrets = require('./secrets-${environment}.json');
612
+ console.log(secrets.AUTH_JWT_SECRET);
613
+ \`\`\`
614
+
615
+ ### Cloudflare Workers Deployment
616
+ \`\`\`bash
617
+ # Linux/Mac
618
+ chmod +x secrets-${environment}.sh
619
+ ./secrets-${environment}.sh
620
+
621
+ # Windows PowerShell
622
+ .\\secrets-${environment}.ps1
623
+ \`\`\`
624
+
625
+ ### Docker Deployment
626
+ \`\`\`bash
627
+ # Use as environment file
628
+ docker run --env-file secrets-${environment}.env your-image
629
+ \`\`\`
630
+
631
+ ### Kubernetes Deployment
632
+ \`\`\`bash
633
+ # Apply secret manifest
634
+ kubectl apply -f secrets-${environment}.yaml
635
+ \`\`\`
636
+
637
+ ## 🛡️ Security Guidelines
638
+
639
+ - **Keep secrets secure** - Never commit to version control
640
+ - **Use secure channels** - Distribute via encrypted channels only
641
+ - **Rotate regularly** - Update secrets according to security policy
642
+ - **Monitor access** - Track secret usage and access patterns
643
+ - **Backup safely** - Store backups in encrypted, access-controlled locations
644
+
645
+ ## 🔄 Secret Rotation
646
+
647
+ To rotate secrets for this domain and environment:
648
+ \`\`\`bash
649
+ # Rotate all secrets
650
+ node scripts/shared/secret-generator.js --domain ${domain} --environment ${environment} --rotate-all
651
+
652
+ # Rotate specific secret
653
+ node scripts/shared/secret-generator.js --domain ${domain} --environment ${environment} --rotate AUTH_JWT_SECRET
654
+ \`\`\`
655
+
656
+ ## 📊 Secret Inventory
657
+
658
+ ${Object.entries(SECRET_CONFIGS).map(([key, config]) =>
659
+ `- **${key}**: ${config.description} (${config.scope})`
660
+ ).join('\n')}
661
+
662
+ ---
663
+ *Generated by Enhanced Secret Manager v2.0*
664
+ *For support: Check project documentation*
665
+ `;
666
+
667
+ const readmeFile = join(domain, environment, 'README.md');
668
+ const readmePath = join(this.secretPaths.distribution, readmeFile);
669
+ writeFileSync(readmePath, readmeContent);
670
+
671
+ return {
672
+ filename: 'README.md',
673
+ filepath: readmePath,
674
+ size: readmeContent.length
675
+ };
676
+ }
677
+
678
+ // Utility methods
679
+
680
+ generateSecretValue(length) {
681
+ return randomBytes(length / 2).toString('hex');
682
+ }
683
+
684
+ generateCoordinationId(domain) {
685
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
686
+ const hash = createHash('md5').update(domain).digest('hex').substring(0, 8);
687
+ return `coord-${domain}-${hash}-${timestamp}`;
688
+ }
689
+
690
+ async loadDomainSecrets(domain, environment) {
691
+ const filename = join(this.secretPaths.root, `${domain}-${environment}-secrets.json`);
692
+
693
+ if (!existsSync(filename)) {
694
+ return null;
695
+ }
696
+
697
+ try {
698
+ const data = JSON.parse(readFileSync(filename, 'utf8'));
699
+ const { domain: d, environment: e, generated, generatedBy, secretCount, newSecrets, formats, ...secrets } = data;
700
+
701
+ return {
702
+ secrets,
703
+ metadata: { domain: d, environment: e, generated, generatedBy, secretCount, newSecrets, formats },
704
+ filename
705
+ };
706
+ } catch (error) {
707
+ throw new Error(`Failed to load secrets for ${domain} (${environment}): ${error.message}`);
708
+ }
709
+ }
710
+
711
+ async saveDomainSecrets(domain, environment, secretData) {
712
+ const filename = join(this.secretPaths.root, `${domain}-${environment}-secrets.json`);
713
+
714
+ if (this.dryRun) {
715
+ console.log(` 🔍 DRY RUN: Would save secrets to ${filename}`);
716
+ return filename;
717
+ }
718
+
719
+ writeFileSync(filename, JSON.stringify(secretData, null, 2));
720
+ console.log(` 💾 Secrets saved: ${filename}`);
721
+
722
+ return filename;
723
+ }
724
+
725
+ ensureDirectory(path) {
726
+ if (!existsSync(path)) {
727
+ mkdirSync(path, { recursive: true });
728
+ }
729
+ }
730
+
731
+ logSecretEvent(event, domain, details = {}) {
732
+ const logEntry = {
733
+ timestamp: new Date().toISOString(),
734
+ event,
735
+ domain,
736
+ details,
737
+ user: process.env.USER || process.env.USERNAME || 'system'
738
+ };
739
+
740
+ try {
741
+ const logLine = JSON.stringify(logEntry) + '\n';
742
+
743
+ if (existsSync(this.secretPaths.audit)) {
744
+ appendFileSync(this.secretPaths.audit, logLine);
745
+ } else {
746
+ writeFileSync(this.secretPaths.audit, logLine);
747
+ }
748
+ } catch (error) {
749
+ console.warn(`⚠️ Failed to log secret event: ${error.message}`);
750
+ }
751
+ }
752
+ }
753
+
754
+ // Legacy function exports for backward compatibility
755
+ export function generateSecrets(customConfigs = {}) {
756
+ const configs = { ...SECRET_CONFIGS, ...customConfigs };
757
+ const secrets = {};
758
+
759
+ for (const [key, config] of Object.entries(configs)) {
760
+ secrets[key] = randomBytes(config.length / 2).toString('hex');
761
+ }
762
+
763
+ return secrets;
764
+ }
765
+
766
+ export function generateSingleSecret(length = 32) {
767
+ return randomBytes(length / 2).toString('hex');
768
+ }
769
+
770
+ export function saveSecrets(domain, environment, secrets, additionalData = {}) {
771
+ const secretsDir = 'secrets';
772
+ if (!existsSync(secretsDir)) {
773
+ mkdirSync(secretsDir, { recursive: true });
774
+ }
775
+
776
+ const data = {
777
+ domain,
778
+ environment,
779
+ generated: new Date().toISOString(),
780
+ note: 'Generated by modular secret system',
781
+ ...additionalData,
782
+ ...secrets
783
+ };
784
+
785
+ const filename = join(secretsDir, `${domain}-secrets.json`);
786
+ writeFileSync(filename, JSON.stringify(data, null, 2));
787
+ return filename;
788
+ }
789
+
790
+ export function loadSecrets(domain) {
791
+ const filename = join('secrets', `${domain}-secrets.json`);
792
+ if (!existsSync(filename)) {
793
+ return null;
794
+ }
795
+
796
+ try {
797
+ const data = JSON.parse(readFileSync(filename, 'utf8'));
798
+ const { domain: d, environment, generated, note, ...secrets } = data;
799
+ return {
800
+ secrets,
801
+ metadata: { domain: d, environment, generated, note },
802
+ filename
803
+ };
804
+ } catch (error) {
805
+ throw new Error(`Failed to load secrets: ${error.message}`);
806
+ }
807
+ }
808
+
809
+ export function distributeSecrets(domain, secrets) {
810
+ const distDir = join('secrets', 'distribution', domain);
811
+ mkdirSync(distDir, { recursive: true });
812
+
813
+ const files = {};
814
+
815
+ // .env format
816
+ const envContent = Object.entries(secrets)
817
+ .map(([key, value]) => `${key}=${value}`)
818
+ .join('\n');
819
+ const envFile = join(distDir, '.env');
820
+ writeFileSync(envFile, envContent);
821
+ files.env = envFile;
822
+
823
+ // JSON format
824
+ const jsonFile = join(distDir, 'secrets.json');
825
+ writeFileSync(jsonFile, JSON.stringify(secrets, null, 2));
826
+ files.json = jsonFile;
827
+
828
+ // Shell script format (cross-platform)
829
+ const shellContent = Object.entries(secrets)
830
+ .map(([key, value]) => `echo "${value}" | npx wrangler secret put ${key} --env production`)
831
+ .join('\n');
832
+ const shellFile = join(distDir, 'deploy-secrets.sh');
833
+ writeFileSync(shellFile, shellContent);
834
+ files.shell = shellFile;
835
+
836
+ // PowerShell script format
837
+ const psContent = Object.entries(secrets)
838
+ .map(([key, value]) => `"${value}" | npx wrangler secret put ${key} --env production`)
839
+ .join('\n');
840
+ const psFile = join(distDir, 'deploy-secrets.ps1');
841
+ writeFileSync(psFile, psContent);
842
+ files.powershell = psFile;
843
+
844
+ // README
845
+ const readme = `# Secret Distribution for ${domain}
846
+
847
+ Generated: ${new Date().toISOString()}
848
+
849
+ ## Files
850
+ - \`.env\` - Environment variables for Node.js applications
851
+ - \`secrets.json\` - JSON format for API consumption
852
+ - \`deploy-secrets.sh\` - Bash commands for Cloudflare Workers
853
+ - \`deploy-secrets.ps1\` - PowerShell commands for Cloudflare Workers
854
+
855
+ ## Usage
856
+
857
+ ### For downstream Node.js applications:
858
+ \`\`\`bash
859
+ cp .env /path/to/your/app/
860
+ \`\`\`
861
+
862
+ ### For upstream Cloudflare Workers (Linux/Mac):
863
+ \`\`\`bash
864
+ chmod +x deploy-secrets.sh
865
+ ./deploy-secrets.sh
866
+ \`\`\`
867
+
868
+ ### For upstream Cloudflare Workers (Windows):
869
+ \`\`\`powershell
870
+ .\\deploy-secrets.ps1
871
+ \`\`\`
872
+
873
+ ## Security Notice
874
+ - Keep these files secure
875
+ - Never commit to version control
876
+ - Distribute via secure channels only
877
+ `;
878
+
879
+ const readmeFile = join(distDir, 'README.md');
880
+ writeFileSync(readmeFile, readme);
881
+ files.readme = readmeFile;
882
+
883
+ return { directory: distDir, files };
884
+ }
885
+
886
+ export function validateSecrets(secrets) {
887
+ const issues = [];
888
+
889
+ for (const [key, value] of Object.entries(secrets)) {
890
+ if (!value || typeof value !== 'string') {
891
+ issues.push(`${key}: Invalid value`);
892
+ continue;
893
+ }
894
+
895
+ if (value.length < 16) {
896
+ issues.push(`${key}: Too short (minimum 16 characters)`);
897
+ }
898
+
899
+ if (!/^[a-f0-9]+$/i.test(value)) {
900
+ issues.push(`${key}: Should be hexadecimal`);
901
+ }
902
+ }
903
+
904
+ return issues;
905
+ }
906
+
907
+ export function listSecretsFiles() {
908
+ const secretsDir = 'secrets';
909
+ if (!existsSync(secretsDir)) {
910
+ return [];
911
+ }
912
+
913
+ try {
914
+ const files = [];
915
+ const items = readdirSync(secretsDir);
916
+
917
+ for (const item of items) {
918
+ if (item.endsWith('-secrets.json')) {
919
+ const domain = item.replace('-secrets.json', '');
920
+ const filepath = join(secretsDir, item);
921
+ const stat = statSync(filepath);
922
+ files.push({
923
+ domain,
924
+ filepath,
925
+ modified: stat.mtime,
926
+ size: stat.size
927
+ });
928
+ }
929
+ }
930
+
931
+ return files.sort((a, b) => b.modified - a.modified);
932
+ } catch (error) {
933
+ return [];
934
+ }
935
+ }
936
+
937
+ export { SECRET_CONFIGS };