@friggframework/core 2.0.0-next.38 → 2.0.0-next.39
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 +131 -5
- package/handlers/routers/health.js +185 -101
- package/package.json +5 -5
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
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
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:',
|
|
159
|
+
debug('Connection state:', mongoose.STATES[mongoose.connection.readyState]);
|
|
34
160
|
mongoose.connection.on('error', (error) => flushDebugLog(error));
|
|
35
161
|
};
|
|
36
162
|
|
|
@@ -9,18 +9,19 @@ const router = Router();
|
|
|
9
9
|
|
|
10
10
|
const validateApiKey = (req, res, next) => {
|
|
11
11
|
const apiKey = req.headers['x-api-key'];
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
if (req.path === '/health') {
|
|
14
14
|
return 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
|
-
message: 'Unauthorized'
|
|
21
|
+
message: 'Unauthorized',
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
next();
|
|
25
26
|
};
|
|
26
27
|
|
|
@@ -30,7 +31,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
30
31
|
return new Promise((resolve) => {
|
|
31
32
|
const protocol = url.startsWith('https:') ? https : http;
|
|
32
33
|
const startTime = Date.now();
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
try {
|
|
35
36
|
const request = protocol.get(url, { timeout }, (res) => {
|
|
36
37
|
const responseTime = Date.now() - startTime;
|
|
@@ -38,7 +39,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
38
39
|
status: 'healthy',
|
|
39
40
|
statusCode: res.statusCode,
|
|
40
41
|
responseTime,
|
|
41
|
-
reachable: res.statusCode < 500
|
|
42
|
+
reachable: res.statusCode < 500,
|
|
42
43
|
});
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -47,7 +48,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
47
48
|
status: 'unhealthy',
|
|
48
49
|
error: error.message,
|
|
49
50
|
responseTime: Date.now() - startTime,
|
|
50
|
-
reachable: false
|
|
51
|
+
reachable: false,
|
|
51
52
|
});
|
|
52
53
|
});
|
|
53
54
|
|
|
@@ -57,7 +58,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
57
58
|
status: 'timeout',
|
|
58
59
|
error: 'Request timeout',
|
|
59
60
|
responseTime: timeout,
|
|
60
|
-
reachable: false
|
|
61
|
+
reachable: false,
|
|
61
62
|
});
|
|
62
63
|
});
|
|
63
64
|
} catch (error) {
|
|
@@ -65,7 +66,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
65
66
|
status: 'error',
|
|
66
67
|
error: error.message,
|
|
67
68
|
responseTime: Date.now() - startTime,
|
|
68
|
-
reachable: false
|
|
69
|
+
reachable: false,
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
72
|
});
|
|
@@ -76,14 +77,14 @@ const getDatabaseState = () => {
|
|
|
76
77
|
0: 'disconnected',
|
|
77
78
|
1: 'connected',
|
|
78
79
|
2: 'connecting',
|
|
79
|
-
3: 'disconnecting'
|
|
80
|
+
3: 'disconnecting',
|
|
80
81
|
};
|
|
81
82
|
const readyState = mongoose.connection.readyState;
|
|
82
|
-
|
|
83
|
+
|
|
83
84
|
return {
|
|
84
85
|
readyState,
|
|
85
86
|
stateName: stateMap[readyState],
|
|
86
|
-
isConnected: readyState === 1
|
|
87
|
+
isConnected: readyState === 1,
|
|
87
88
|
};
|
|
88
89
|
};
|
|
89
90
|
|
|
@@ -91,7 +92,7 @@ const checkDatabaseHealth = async () => {
|
|
|
91
92
|
const { stateName, isConnected } = getDatabaseState();
|
|
92
93
|
const result = {
|
|
93
94
|
status: isConnected ? 'healthy' : 'unhealthy',
|
|
94
|
-
state: stateName
|
|
95
|
+
state: stateName,
|
|
95
96
|
};
|
|
96
97
|
|
|
97
98
|
if (isConnected) {
|
|
@@ -104,19 +105,20 @@ const checkDatabaseHealth = async () => {
|
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
const getEncryptionConfiguration = () => {
|
|
107
|
-
const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } =
|
|
108
|
-
|
|
108
|
+
const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } =
|
|
109
|
+
process.env;
|
|
110
|
+
|
|
109
111
|
const defaultBypassStages = ['dev', 'test', 'local'];
|
|
110
112
|
const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
|
|
111
113
|
const bypassStages = useEnv
|
|
112
114
|
? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
|
|
113
115
|
: defaultBypassStages;
|
|
114
|
-
|
|
116
|
+
|
|
115
117
|
const isBypassed = bypassStages.includes(STAGE);
|
|
116
118
|
const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
|
|
117
119
|
const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '' && !hasAES;
|
|
118
120
|
const mode = hasAES ? 'aes' : hasKMS ? 'kms' : 'none';
|
|
119
|
-
|
|
121
|
+
|
|
120
122
|
return {
|
|
121
123
|
stage: STAGE || null,
|
|
122
124
|
isBypassed,
|
|
@@ -128,41 +130,33 @@ const getEncryptionConfiguration = () => {
|
|
|
128
130
|
|
|
129
131
|
const createTestEncryptionModel = () => {
|
|
130
132
|
const { Encrypt } = require('./../../encrypt');
|
|
131
|
-
|
|
132
|
-
const testSchema = new mongoose.Schema({
|
|
133
|
-
testSecret: { type: String, lhEncrypt: true },
|
|
134
|
-
normalField: { type: String },
|
|
135
|
-
nestedSecret: {
|
|
136
|
-
value: { type: String, lhEncrypt: true }
|
|
137
|
-
}
|
|
138
|
-
}, { timestamps: false });
|
|
139
133
|
|
|
140
|
-
testSchema.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
134
|
+
const testSchema = new mongoose.Schema(
|
|
135
|
+
{
|
|
136
|
+
testSecret: { type: String, lhEncrypt: true },
|
|
137
|
+
normalField: { type: String },
|
|
138
|
+
nestedSecret: {
|
|
139
|
+
value: { type: String, lhEncrypt: true },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{ timestamps: false }
|
|
143
|
+
);
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
const testData = {
|
|
148
|
-
testSecret: 'This is a secret value that should be encrypted',
|
|
149
|
-
normalField: 'This is a normal field that should not be encrypted',
|
|
150
|
-
nestedSecret: {
|
|
151
|
-
value: 'This is a nested secret that should be encrypted'
|
|
152
|
-
}
|
|
153
|
-
};
|
|
145
|
+
testSchema.plugin(Encrypt);
|
|
154
146
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
return (
|
|
148
|
+
mongoose.models.TestEncryption ||
|
|
149
|
+
mongoose.model('TestEncryption', testSchema)
|
|
150
|
+
);
|
|
159
151
|
};
|
|
160
152
|
|
|
161
153
|
const verifyDecryption = (retrievedDoc, originalData) => {
|
|
162
|
-
return
|
|
154
|
+
return (
|
|
155
|
+
retrievedDoc &&
|
|
163
156
|
retrievedDoc.testSecret === originalData.testSecret &&
|
|
164
157
|
retrievedDoc.normalField === originalData.normalField &&
|
|
165
|
-
retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value
|
|
158
|
+
retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value
|
|
159
|
+
);
|
|
166
160
|
};
|
|
167
161
|
|
|
168
162
|
const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
|
|
@@ -171,84 +165,144 @@ const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
|
|
|
171
165
|
.collection(collectionName)
|
|
172
166
|
.findOne({ _id: testDoc._id });
|
|
173
167
|
|
|
174
|
-
const secretIsEncrypted =
|
|
175
|
-
|
|
176
|
-
rawDoc.testSecret
|
|
168
|
+
const secretIsEncrypted =
|
|
169
|
+
rawDoc &&
|
|
170
|
+
typeof rawDoc.testSecret === 'string' &&
|
|
171
|
+
rawDoc.testSecret.includes(':') &&
|
|
177
172
|
rawDoc.testSecret !== originalData.testSecret;
|
|
178
|
-
|
|
179
|
-
const nestedIsEncrypted =
|
|
173
|
+
|
|
174
|
+
const nestedIsEncrypted =
|
|
175
|
+
rawDoc?.nestedSecret?.value &&
|
|
180
176
|
typeof rawDoc.nestedSecret.value === 'string' &&
|
|
181
|
-
rawDoc.nestedSecret.value.includes(':') &&
|
|
177
|
+
rawDoc.nestedSecret.value.includes(':') &&
|
|
182
178
|
rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
|
|
183
|
-
|
|
184
|
-
const normalNotEncrypted =
|
|
185
|
-
rawDoc.normalField === originalData.normalField;
|
|
179
|
+
|
|
180
|
+
const normalNotEncrypted =
|
|
181
|
+
rawDoc && rawDoc.normalField === originalData.normalField;
|
|
186
182
|
|
|
187
183
|
return {
|
|
188
184
|
secretIsEncrypted,
|
|
189
185
|
nestedIsEncrypted,
|
|
190
|
-
normalNotEncrypted
|
|
186
|
+
normalNotEncrypted,
|
|
191
187
|
};
|
|
192
188
|
};
|
|
193
189
|
|
|
194
190
|
const evaluateEncryptionTestResults = (decryptionWorks, encryptionResults) => {
|
|
195
|
-
const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } =
|
|
196
|
-
|
|
197
|
-
|
|
191
|
+
const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } =
|
|
192
|
+
encryptionResults;
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
decryptionWorks &&
|
|
196
|
+
secretIsEncrypted &&
|
|
197
|
+
nestedIsEncrypted &&
|
|
198
|
+
normalNotEncrypted
|
|
199
|
+
) {
|
|
198
200
|
return {
|
|
199
201
|
status: 'enabled',
|
|
200
|
-
testResult: 'Encryption and decryption verified successfully'
|
|
202
|
+
testResult: 'Encryption and decryption verified successfully',
|
|
201
203
|
};
|
|
202
204
|
}
|
|
203
|
-
|
|
205
|
+
|
|
204
206
|
if (decryptionWorks && (!secretIsEncrypted || !nestedIsEncrypted)) {
|
|
205
207
|
return {
|
|
206
208
|
status: 'unhealthy',
|
|
207
|
-
testResult: 'Fields are not being encrypted in database'
|
|
209
|
+
testResult: 'Fields are not being encrypted in database',
|
|
208
210
|
};
|
|
209
211
|
}
|
|
210
|
-
|
|
212
|
+
|
|
211
213
|
if (decryptionWorks && !normalNotEncrypted) {
|
|
212
214
|
return {
|
|
213
215
|
status: 'unhealthy',
|
|
214
|
-
testResult: 'Normal fields are being incorrectly encrypted'
|
|
216
|
+
testResult: 'Normal fields are being incorrectly encrypted',
|
|
215
217
|
};
|
|
216
218
|
}
|
|
217
|
-
|
|
219
|
+
|
|
218
220
|
return {
|
|
219
221
|
status: 'unhealthy',
|
|
220
|
-
testResult: 'Decryption failed or data mismatch'
|
|
222
|
+
testResult: 'Decryption failed or data mismatch',
|
|
221
223
|
};
|
|
222
224
|
};
|
|
223
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
|
+
|
|
224
235
|
const testEncryption = async () => {
|
|
236
|
+
// eslint-disable-next-line no-console
|
|
237
|
+
console.log('Starting encryption test');
|
|
225
238
|
const TestModel = createTestEncryptionModel();
|
|
226
|
-
|
|
227
|
-
|
|
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);
|
|
251
|
+
await withTimeout(testDoc.save(), 5000, 'Save operation timed out');
|
|
252
|
+
// eslint-disable-next-line no-console
|
|
253
|
+
console.log('Test document saved');
|
|
254
|
+
|
|
228
255
|
try {
|
|
229
|
-
const retrievedDoc = await
|
|
256
|
+
const retrievedDoc = await withTimeout(
|
|
257
|
+
TestModel.findById(testDoc._id),
|
|
258
|
+
5000,
|
|
259
|
+
'Find operation timed out'
|
|
260
|
+
);
|
|
261
|
+
// eslint-disable-next-line no-console
|
|
262
|
+
console.log('Test document retrieved');
|
|
230
263
|
const decryptionWorks = verifyDecryption(retrievedDoc, testData);
|
|
231
|
-
const encryptionResults = await
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
264
|
+
const encryptionResults = await withTimeout(
|
|
265
|
+
verifyEncryptionInDatabase(testDoc, testData, TestModel),
|
|
266
|
+
5000,
|
|
267
|
+
'Database verification timed out'
|
|
268
|
+
);
|
|
269
|
+
// eslint-disable-next-line no-console
|
|
270
|
+
console.log('Encryption verification completed');
|
|
271
|
+
|
|
272
|
+
const evaluation = evaluateEncryptionTestResults(
|
|
273
|
+
decryptionWorks,
|
|
274
|
+
encryptionResults
|
|
275
|
+
);
|
|
276
|
+
|
|
235
277
|
return {
|
|
236
278
|
...evaluation,
|
|
237
|
-
encryptionWorks: decryptionWorks
|
|
279
|
+
encryptionWorks: decryptionWorks,
|
|
238
280
|
};
|
|
239
281
|
} finally {
|
|
240
|
-
await
|
|
282
|
+
await withTimeout(
|
|
283
|
+
TestModel.deleteOne({ _id: testDoc._id }),
|
|
284
|
+
5000,
|
|
285
|
+
'Delete operation timed out'
|
|
286
|
+
);
|
|
287
|
+
// eslint-disable-next-line no-console
|
|
288
|
+
console.log('Test document deleted');
|
|
241
289
|
}
|
|
242
290
|
};
|
|
243
291
|
|
|
244
292
|
const checkEncryptionHealth = async () => {
|
|
245
293
|
const config = getEncryptionConfiguration();
|
|
246
|
-
|
|
294
|
+
|
|
247
295
|
if (config.isBypassed || config.mode === 'none') {
|
|
248
|
-
|
|
249
|
-
|
|
296
|
+
// eslint-disable-next-line no-console
|
|
297
|
+
console.log('Encryption check bypassed:', {
|
|
298
|
+
stage: config.stage,
|
|
299
|
+
mode: config.mode,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const testResult = config.isBypassed
|
|
303
|
+
? 'Encryption bypassed for this stage'
|
|
250
304
|
: 'No encryption keys configured';
|
|
251
|
-
|
|
305
|
+
|
|
252
306
|
return {
|
|
253
307
|
status: 'disabled',
|
|
254
308
|
mode: config.mode,
|
|
@@ -258,14 +312,14 @@ const checkEncryptionHealth = async () => {
|
|
|
258
312
|
encryptionWorks: false,
|
|
259
313
|
debug: {
|
|
260
314
|
hasKMS: config.hasKMS,
|
|
261
|
-
hasAES: config.hasAES
|
|
262
|
-
}
|
|
315
|
+
hasAES: config.hasAES,
|
|
316
|
+
},
|
|
263
317
|
};
|
|
264
318
|
}
|
|
265
319
|
|
|
266
320
|
try {
|
|
267
321
|
const testResults = await testEncryption();
|
|
268
|
-
|
|
322
|
+
|
|
269
323
|
return {
|
|
270
324
|
...testResults,
|
|
271
325
|
mode: config.mode,
|
|
@@ -273,8 +327,8 @@ const checkEncryptionHealth = async () => {
|
|
|
273
327
|
stage: config.stage,
|
|
274
328
|
debug: {
|
|
275
329
|
hasKMS: config.hasKMS,
|
|
276
|
-
hasAES: config.hasAES
|
|
277
|
-
}
|
|
330
|
+
hasAES: config.hasAES,
|
|
331
|
+
},
|
|
278
332
|
};
|
|
279
333
|
} catch (error) {
|
|
280
334
|
return {
|
|
@@ -286,8 +340,8 @@ const checkEncryptionHealth = async () => {
|
|
|
286
340
|
encryptionWorks: false,
|
|
287
341
|
debug: {
|
|
288
342
|
hasKMS: config.hasKMS,
|
|
289
|
-
hasAES: config.hasAES
|
|
290
|
-
}
|
|
343
|
+
hasAES: config.hasAES,
|
|
344
|
+
},
|
|
291
345
|
};
|
|
292
346
|
}
|
|
293
347
|
};
|
|
@@ -295,25 +349,28 @@ const checkEncryptionHealth = async () => {
|
|
|
295
349
|
const checkExternalAPIs = async () => {
|
|
296
350
|
const apis = [
|
|
297
351
|
{ name: 'github', url: 'https://api.github.com/status' },
|
|
298
|
-
{ name: 'npm', url: 'https://registry.npmjs.org' }
|
|
352
|
+
{ name: 'npm', url: 'https://registry.npmjs.org' },
|
|
299
353
|
];
|
|
300
354
|
|
|
301
355
|
const results = await Promise.all(
|
|
302
|
-
apis.map(api =>
|
|
303
|
-
checkExternalAPI(api.url).then(result => ({
|
|
356
|
+
apis.map((api) =>
|
|
357
|
+
checkExternalAPI(api.url).then((result) => ({
|
|
358
|
+
name: api.name,
|
|
359
|
+
...result,
|
|
360
|
+
}))
|
|
304
361
|
)
|
|
305
362
|
);
|
|
306
|
-
|
|
363
|
+
|
|
307
364
|
const apiStatuses = {};
|
|
308
365
|
let allReachable = true;
|
|
309
|
-
|
|
366
|
+
|
|
310
367
|
results.forEach(({ name, ...checkResult }) => {
|
|
311
368
|
apiStatuses[name] = checkResult;
|
|
312
369
|
if (!checkResult.reachable) {
|
|
313
370
|
allReachable = false;
|
|
314
371
|
}
|
|
315
372
|
});
|
|
316
|
-
|
|
373
|
+
|
|
317
374
|
return { apiStatuses, allReachable };
|
|
318
375
|
};
|
|
319
376
|
|
|
@@ -321,7 +378,7 @@ const checkIntegrations = () => {
|
|
|
321
378
|
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
322
379
|
? moduleFactory.moduleTypes
|
|
323
380
|
: [];
|
|
324
|
-
|
|
381
|
+
|
|
325
382
|
const integrationTypes = Array.isArray(integrationFactory.integrationTypes)
|
|
326
383
|
? integrationFactory.integrationTypes
|
|
327
384
|
: [];
|
|
@@ -345,7 +402,7 @@ const buildHealthCheckResponse = (startTime) => {
|
|
|
345
402
|
status: 'healthy',
|
|
346
403
|
timestamp: new Date().toISOString(),
|
|
347
404
|
checks: {},
|
|
348
|
-
calculateResponseTime: () => Date.now() - startTime
|
|
405
|
+
calculateResponseTime: () => Date.now() - startTime,
|
|
349
406
|
};
|
|
350
407
|
};
|
|
351
408
|
|
|
@@ -353,13 +410,15 @@ router.get('/health', async (_req, res) => {
|
|
|
353
410
|
const status = {
|
|
354
411
|
status: 'ok',
|
|
355
412
|
timestamp: new Date().toISOString(),
|
|
356
|
-
service: 'frigg-core-api'
|
|
413
|
+
service: 'frigg-core-api',
|
|
357
414
|
};
|
|
358
415
|
|
|
359
416
|
res.status(200).json(status);
|
|
360
417
|
});
|
|
361
418
|
|
|
362
419
|
router.get('/health/detailed', async (_req, res) => {
|
|
420
|
+
// eslint-disable-next-line no-console
|
|
421
|
+
console.log('Starting detailed health check');
|
|
363
422
|
const startTime = Date.now();
|
|
364
423
|
const response = buildHealthCheckResponse(startTime);
|
|
365
424
|
|
|
@@ -369,12 +428,16 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
369
428
|
if (!dbState.isConnected) {
|
|
370
429
|
response.status = 'unhealthy';
|
|
371
430
|
}
|
|
431
|
+
// eslint-disable-next-line no-console
|
|
432
|
+
console.log('Database check completed:', response.checks.database);
|
|
372
433
|
} catch (error) {
|
|
373
434
|
response.checks.database = {
|
|
374
435
|
status: 'unhealthy',
|
|
375
|
-
error: error.message
|
|
436
|
+
error: error.message,
|
|
376
437
|
};
|
|
377
438
|
response.status = 'unhealthy';
|
|
439
|
+
// eslint-disable-next-line no-console
|
|
440
|
+
console.log('Database check error:', error.message);
|
|
378
441
|
}
|
|
379
442
|
|
|
380
443
|
try {
|
|
@@ -382,12 +445,16 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
382
445
|
if (response.checks.encryption.status === 'unhealthy') {
|
|
383
446
|
response.status = 'unhealthy';
|
|
384
447
|
}
|
|
448
|
+
// eslint-disable-next-line no-console
|
|
449
|
+
console.log('Encryption check completed:', response.checks.encryption);
|
|
385
450
|
} catch (error) {
|
|
386
451
|
response.checks.encryption = {
|
|
387
452
|
status: 'unhealthy',
|
|
388
|
-
error: error.message
|
|
453
|
+
error: error.message,
|
|
389
454
|
};
|
|
390
455
|
response.status = 'unhealthy';
|
|
456
|
+
// eslint-disable-next-line no-console
|
|
457
|
+
console.log('Encryption check error:', error.message);
|
|
391
458
|
}
|
|
392
459
|
|
|
393
460
|
const { apiStatuses, allReachable } = await checkExternalAPIs();
|
|
@@ -395,15 +462,24 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
395
462
|
if (!allReachable) {
|
|
396
463
|
response.status = 'unhealthy';
|
|
397
464
|
}
|
|
465
|
+
// eslint-disable-next-line no-console
|
|
466
|
+
console.log('External APIs check completed:', response.checks.externalApis);
|
|
398
467
|
|
|
399
468
|
try {
|
|
400
469
|
response.checks.integrations = checkIntegrations();
|
|
470
|
+
// eslint-disable-next-line no-console
|
|
471
|
+
console.log(
|
|
472
|
+
'Integrations check completed:',
|
|
473
|
+
response.checks.integrations
|
|
474
|
+
);
|
|
401
475
|
} catch (error) {
|
|
402
476
|
response.checks.integrations = {
|
|
403
477
|
status: 'unhealthy',
|
|
404
|
-
error: error.message
|
|
478
|
+
error: error.message,
|
|
405
479
|
};
|
|
406
480
|
response.status = 'unhealthy';
|
|
481
|
+
// eslint-disable-next-line no-console
|
|
482
|
+
console.log('Integrations check error:', error.message);
|
|
407
483
|
}
|
|
408
484
|
|
|
409
485
|
response.responseTime = response.calculateResponseTime();
|
|
@@ -411,19 +487,27 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
411
487
|
|
|
412
488
|
const statusCode = response.status === 'healthy' ? 200 : 503;
|
|
413
489
|
res.status(statusCode).json(response);
|
|
490
|
+
|
|
491
|
+
// eslint-disable-next-line no-console
|
|
492
|
+
console.log(
|
|
493
|
+
'Final health status:',
|
|
494
|
+
response.status,
|
|
495
|
+
'Response time:',
|
|
496
|
+
response.responseTime
|
|
497
|
+
);
|
|
414
498
|
});
|
|
415
499
|
|
|
416
500
|
router.get('/health/live', (_req, res) => {
|
|
417
501
|
res.status(200).json({
|
|
418
502
|
status: 'alive',
|
|
419
|
-
timestamp: new Date().toISOString()
|
|
503
|
+
timestamp: new Date().toISOString(),
|
|
420
504
|
});
|
|
421
505
|
});
|
|
422
506
|
|
|
423
507
|
router.get('/health/ready', async (_req, res) => {
|
|
424
508
|
const dbState = getDatabaseState();
|
|
425
509
|
const isDbReady = dbState.isConnected;
|
|
426
|
-
|
|
510
|
+
|
|
427
511
|
let areModulesReady = false;
|
|
428
512
|
try {
|
|
429
513
|
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
@@ -441,11 +525,11 @@ router.get('/health/ready', async (_req, res) => {
|
|
|
441
525
|
timestamp: new Date().toISOString(),
|
|
442
526
|
checks: {
|
|
443
527
|
database: isDbReady,
|
|
444
|
-
modules: areModulesReady
|
|
445
|
-
}
|
|
528
|
+
modules: areModulesReady,
|
|
529
|
+
},
|
|
446
530
|
});
|
|
447
531
|
});
|
|
448
532
|
|
|
449
533
|
const handler = createAppHandler('HTTP Event: Health', router);
|
|
450
534
|
|
|
451
|
-
module.exports = { handler, router };
|
|
535
|
+
module.exports = { handler, router };
|
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-next.
|
|
4
|
+
"version": "2.0.0-next.39",
|
|
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-next.
|
|
26
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
27
|
-
"@friggframework/test": "2.0.0-next.
|
|
25
|
+
"@friggframework/eslint-config": "2.0.0-next.39",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0-next.39",
|
|
27
|
+
"@friggframework/test": "2.0.0-next.39",
|
|
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": "
|
|
59
|
+
"gitHead": "38a7b885828dca923a6ea0d2e297d0048ba06822"
|
|
60
60
|
}
|