@friggframework/core 2.0.0--canary.427.c8ff14b.0 → 2.0.0--canary.427.04558b7.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/database/mongo.js CHANGED
@@ -5,14 +5,16 @@
5
5
  const { Encrypt } = require('../encrypt');
6
6
  const { mongoose } = require('./mongoose');
7
7
  const { debug, flushDebugLog } = require('../logs');
8
+ const { findNearestBackendPackageJson } = require('../utils');
9
+ const path = require('path');
10
+ const fs = require('fs');
8
11
 
9
12
  mongoose.plugin(Encrypt);
10
13
  mongoose.set('applyPluginsToDiscriminators', true); // Needed for LHEncrypt
11
14
 
12
- // Buffering means mongoose will queue up operations if it gets
13
- // With serverless, better to fail fast if not connected.
14
- // disconnected from MongoDB and send them when it reconnects.
15
- const mongoConfig = {
15
+ // Load app definition to check for DocumentDB configuration
16
+ let appDefinition = {};
17
+ let mongoConfig = {
16
18
  useNewUrlParser: true,
17
19
  bufferCommands: false, // Disable mongoose buffering
18
20
  autoCreate: false, // Disable because auto creation does not work without buffering
@@ -28,9 +30,133 @@ const connectToDatabase = async () => {
28
30
  return;
29
31
  }
30
32
 
33
+ console.log('🔗 Connecting to database...');
34
+
35
+ // Load appDefinition inside the function
36
+ try {
37
+ console.log(
38
+ '🔍 Loading app definition for DocumentDB configuration...'
39
+ );
40
+
41
+ const backendPath = findNearestBackendPackageJson();
42
+ if (!backendPath) {
43
+ throw new Error('Could not find backend package.json');
44
+ }
45
+
46
+ const backendDir = path.dirname(backendPath);
47
+ const backendFilePath = path.join(backendDir, 'index.js');
48
+ if (!fs.existsSync(backendFilePath)) {
49
+ throw new Error('Could not find index.js');
50
+ }
51
+
52
+ const backend = require(backendFilePath);
53
+ appDefinition = backend.Definition;
54
+
55
+ console.log('📁 AppDefinition content:', JSON.stringify(appDefinition));
56
+
57
+ // Add DocumentDB TLS configuration if enabled
58
+ if (appDefinition.database?.documentDB?.enable === true) {
59
+ console.log('📄 DocumentDB configuration detected, enabling TLS');
60
+ console.log('📁 Current working directory:', process.cwd());
61
+ console.log(
62
+ '📋 App definition database config:',
63
+ JSON.stringify(appDefinition.database, null, 2)
64
+ );
65
+
66
+ mongoConfig.tls = true;
67
+
68
+ // Set TLS CA file path if specified
69
+ if (appDefinition.database.documentDB.tlsCAFile) {
70
+ const tlsCAFile = appDefinition.database.documentDB.tlsCAFile;
71
+
72
+ // Basic safety: reject obviously dangerous paths
73
+ if (tlsCAFile.includes('..') || path.isAbsolute(tlsCAFile)) {
74
+ console.warn(
75
+ '⚠️ Rejecting potentially unsafe tlsCAFile path:',
76
+ tlsCAFile
77
+ );
78
+ } else {
79
+ const tlsCAFilePath = path.resolve(
80
+ process.cwd(),
81
+ tlsCAFile
82
+ );
83
+
84
+ console.log('📄 DocumentDB TLS CA file configured:');
85
+ console.log(' 📎 Original path:', tlsCAFile);
86
+ console.log(' 📎 Resolved path:', tlsCAFilePath);
87
+ console.log(
88
+ ' 📄 File exists:',
89
+ fs.existsSync(tlsCAFilePath)
90
+ );
91
+
92
+ // Only set tlsCAFile if the file actually exists
93
+ if (fs.existsSync(tlsCAFilePath)) {
94
+ mongoConfig.tlsCAFile = tlsCAFilePath;
95
+ console.log('✅ TLS CA file configured successfully');
96
+ } else {
97
+ throw new Error(
98
+ `TLS CA file not found at ${tlsCAFilePath}`
99
+ );
100
+ }
101
+
102
+ // Debug directory listing (only in development)
103
+ if (process.env.NODE_ENV !== 'production') {
104
+ try {
105
+ console.log('📁 Current directory contents:');
106
+ fs.readdirSync(process.cwd()).forEach((item) => {
107
+ const stats = fs.statSync(
108
+ path.join(process.cwd(), item)
109
+ );
110
+ console.log(
111
+ ` ${
112
+ stats.isDirectory() ? '📁' : '📄'
113
+ } ${item}`
114
+ );
115
+ });
116
+
117
+ const securityDir = path.join(
118
+ process.cwd(),
119
+ 'security'
120
+ );
121
+ if (fs.existsSync(securityDir)) {
122
+ console.log('📁 Security directory contents:');
123
+ fs.readdirSync(securityDir).forEach((item) => {
124
+ console.log(` 📄 ${item}`);
125
+ });
126
+ } else {
127
+ console.log(
128
+ '❌ Security directory does not exist at:',
129
+ securityDir
130
+ );
131
+ }
132
+ } catch (error) {
133
+ console.log(
134
+ '❌ Error listing directory contents:',
135
+ error.message
136
+ );
137
+ }
138
+ }
139
+ }
140
+ }
141
+ } else {
142
+ console.log(
143
+ '📄 DocumentDB not enabled, using standard MongoDB configuration'
144
+ );
145
+ }
146
+ } catch (error) {
147
+ console.error('❌ Error loading app definition:', error.message);
148
+ debug(
149
+ 'Could not load app definition for DocumentDB configuration:',
150
+ error.message
151
+ );
152
+ }
153
+
154
+ console.log('🔗 MongoDB URI:', process.env.MONGO_URI ? 'SET' : 'NOT SET');
155
+ console.log('🔧 Final mongoConfig:', JSON.stringify(mongoConfig, null, 2));
156
+
31
157
  debug('=> using new database connection');
32
158
  await mongoose.connect(process.env.MONGO_URI, mongoConfig);
33
- debug('Connection state:', mongoose.STATES[mongoose.connection.readyState]);
159
+ debug('Connection state:', mongoose.STATES[mongoose.connection.readyState]);
34
160
  mongoose.connection.on('error', (error) => flushDebugLog(error));
35
161
  };
36
162
 
@@ -15,6 +15,7 @@ const validateApiKey = (req, res, next) => {
15
15
  }
16
16
 
17
17
  if (!apiKey || apiKey !== process.env.HEALTH_API_KEY) {
18
+ console.error('Unauthorized access attempt to health endpoint');
18
19
  return res.status(401).json({
19
20
  status: 'error',
20
21
  message: 'Unauthorized',
@@ -149,69 +150,6 @@ const createTestEncryptionModel = () => {
149
150
  );
150
151
  };
151
152
 
152
- const createTestDocument = async (TestModel) => {
153
- const testData = {
154
- testSecret: 'This is a secret value that should be encrypted',
155
- normalField: 'This is a normal field that should not be encrypted',
156
- nestedSecret: {
157
- value: 'This is a nested secret that should be encrypted',
158
- },
159
- };
160
-
161
- console.log('🔧 Creating test document with encryption...');
162
- const testDoc = new TestModel(testData);
163
-
164
- // Add timeout and detailed error logging
165
- const savePromise = testDoc.save();
166
- const timeoutPromise = new Promise((_, reject) => {
167
- setTimeout(
168
- () =>
169
- reject(new Error('Save operation timed out after 30 seconds')),
170
- 30000
171
- );
172
- });
173
-
174
- try {
175
- console.log('💾 Attempting to save document...');
176
- const startTime = Date.now();
177
-
178
- await Promise.race([savePromise, timeoutPromise]);
179
-
180
- const duration = Date.now() - startTime;
181
- console.log(`✅ Document saved successfully in ${duration}ms`);
182
-
183
- return { testDoc, testData };
184
- } catch (error) {
185
- console.error('❌ Save operation failed:', {
186
- errorName: error.name,
187
- errorMessage: error.message,
188
- errorStack: error.stack,
189
- mongooseConnectionState: testDoc.db.readyState,
190
- modelName: TestModel.modelName,
191
- });
192
-
193
- // Try to get more details about the connection
194
- if (mongoose.connection && mongoose.connection.db) {
195
- try {
196
- const admin = mongoose.connection.db.admin();
197
- const serverStatus = await admin.serverStatus();
198
- console.log('📊 DocumentDB Server Status:', {
199
- version: serverStatus.version,
200
- uptime: serverStatus.uptime,
201
- connections: serverStatus.connections,
202
- });
203
- } catch (statusError) {
204
- console.error(
205
- '❌ Could not get server status:',
206
- statusError.message
207
- );
208
- }
209
- }
210
-
211
- throw error;
212
- }
213
- };
214
-
215
153
  const verifyDecryption = (retrievedDoc, originalData) => {
216
154
  return (
217
155
  retrievedDoc &&
@@ -285,18 +223,95 @@ const evaluateEncryptionTestResults = (decryptionWorks, encryptionResults) => {
285
223
  };
286
224
  };
287
225
 
226
+ const withTimeout = (promise, ms, errorMessage) => {
227
+ return Promise.race([
228
+ promise,
229
+ new Promise((_, reject) =>
230
+ setTimeout(() => reject(new Error(errorMessage)), ms)
231
+ ),
232
+ ]);
233
+ };
234
+
288
235
  const testEncryption = async () => {
236
+ // eslint-disable-next-line no-console
237
+ console.log('Starting encryption test');
289
238
  const TestModel = createTestEncryptionModel();
290
- const { testDoc, testData } = await createTestDocument(TestModel);
239
+ // eslint-disable-next-line no-console
240
+ console.log('Test model created');
241
+
242
+ const testData = {
243
+ testSecret: 'This is a secret value that should be encrypted',
244
+ normalField: 'This is a normal field that should not be encrypted',
245
+ nestedSecret: {
246
+ value: 'This is a nested secret that should be encrypted',
247
+ },
248
+ };
249
+
250
+ const testDoc = new TestModel(testData);
291
251
 
292
252
  try {
293
- const retrievedDoc = await TestModel.findById(testDoc._id);
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
+ }
298
+
299
+ try {
300
+ const retrievedDoc = await withTimeout(
301
+ TestModel.findById(testDoc._id),
302
+ 5000,
303
+ 'Find operation timed out'
304
+ );
305
+ // eslint-disable-next-line no-console
306
+ console.log('Test document retrieved');
294
307
  const decryptionWorks = verifyDecryption(retrievedDoc, testData);
295
- const encryptionResults = await verifyEncryptionInDatabase(
296
- testDoc,
297
- testData,
298
- TestModel
308
+ const encryptionResults = await withTimeout(
309
+ verifyEncryptionInDatabase(testDoc, testData, TestModel),
310
+ 5000,
311
+ 'Database verification timed out'
299
312
  );
313
+ // eslint-disable-next-line no-console
314
+ console.log('Encryption verification completed');
300
315
 
301
316
  const evaluation = evaluateEncryptionTestResults(
302
317
  decryptionWorks,
@@ -308,7 +323,13 @@ const testEncryption = async () => {
308
323
  encryptionWorks: decryptionWorks,
309
324
  };
310
325
  } finally {
311
- await TestModel.deleteOne({ _id: testDoc._id });
326
+ await withTimeout(
327
+ TestModel.deleteOne({ _id: testDoc._id }),
328
+ 5000,
329
+ 'Delete operation timed out'
330
+ );
331
+ // eslint-disable-next-line no-console
332
+ console.log('Test document deleted');
312
333
  }
313
334
  };
314
335
 
@@ -316,6 +337,12 @@ const checkEncryptionHealth = async () => {
316
337
  const config = getEncryptionConfiguration();
317
338
 
318
339
  if (config.isBypassed || config.mode === 'none') {
340
+ // eslint-disable-next-line no-console
341
+ console.log('Encryption check bypassed:', {
342
+ stage: config.stage,
343
+ mode: config.mode,
344
+ });
345
+
319
346
  const testResult = config.isBypassed
320
347
  ? 'Encryption bypassed for this stage'
321
348
  : 'No encryption keys configured';
@@ -434,50 +461,134 @@ router.get('/health', async (_req, res) => {
434
461
  });
435
462
 
436
463
  router.get('/health/detailed', async (_req, res) => {
464
+ // eslint-disable-next-line no-console
465
+ console.log('Starting detailed health check');
437
466
  const startTime = Date.now();
438
467
  const response = buildHealthCheckResponse(startTime);
439
468
 
440
- try {
441
- response.checks.database = await checkDatabaseHealth();
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;
442
500
  const dbState = getDatabaseState();
443
- if (!dbState.isConnected) {
501
+ if (
502
+ !dbState.isConnected ||
503
+ response.checks.database.status === 'unhealthy'
504
+ ) {
444
505
  response.status = 'unhealthy';
445
506
  }
446
- } catch (error) {
507
+ // eslint-disable-next-line no-console
508
+ console.log('Database check completed:', response.checks.database);
509
+ } else {
447
510
  response.checks.database = {
448
511
  status: 'unhealthy',
449
- error: error.message,
512
+ error: databaseResult.reason?.message || 'Database check failed',
450
513
  };
451
514
  response.status = 'unhealthy';
515
+ // eslint-disable-next-line no-console
516
+ console.log('Database check error:', databaseResult.reason?.message);
452
517
  }
453
518
 
454
- try {
455
- response.checks.encryption = await checkEncryptionHealth();
519
+ // Process encryption check results
520
+ if (encryptionResult.status === 'fulfilled') {
521
+ response.checks.encryption = encryptionResult.value;
456
522
  if (response.checks.encryption.status === 'unhealthy') {
457
523
  response.status = 'unhealthy';
458
524
  }
459
- } catch (error) {
525
+ // eslint-disable-next-line no-console
526
+ console.log('Encryption check completed:', response.checks.encryption);
527
+ } else {
460
528
  response.checks.encryption = {
461
529
  status: 'unhealthy',
462
- error: error.message,
530
+ error:
531
+ encryptionResult.reason?.message || 'Encryption check failed',
463
532
  };
464
533
  response.status = 'unhealthy';
534
+ // eslint-disable-next-line no-console
535
+ console.log(
536
+ 'Encryption check error:',
537
+ encryptionResult.reason?.message
538
+ );
465
539
  }
466
540
 
467
- const { apiStatuses, allReachable } = await checkExternalAPIs();
468
- response.checks.externalApis = apiStatuses;
469
- if (!allReachable) {
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) {
546
+ response.status = 'unhealthy';
547
+ }
548
+ // 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 = {
555
+ status: 'unhealthy',
556
+ error:
557
+ externalApisResult.reason?.message ||
558
+ 'External APIs check failed',
559
+ };
470
560
  response.status = 'unhealthy';
561
+ // eslint-disable-next-line no-console
562
+ console.log(
563
+ 'External APIs check error:',
564
+ externalApisResult.reason?.message
565
+ );
471
566
  }
472
567
 
473
- try {
474
- response.checks.integrations = checkIntegrations();
475
- } catch (error) {
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
+ }
574
+ // eslint-disable-next-line no-console
575
+ console.log(
576
+ 'Integrations check completed:',
577
+ response.checks.integrations
578
+ );
579
+ } else {
476
580
  response.checks.integrations = {
477
581
  status: 'unhealthy',
478
- error: error.message,
582
+ error:
583
+ integrationsResult.reason?.message ||
584
+ 'Integrations check failed',
479
585
  };
480
586
  response.status = 'unhealthy';
587
+ // eslint-disable-next-line no-console
588
+ console.log(
589
+ 'Integrations check error:',
590
+ integrationsResult.reason?.message
591
+ );
481
592
  }
482
593
 
483
594
  response.responseTime = response.calculateResponseTime();
@@ -485,6 +596,14 @@ router.get('/health/detailed', async (_req, res) => {
485
596
 
486
597
  const statusCode = response.status === 'healthy' ? 200 : 503;
487
598
  res.status(statusCode).json(response);
599
+
600
+ // eslint-disable-next-line no-console
601
+ console.log(
602
+ 'Final health status:',
603
+ response.status,
604
+ 'Response time:',
605
+ response.responseTime
606
+ );
488
607
  });
489
608
 
490
609
  router.get('/health/live', (_req, res) => {
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.c8ff14b.0",
4
+ "version": "2.0.0--canary.427.04558b7.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.c8ff14b.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.427.c8ff14b.0",
27
- "@friggframework/test": "2.0.0--canary.427.c8ff14b.0",
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",
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": "c8ff14b4f6c1a21bd6c254ad12f1c9963b0374db"
59
+ "gitHead": "04558b7a08b166c687327d9635bf872ca33f8b94"
60
60
  }