@friggframework/core 2.0.0--canary.427.9708b13.0 → 2.0.0--canary.427.972de91.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,193 @@ 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(
350
+ 'Testing KMS key access with key:',
351
+ KMS_KEY_ARN.substring(0, 50) + '...'
352
+ );
353
+
354
+ const AWS = require('aws-sdk');
355
+ const kms = new AWS.KMS();
356
+
357
+ // First, check if the master key exists and is accessible
358
+ let keyExists = false;
359
+ let keyMetadata = null;
360
+
361
+ try {
362
+ // eslint-disable-next-line no-console
363
+ console.log('Checking if KMS master key exists...');
364
+ const describeResult = await withTimeout(
365
+ kms.describeKey({ KeyId: KMS_KEY_ARN }).promise(),
366
+ 5000,
367
+ 'KMS describeKey operation timed out after 5 seconds'
368
+ );
369
+
370
+ keyMetadata = describeResult.KeyMetadata;
371
+ keyExists = true;
372
+ // eslint-disable-next-line no-console
373
+ console.log('KMS master key found:', {
374
+ KeyId: keyMetadata.KeyId,
375
+ KeyState: keyMetadata.KeyState,
376
+ Enabled: keyMetadata.Enabled,
377
+ KeyUsage: keyMetadata.KeyUsage,
378
+ });
379
+ } catch (describeError) {
380
+ // eslint-disable-next-line no-console
381
+ console.error(
382
+ 'KMS master key does not exist or is not accessible:',
383
+ describeError.message
384
+ );
385
+
386
+ if (describeError.code === 'NotFoundException') {
387
+ return {
388
+ status: 'unhealthy',
389
+ testResult: 'KMS master key not found',
390
+ canAccessKey: false,
391
+ error: 'Master key does not exist',
392
+ keyExists: false,
393
+ };
394
+ } else if (describeError.code === 'AccessDeniedException') {
395
+ return {
396
+ status: 'unhealthy',
397
+ testResult: 'No permission to access KMS master key',
398
+ canAccessKey: false,
399
+ error: 'Access denied to master key',
400
+ keyExists: 'unknown',
401
+ };
402
+ }
403
+ // Continue to try generateDataKey even if describeKey fails
404
+ // as permissions might be limited
405
+ }
406
+
407
+ // Check if key is in a usable state
408
+ if (keyExists && keyMetadata) {
409
+ // Only 'Enabled' state allows cryptographic operations
410
+ if (keyMetadata.KeyState !== 'Enabled') {
411
+ // eslint-disable-next-line no-console
412
+ console.error(
413
+ `KMS master key exists but is in state: ${keyMetadata.KeyState}`
414
+ );
415
+
416
+ let testResult = '';
417
+ switch (keyMetadata.KeyState) {
418
+ case 'Disabled':
419
+ testResult = 'KMS master key is disabled';
420
+ break;
421
+ case 'PendingDeletion':
422
+ testResult = 'KMS master key is pending deletion';
423
+ break;
424
+ case 'PendingImport':
425
+ testResult = 'KMS master key is pending import';
426
+ break;
427
+ case 'Unavailable':
428
+ testResult =
429
+ 'KMS master key is unavailable (custom key store disconnected)';
430
+ break;
431
+ case 'Creating':
432
+ testResult = 'KMS master key is still being created';
433
+ break;
434
+ case 'Updating':
435
+ testResult = 'KMS master key is being updated';
436
+ break;
437
+ default:
438
+ testResult = `KMS master key is in unusable state: ${keyMetadata.KeyState}`;
439
+ }
440
+
441
+ return {
442
+ status: 'unhealthy',
443
+ testResult,
444
+ canAccessKey: false,
445
+ keyExists: true,
446
+ keyState: keyMetadata.KeyState,
447
+ };
448
+ }
449
+ }
450
+
451
+ // Try to generate a data key to test full KMS access
452
+ // eslint-disable-next-line no-console
453
+ console.log('Attempting to generate data key...');
454
+ const startTime = Date.now();
455
+ const result = await withTimeout(
456
+ kms
457
+ .generateDataKey({
458
+ KeyId: KMS_KEY_ARN,
459
+ KeySpec: 'AES_256',
460
+ })
461
+ .promise(),
462
+ 10000,
463
+ 'KMS generateDataKey operation timed out after 10 seconds'
464
+ );
465
+
466
+ const responseTime = Date.now() - startTime;
467
+
468
+ // If we got a result with plaintext key, KMS access works
469
+ if (result && result.Plaintext) {
470
+ // eslint-disable-next-line no-console
471
+ console.log(
472
+ `KMS key access successful, response time: ${responseTime}ms`
473
+ );
474
+ return {
475
+ status: 'healthy',
476
+ testResult:
477
+ 'Successfully requested and received decrypt key from KMS',
478
+ canAccessKey: true,
479
+ responseTime,
480
+ keyExists: true,
481
+ keyState: keyMetadata?.KeyState || 'Enabled',
482
+ };
483
+ }
484
+
485
+ return {
486
+ status: 'unhealthy',
487
+ testResult: 'KMS responded but no key data received',
488
+ canAccessKey: false,
489
+ responseTime,
490
+ keyExists: keyExists,
491
+ };
492
+ } catch (error) {
493
+ // eslint-disable-next-line no-console
494
+ console.error('KMS generateDataKey failed:', error.message);
495
+
496
+ // Provide more specific error messages based on error codes
497
+ let testResult = `KMS access failed: ${error.message}`;
498
+ if (error.code === 'NotFoundException') {
499
+ testResult = 'KMS master key not found during data key generation';
500
+ // eslint-disable-next-line no-console
501
+ console.error('Master key does not exist in KMS');
502
+ } else if (error.code === 'AccessDeniedException') {
503
+ testResult =
504
+ 'Access denied - check IAM permissions for kms:GenerateDataKey';
505
+ // eslint-disable-next-line no-console
506
+ console.error('IAM permissions insufficient for KMS operations');
507
+ } else if (error.code === 'InvalidKeyId.NotFound') {
508
+ testResult = 'Invalid KMS key ID format or key not found';
509
+ // eslint-disable-next-line no-console
510
+ console.error('KMS key ID is invalid or does not exist');
511
+ }
512
+
513
+ return {
514
+ status: 'unhealthy',
515
+ testResult,
516
+ canAccessKey: false,
517
+ error: error.message,
518
+ errorCode: error.code,
519
+ };
520
+ }
521
+ };
522
+
336
523
  const checkEncryptionHealth = async () => {
337
524
  const config = getEncryptionConfiguration();
338
525
 
@@ -466,64 +653,113 @@ router.get('/health/detailed', async (_req, res) => {
466
653
  const startTime = Date.now();
467
654
  const response = buildHealthCheckResponse(startTime);
468
655
 
469
- try {
470
- response.checks.database = await checkDatabaseHealth();
656
+ // Run all health checks in parallel for faster response
657
+ // eslint-disable-next-line no-console
658
+ console.log('Running all health checks in parallel...');
659
+
660
+ const [
661
+ kmsResult,
662
+ databaseResult,
663
+ encryptionResult,
664
+ externalApisResult,
665
+ integrationsResult,
666
+ ] = await Promise.allSettled([
667
+ checkKMSAccess(),
668
+ checkDatabaseHealth(),
669
+ checkEncryptionHealth(),
670
+ checkExternalAPIs(),
671
+ Promise.resolve(checkIntegrations()), // Wrap sync function in Promise
672
+ ]);
673
+
674
+ // Process KMS check result
675
+ if (kmsResult.status === 'fulfilled') {
676
+ response.checks.kmsAccess = kmsResult.value;
677
+ if (kmsResult.value.status === 'unhealthy') {
678
+ response.status = 'unhealthy';
679
+ }
680
+ // eslint-disable-next-line no-console
681
+ console.log('KMS access check completed:', response.checks.kmsAccess);
682
+ } else {
683
+ response.checks.kmsAccess = {
684
+ status: 'unhealthy',
685
+ error: kmsResult.reason?.message || 'KMS check failed',
686
+ };
687
+ response.status = 'unhealthy';
688
+ // eslint-disable-next-line no-console
689
+ console.log('KMS access check error:', kmsResult.reason?.message);
690
+ }
691
+
692
+ // Process database check result
693
+ if (databaseResult.status === 'fulfilled') {
694
+ response.checks.database = databaseResult.value;
471
695
  const dbState = getDatabaseState();
472
696
  if (!dbState.isConnected) {
473
697
  response.status = 'unhealthy';
474
698
  }
475
699
  // eslint-disable-next-line no-console
476
700
  console.log('Database check completed:', response.checks.database);
477
- } catch (error) {
701
+ } else {
478
702
  response.checks.database = {
479
703
  status: 'unhealthy',
480
- error: error.message,
704
+ error: databaseResult.reason?.message || 'Database check failed',
481
705
  };
482
706
  response.status = 'unhealthy';
483
707
  // eslint-disable-next-line no-console
484
- console.log('Database check error:', error.message);
708
+ console.log('Database check error:', databaseResult.reason?.message);
485
709
  }
486
710
 
487
- try {
488
- response.checks.encryption = await checkEncryptionHealth();
489
- if (response.checks.encryption.status === 'unhealthy') {
711
+ // Process encryption check result
712
+ if (encryptionResult.status === 'fulfilled') {
713
+ response.checks.encryption = encryptionResult.value;
714
+ if (encryptionResult.value.status === 'unhealthy') {
490
715
  response.status = 'unhealthy';
491
716
  }
492
717
  // eslint-disable-next-line no-console
493
718
  console.log('Encryption check completed:', response.checks.encryption);
494
- } catch (error) {
719
+ } else {
495
720
  response.checks.encryption = {
496
721
  status: 'unhealthy',
497
- error: error.message,
722
+ error: encryptionResult.reason?.message || 'Encryption check failed',
498
723
  };
499
724
  response.status = 'unhealthy';
500
725
  // eslint-disable-next-line no-console
501
- console.log('Encryption check error:', error.message);
726
+ console.log('Encryption check error:', encryptionResult.reason?.message);
502
727
  }
503
728
 
504
- const { apiStatuses, allReachable } = await checkExternalAPIs();
505
- response.checks.externalApis = apiStatuses;
506
- if (!allReachable) {
729
+ // Process external APIs check result
730
+ if (externalApisResult.status === 'fulfilled') {
731
+ const { apiStatuses, allReachable } = externalApisResult.value;
732
+ response.checks.externalApis = apiStatuses;
733
+ if (!allReachable) {
734
+ response.status = 'unhealthy';
735
+ }
736
+ // eslint-disable-next-line no-console
737
+ console.log('External APIs check completed:', response.checks.externalApis);
738
+ } else {
739
+ response.checks.externalApis = {
740
+ error: externalApisResult.reason?.message || 'External APIs check failed',
741
+ };
507
742
  response.status = 'unhealthy';
743
+ // eslint-disable-next-line no-console
744
+ console.log('External APIs check error:', externalApisResult.reason?.message);
508
745
  }
509
- // eslint-disable-next-line no-console
510
- console.log('External APIs check completed:', response.checks.externalApis);
511
746
 
512
- try {
513
- response.checks.integrations = checkIntegrations();
747
+ // Process integrations check result
748
+ if (integrationsResult.status === 'fulfilled') {
749
+ response.checks.integrations = integrationsResult.value;
514
750
  // eslint-disable-next-line no-console
515
751
  console.log(
516
752
  'Integrations check completed:',
517
753
  response.checks.integrations
518
754
  );
519
- } catch (error) {
755
+ } else {
520
756
  response.checks.integrations = {
521
757
  status: 'unhealthy',
522
- error: error.message,
758
+ error: integrationsResult.reason?.message || 'Integrations check failed',
523
759
  };
524
760
  response.status = 'unhealthy';
525
761
  // eslint-disable-next-line no-console
526
- console.log('Integrations check error:', error.message);
762
+ console.log('Integrations check error:', integrationsResult.reason?.message);
527
763
  }
528
764
 
529
765
  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.9708b13.0",
4
+ "version": "2.0.0--canary.427.972de91.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.9708b13.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.427.9708b13.0",
27
- "@friggframework/test": "2.0.0--canary.427.9708b13.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.427.972de91.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.427.972de91.0",
27
+ "@friggframework/test": "2.0.0--canary.427.972de91.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": "9708b13e311605ac2620ee524dd2f19859fda32e"
59
+ "gitHead": "972de918c8273da9f1ffb85e1eedfc088f880f54"
60
60
  }