@friggframework/core 2.0.0--canary.427.04558b7.0 → 2.0.0--canary.427.bd07d1c.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.
@@ -333,6 +333,178 @@ const testEncryption = async () => {
333
333
  }
334
334
  };
335
335
 
336
+ const checkKMSAccess = async () => {
337
+ const { KMS_KEY_ARN } = process.env;
338
+
339
+ if (!KMS_KEY_ARN || KMS_KEY_ARN.trim() === '') {
340
+ return {
341
+ status: 'disabled',
342
+ testResult: 'No KMS key configured',
343
+ canAccessKey: false,
344
+ };
345
+ }
346
+
347
+ try {
348
+ // eslint-disable-next-line no-console
349
+ console.log('Testing KMS key access with key:', KMS_KEY_ARN.substring(0, 50) + '...');
350
+
351
+ const AWS = require('aws-sdk');
352
+ const kms = new AWS.KMS();
353
+
354
+ // First, check if the master key exists and is accessible
355
+ let keyExists = false;
356
+ let keyMetadata = null;
357
+
358
+ try {
359
+ // eslint-disable-next-line no-console
360
+ console.log('Checking if KMS master key exists...');
361
+ const describeResult = await withTimeout(
362
+ kms.describeKey({ KeyId: KMS_KEY_ARN }).promise(),
363
+ 5000,
364
+ 'KMS describeKey operation timed out after 5 seconds'
365
+ );
366
+
367
+ keyMetadata = describeResult.KeyMetadata;
368
+ keyExists = true;
369
+ // eslint-disable-next-line no-console
370
+ console.log('KMS master key found:', {
371
+ KeyId: keyMetadata.KeyId,
372
+ KeyState: keyMetadata.KeyState,
373
+ Enabled: keyMetadata.Enabled,
374
+ KeyUsage: keyMetadata.KeyUsage,
375
+ });
376
+ } catch (describeError) {
377
+ // eslint-disable-next-line no-console
378
+ console.error('KMS master key does not exist or is not accessible:', describeError.message);
379
+
380
+ if (describeError.code === 'NotFoundException') {
381
+ return {
382
+ status: 'unhealthy',
383
+ testResult: 'KMS master key not found',
384
+ canAccessKey: false,
385
+ error: 'Master key does not exist',
386
+ keyExists: false,
387
+ };
388
+ } else if (describeError.code === 'AccessDeniedException') {
389
+ return {
390
+ status: 'unhealthy',
391
+ testResult: 'No permission to access KMS master key',
392
+ canAccessKey: false,
393
+ error: 'Access denied to master key',
394
+ keyExists: 'unknown',
395
+ };
396
+ }
397
+ // Continue to try generateDataKey even if describeKey fails
398
+ // as permissions might be limited
399
+ }
400
+
401
+ // Check if key is in a usable state
402
+ if (keyExists && keyMetadata) {
403
+ // Only 'Enabled' state allows cryptographic operations
404
+ if (keyMetadata.KeyState !== 'Enabled') {
405
+ // eslint-disable-next-line no-console
406
+ console.error(`KMS master key exists but is in state: ${keyMetadata.KeyState}`);
407
+
408
+ let testResult = '';
409
+ switch (keyMetadata.KeyState) {
410
+ case 'Disabled':
411
+ testResult = 'KMS master key is disabled';
412
+ break;
413
+ case 'PendingDeletion':
414
+ testResult = 'KMS master key is pending deletion';
415
+ break;
416
+ case 'PendingImport':
417
+ testResult = 'KMS master key is pending import';
418
+ break;
419
+ case 'Unavailable':
420
+ testResult = 'KMS master key is unavailable (custom key store disconnected)';
421
+ break;
422
+ case 'Creating':
423
+ testResult = 'KMS master key is still being created';
424
+ break;
425
+ case 'Updating':
426
+ testResult = 'KMS master key is being updated';
427
+ break;
428
+ default:
429
+ testResult = `KMS master key is in unusable state: ${keyMetadata.KeyState}`;
430
+ }
431
+
432
+ return {
433
+ status: 'unhealthy',
434
+ testResult,
435
+ canAccessKey: false,
436
+ keyExists: true,
437
+ keyState: keyMetadata.KeyState,
438
+ };
439
+ }
440
+ }
441
+
442
+ // Try to generate a data key to test full KMS access
443
+ // eslint-disable-next-line no-console
444
+ console.log('Attempting to generate data key...');
445
+ const startTime = Date.now();
446
+ const result = await withTimeout(
447
+ kms.generateDataKey({
448
+ KeyId: KMS_KEY_ARN,
449
+ KeySpec: 'AES_256'
450
+ }).promise(),
451
+ 10000,
452
+ 'KMS generateDataKey operation timed out after 10 seconds'
453
+ );
454
+
455
+ const responseTime = Date.now() - startTime;
456
+
457
+ // If we got a result with plaintext key, KMS access works
458
+ if (result && result.Plaintext) {
459
+ // eslint-disable-next-line no-console
460
+ console.log(`KMS key access successful, response time: ${responseTime}ms`);
461
+ return {
462
+ status: 'healthy',
463
+ testResult: 'Successfully requested and received decrypt key from KMS',
464
+ canAccessKey: true,
465
+ responseTime,
466
+ keyExists: true,
467
+ keyState: keyMetadata?.KeyState || 'Enabled',
468
+ };
469
+ }
470
+
471
+ return {
472
+ status: 'unhealthy',
473
+ testResult: 'KMS responded but no key data received',
474
+ canAccessKey: false,
475
+ responseTime,
476
+ keyExists: keyExists,
477
+ };
478
+ } catch (error) {
479
+ // eslint-disable-next-line no-console
480
+ console.error('KMS generateDataKey failed:', error.message);
481
+
482
+ // Provide more specific error messages based on error codes
483
+ let testResult = `KMS access failed: ${error.message}`;
484
+ if (error.code === 'NotFoundException') {
485
+ testResult = 'KMS master key not found during data key generation';
486
+ // eslint-disable-next-line no-console
487
+ console.error('Master key does not exist in KMS');
488
+ } else if (error.code === 'AccessDeniedException') {
489
+ testResult = 'Access denied - check IAM permissions for kms:GenerateDataKey';
490
+ // eslint-disable-next-line no-console
491
+ console.error('IAM permissions insufficient for KMS operations');
492
+ } else if (error.code === 'InvalidKeyId.NotFound') {
493
+ testResult = 'Invalid KMS key ID format or key not found';
494
+ // eslint-disable-next-line no-console
495
+ console.error('KMS key ID is invalid or does not exist');
496
+ }
497
+
498
+ return {
499
+ status: 'unhealthy',
500
+ testResult,
501
+ canAccessKey: false,
502
+ error: error.message,
503
+ errorCode: error.code,
504
+ };
505
+ }
506
+ };
507
+
336
508
  const checkEncryptionHealth = async () => {
337
509
  const config = getEncryptionConfiguration();
338
510
 
@@ -466,129 +638,81 @@ router.get('/health/detailed', async (_req, res) => {
466
638
  const startTime = Date.now();
467
639
  const response = buildHealthCheckResponse(startTime);
468
640
 
469
- // Run all async health checks concurrently
470
- const [
471
- databaseResult,
472
- encryptionResult,
473
- externalApisResult,
474
- integrationsResult,
475
- ] = await Promise.allSettled([
476
- checkDatabaseHealth().catch((error) => ({
477
- status: 'unhealthy',
478
- error: error.message,
479
- })),
480
- checkEncryptionHealth().catch((error) => ({
481
- status: 'unhealthy',
482
- error: error.message,
483
- })),
484
- checkExternalAPIs(),
485
- Promise.resolve().then(() => {
486
- try {
487
- return checkIntegrations();
488
- } catch (error) {
489
- return {
490
- status: 'unhealthy',
491
- error: error.message,
492
- };
493
- }
494
- }),
495
- ]);
496
-
497
- // Process database check results
498
- if (databaseResult.status === 'fulfilled') {
499
- response.checks.database = databaseResult.value;
641
+ try {
642
+ response.checks.database = await checkDatabaseHealth();
500
643
  const dbState = getDatabaseState();
501
- if (
502
- !dbState.isConnected ||
503
- response.checks.database.status === 'unhealthy'
504
- ) {
644
+ if (!dbState.isConnected) {
505
645
  response.status = 'unhealthy';
506
646
  }
507
647
  // eslint-disable-next-line no-console
508
648
  console.log('Database check completed:', response.checks.database);
509
- } else {
649
+ } catch (error) {
510
650
  response.checks.database = {
511
651
  status: 'unhealthy',
512
- error: databaseResult.reason?.message || 'Database check failed',
652
+ error: error.message,
513
653
  };
514
654
  response.status = 'unhealthy';
515
655
  // eslint-disable-next-line no-console
516
- console.log('Database check error:', databaseResult.reason?.message);
656
+ console.log('Database check error:', error.message);
517
657
  }
518
658
 
519
- // Process encryption check results
520
- if (encryptionResult.status === 'fulfilled') {
521
- response.checks.encryption = encryptionResult.value;
659
+ try {
660
+ response.checks.encryption = await checkEncryptionHealth();
522
661
  if (response.checks.encryption.status === 'unhealthy') {
523
662
  response.status = 'unhealthy';
524
663
  }
525
664
  // eslint-disable-next-line no-console
526
665
  console.log('Encryption check completed:', response.checks.encryption);
527
- } else {
666
+ } catch (error) {
528
667
  response.checks.encryption = {
529
668
  status: 'unhealthy',
530
- error:
531
- encryptionResult.reason?.message || 'Encryption check failed',
669
+ error: error.message,
532
670
  };
533
671
  response.status = 'unhealthy';
534
672
  // eslint-disable-next-line no-console
535
- console.log(
536
- 'Encryption check error:',
537
- encryptionResult.reason?.message
538
- );
673
+ console.log('Encryption check error:', error.message);
539
674
  }
540
675
 
541
- // Process external APIs check results
542
- if (externalApisResult.status === 'fulfilled') {
543
- const { apiStatuses, allReachable } = externalApisResult.value;
544
- response.checks.externalApis = apiStatuses;
545
- if (!allReachable) {
676
+ try {
677
+ response.checks.kmsAccess = await checkKMSAccess();
678
+ if (response.checks.kmsAccess.status === 'unhealthy') {
546
679
  response.status = 'unhealthy';
547
680
  }
548
681
  // eslint-disable-next-line no-console
549
- console.log(
550
- 'External APIs check completed:',
551
- response.checks.externalApis
552
- );
553
- } else {
554
- response.checks.externalApis = {
682
+ console.log('KMS access check completed:', response.checks.kmsAccess);
683
+ } catch (error) {
684
+ response.checks.kmsAccess = {
555
685
  status: 'unhealthy',
556
- error:
557
- externalApisResult.reason?.message ||
558
- 'External APIs check failed',
686
+ error: error.message,
559
687
  };
560
688
  response.status = 'unhealthy';
561
689
  // eslint-disable-next-line no-console
562
- console.log(
563
- 'External APIs check error:',
564
- externalApisResult.reason?.message
565
- );
690
+ console.log('KMS access check error:', error.message);
566
691
  }
567
692
 
568
- // Process integrations check results
569
- if (integrationsResult.status === 'fulfilled') {
570
- response.checks.integrations = integrationsResult.value;
571
- if (response.checks.integrations.status === 'unhealthy') {
572
- response.status = 'unhealthy';
573
- }
693
+ const { apiStatuses, allReachable } = await checkExternalAPIs();
694
+ response.checks.externalApis = apiStatuses;
695
+ if (!allReachable) {
696
+ response.status = 'unhealthy';
697
+ }
698
+ // eslint-disable-next-line no-console
699
+ console.log('External APIs check completed:', response.checks.externalApis);
700
+
701
+ try {
702
+ response.checks.integrations = checkIntegrations();
574
703
  // eslint-disable-next-line no-console
575
704
  console.log(
576
705
  'Integrations check completed:',
577
706
  response.checks.integrations
578
707
  );
579
- } else {
708
+ } catch (error) {
580
709
  response.checks.integrations = {
581
710
  status: 'unhealthy',
582
- error:
583
- integrationsResult.reason?.message ||
584
- 'Integrations check failed',
711
+ error: error.message,
585
712
  };
586
713
  response.status = 'unhealthy';
587
714
  // eslint-disable-next-line no-console
588
- console.log(
589
- 'Integrations check error:',
590
- integrationsResult.reason?.message
591
- );
715
+ console.log('Integrations check error:', error.message);
592
716
  }
593
717
 
594
718
  response.responseTime = response.calculateResponseTime();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.427.04558b7.0",
4
+ "version": "2.0.0--canary.427.bd07d1c.0",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
7
  "aws-sdk": "^2.1200.0",
@@ -22,9 +22,9 @@
22
22
  "uuid": "^9.0.1"
23
23
  },
24
24
  "devDependencies": {
25
- "@friggframework/eslint-config": "2.0.0--canary.427.04558b7.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.427.04558b7.0",
27
- "@friggframework/test": "2.0.0--canary.427.04558b7.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.427.bd07d1c.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.427.bd07d1c.0",
27
+ "@friggframework/test": "2.0.0--canary.427.bd07d1c.0",
28
28
  "@types/lodash": "4.17.15",
29
29
  "@typescript-eslint/eslint-plugin": "^8.0.0",
30
30
  "chai": "^4.3.6",
@@ -56,5 +56,5 @@
56
56
  "publishConfig": {
57
57
  "access": "public"
58
58
  },
59
- "gitHead": "04558b7a08b166c687327d9635bf872ca33f8b94"
59
+ "gitHead": "bd07d1c1eee5c2cc2881ba62beeeef06d17c4abf"
60
60
  }