@friggframework/core 2.0.0--canary.427.972de91.0 → 2.0.0--canary.428.a673c47.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.
@@ -248,53 +248,9 @@ const testEncryption = async () => {
248
248
  };
249
249
 
250
250
  const testDoc = new TestModel(testData);
251
-
252
- try {
253
- // eslint-disable-next-line no-console
254
- console.log('Attempting to save document with encryption...');
255
- const startTime = Date.now();
256
-
257
- await withTimeout(
258
- testDoc.save(),
259
- 30000,
260
- 'Save operation timed out after 30 seconds'
261
- );
262
-
263
- const duration = Date.now() - startTime;
264
- // eslint-disable-next-line no-console
265
- console.log(`Test document saved successfully in ${duration}ms`);
266
- } catch (error) {
267
- // eslint-disable-next-line no-console
268
- console.error('Save operation failed:', {
269
- errorName: error.name,
270
- errorMessage: error.message,
271
- errorStack: error.stack,
272
- mongooseConnectionState: testDoc.db.readyState,
273
- modelName: TestModel.modelName,
274
- });
275
-
276
- // Try to get more details about the connection
277
- if (mongoose.connection && mongoose.connection.db) {
278
- try {
279
- const admin = mongoose.connection.db.admin();
280
- const serverStatus = await admin.serverStatus();
281
- // eslint-disable-next-line no-console
282
- console.log('DocumentDB Server Status:', {
283
- version: serverStatus.version,
284
- uptime: serverStatus.uptime,
285
- connections: serverStatus.connections,
286
- });
287
- } catch (statusError) {
288
- // eslint-disable-next-line no-console
289
- console.error(
290
- 'Could not get server status:',
291
- statusError.message
292
- );
293
- }
294
- }
295
-
296
- throw error;
297
- }
251
+ await withTimeout(testDoc.save(), 5000, 'Save operation timed out');
252
+ // eslint-disable-next-line no-console
253
+ console.log('Test document saved');
298
254
 
299
255
  try {
300
256
  const retrievedDoc = await withTimeout(
@@ -333,193 +289,6 @@ const testEncryption = async () => {
333
289
  }
334
290
  };
335
291
 
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
-
523
292
  const checkEncryptionHealth = async () => {
524
293
  const config = getEncryptionConfiguration();
525
294
 
@@ -637,6 +406,47 @@ const buildHealthCheckResponse = (startTime) => {
637
406
  };
638
407
  };
639
408
 
409
+ // KMS decrypt capability check
410
+ const checkKmsDecryptCapability = async () => {
411
+ const start = Date.now();
412
+ const { KMS_KEY_ARN } = process.env;
413
+ if (!KMS_KEY_ARN) {
414
+ return {
415
+ status: 'skipped',
416
+ reason: 'KMS_KEY_ARN not configured',
417
+ };
418
+ }
419
+ try {
420
+ // Lazy load to avoid cost if unused in some environments
421
+ // eslint-disable-next-line global-require
422
+ const AWS = require('aws-sdk');
423
+ const kms = new AWS.KMS();
424
+ // Generate a data key (without plaintext logging) then immediately decrypt ciphertext to ensure decrypt perms.
425
+ const dataKeyResp = await kms
426
+ .generateDataKey({ KeyId: KMS_KEY_ARN, KeySpec: 'AES_256' })
427
+ .promise();
428
+ const decryptResp = await kms
429
+ .decrypt({ CiphertextBlob: dataKeyResp.CiphertextBlob })
430
+ .promise();
431
+
432
+ const success = Boolean(
433
+ dataKeyResp.CiphertextBlob && decryptResp.Plaintext
434
+ );
435
+
436
+ return {
437
+ status: success ? 'healthy' : 'unhealthy',
438
+ kmsKeyArnSuffix: KMS_KEY_ARN.slice(-12),
439
+ latencyMs: Date.now() - start,
440
+ };
441
+ } catch (error) {
442
+ return {
443
+ status: 'unhealthy',
444
+ error: error.message,
445
+ latencyMs: Date.now() - start,
446
+ };
447
+ }
448
+ };
449
+
640
450
  router.get('/health', async (_req, res) => {
641
451
  const status = {
642
452
  status: 'ok',
@@ -653,113 +463,79 @@ router.get('/health/detailed', async (_req, res) => {
653
463
  const startTime = Date.now();
654
464
  const response = buildHealthCheckResponse(startTime);
655
465
 
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') {
466
+ // 1. KMS decrypt capability (must succeed before DB assumed healthy if encryption depends on KMS)
467
+ try {
468
+ response.checks.kms = await checkKmsDecryptCapability();
469
+ if (response.checks.kms.status === 'unhealthy') {
678
470
  response.status = 'unhealthy';
679
471
  }
680
472
  // 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
- };
473
+ console.log('KMS check completed:', response.checks.kms);
474
+ } catch (error) {
475
+ response.checks.kms = { status: 'unhealthy', error: error.message };
687
476
  response.status = 'unhealthy';
688
477
  // eslint-disable-next-line no-console
689
- console.log('KMS access check error:', kmsResult.reason?.message);
478
+ console.log('KMS check error:', error.message);
690
479
  }
691
480
 
692
- // Process database check result
693
- if (databaseResult.status === 'fulfilled') {
694
- response.checks.database = databaseResult.value;
481
+ try {
482
+ response.checks.database = await checkDatabaseHealth();
695
483
  const dbState = getDatabaseState();
696
484
  if (!dbState.isConnected) {
697
485
  response.status = 'unhealthy';
698
486
  }
699
487
  // eslint-disable-next-line no-console
700
488
  console.log('Database check completed:', response.checks.database);
701
- } else {
489
+ } catch (error) {
702
490
  response.checks.database = {
703
491
  status: 'unhealthy',
704
- error: databaseResult.reason?.message || 'Database check failed',
492
+ error: error.message,
705
493
  };
706
494
  response.status = 'unhealthy';
707
495
  // eslint-disable-next-line no-console
708
- console.log('Database check error:', databaseResult.reason?.message);
496
+ console.log('Database check error:', error.message);
709
497
  }
710
498
 
711
- // Process encryption check result
712
- if (encryptionResult.status === 'fulfilled') {
713
- response.checks.encryption = encryptionResult.value;
714
- if (encryptionResult.value.status === 'unhealthy') {
499
+ try {
500
+ response.checks.encryption = await checkEncryptionHealth();
501
+ if (response.checks.encryption.status === 'unhealthy') {
715
502
  response.status = 'unhealthy';
716
503
  }
717
504
  // eslint-disable-next-line no-console
718
505
  console.log('Encryption check completed:', response.checks.encryption);
719
- } else {
506
+ } catch (error) {
720
507
  response.checks.encryption = {
721
508
  status: 'unhealthy',
722
- error: encryptionResult.reason?.message || 'Encryption check failed',
509
+ error: error.message,
723
510
  };
724
511
  response.status = 'unhealthy';
725
512
  // eslint-disable-next-line no-console
726
- console.log('Encryption check error:', encryptionResult.reason?.message);
513
+ console.log('Encryption check error:', error.message);
727
514
  }
728
515
 
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
- };
516
+ const { apiStatuses, allReachable } = await checkExternalAPIs();
517
+ response.checks.externalApis = apiStatuses;
518
+ if (!allReachable) {
742
519
  response.status = 'unhealthy';
743
- // eslint-disable-next-line no-console
744
- console.log('External APIs check error:', externalApisResult.reason?.message);
745
520
  }
521
+ // eslint-disable-next-line no-console
522
+ console.log('External APIs check completed:', response.checks.externalApis);
746
523
 
747
- // Process integrations check result
748
- if (integrationsResult.status === 'fulfilled') {
749
- response.checks.integrations = integrationsResult.value;
524
+ try {
525
+ response.checks.integrations = checkIntegrations();
750
526
  // eslint-disable-next-line no-console
751
527
  console.log(
752
528
  'Integrations check completed:',
753
529
  response.checks.integrations
754
530
  );
755
- } else {
531
+ } catch (error) {
756
532
  response.checks.integrations = {
757
533
  status: 'unhealthy',
758
- error: integrationsResult.reason?.message || 'Integrations check failed',
534
+ error: error.message,
759
535
  };
760
536
  response.status = 'unhealthy';
761
537
  // eslint-disable-next-line no-console
762
- console.log('Integrations check error:', integrationsResult.reason?.message);
538
+ console.log('Integrations check error:', error.message);
763
539
  }
764
540
 
765
541
  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.972de91.0",
4
+ "version": "2.0.0--canary.428.a673c47.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.972de91.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.427.972de91.0",
27
- "@friggframework/test": "2.0.0--canary.427.972de91.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.428.a673c47.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.428.a673c47.0",
27
+ "@friggframework/test": "2.0.0--canary.428.a673c47.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": "972de918c8273da9f1ffb85e1eedfc088f880f54"
59
+ "gitHead": "a673c4784ce0e950d7bbfb3397f855b086524eca"
60
60
  }