@friggframework/core 2.0.0--canary.425.2d58c19.0 → 2.0.0--canary.427.68e753a.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 +131 -5
- package/handlers/routers/health.js +229 -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,188 @@ 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
|
+
|
|
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
|
+
}
|
|
298
|
+
|
|
228
299
|
try {
|
|
229
|
-
const retrievedDoc = await
|
|
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');
|
|
230
307
|
const decryptionWorks = verifyDecryption(retrievedDoc, testData);
|
|
231
|
-
const encryptionResults = await
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
308
|
+
const encryptionResults = await withTimeout(
|
|
309
|
+
verifyEncryptionInDatabase(testDoc, testData, TestModel),
|
|
310
|
+
5000,
|
|
311
|
+
'Database verification timed out'
|
|
312
|
+
);
|
|
313
|
+
// eslint-disable-next-line no-console
|
|
314
|
+
console.log('Encryption verification completed');
|
|
315
|
+
|
|
316
|
+
const evaluation = evaluateEncryptionTestResults(
|
|
317
|
+
decryptionWorks,
|
|
318
|
+
encryptionResults
|
|
319
|
+
);
|
|
320
|
+
|
|
235
321
|
return {
|
|
236
322
|
...evaluation,
|
|
237
|
-
encryptionWorks: decryptionWorks
|
|
323
|
+
encryptionWorks: decryptionWorks,
|
|
238
324
|
};
|
|
239
325
|
} finally {
|
|
240
|
-
await
|
|
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');
|
|
241
333
|
}
|
|
242
334
|
};
|
|
243
335
|
|
|
244
336
|
const checkEncryptionHealth = async () => {
|
|
245
337
|
const config = getEncryptionConfiguration();
|
|
246
|
-
|
|
338
|
+
|
|
247
339
|
if (config.isBypassed || config.mode === 'none') {
|
|
248
|
-
|
|
249
|
-
|
|
340
|
+
// eslint-disable-next-line no-console
|
|
341
|
+
console.log('Encryption check bypassed:', {
|
|
342
|
+
stage: config.stage,
|
|
343
|
+
mode: config.mode,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const testResult = config.isBypassed
|
|
347
|
+
? 'Encryption bypassed for this stage'
|
|
250
348
|
: 'No encryption keys configured';
|
|
251
|
-
|
|
349
|
+
|
|
252
350
|
return {
|
|
253
351
|
status: 'disabled',
|
|
254
352
|
mode: config.mode,
|
|
@@ -258,14 +356,14 @@ const checkEncryptionHealth = async () => {
|
|
|
258
356
|
encryptionWorks: false,
|
|
259
357
|
debug: {
|
|
260
358
|
hasKMS: config.hasKMS,
|
|
261
|
-
hasAES: config.hasAES
|
|
262
|
-
}
|
|
359
|
+
hasAES: config.hasAES,
|
|
360
|
+
},
|
|
263
361
|
};
|
|
264
362
|
}
|
|
265
363
|
|
|
266
364
|
try {
|
|
267
365
|
const testResults = await testEncryption();
|
|
268
|
-
|
|
366
|
+
|
|
269
367
|
return {
|
|
270
368
|
...testResults,
|
|
271
369
|
mode: config.mode,
|
|
@@ -273,8 +371,8 @@ const checkEncryptionHealth = async () => {
|
|
|
273
371
|
stage: config.stage,
|
|
274
372
|
debug: {
|
|
275
373
|
hasKMS: config.hasKMS,
|
|
276
|
-
hasAES: config.hasAES
|
|
277
|
-
}
|
|
374
|
+
hasAES: config.hasAES,
|
|
375
|
+
},
|
|
278
376
|
};
|
|
279
377
|
} catch (error) {
|
|
280
378
|
return {
|
|
@@ -286,8 +384,8 @@ const checkEncryptionHealth = async () => {
|
|
|
286
384
|
encryptionWorks: false,
|
|
287
385
|
debug: {
|
|
288
386
|
hasKMS: config.hasKMS,
|
|
289
|
-
hasAES: config.hasAES
|
|
290
|
-
}
|
|
387
|
+
hasAES: config.hasAES,
|
|
388
|
+
},
|
|
291
389
|
};
|
|
292
390
|
}
|
|
293
391
|
};
|
|
@@ -295,25 +393,28 @@ const checkEncryptionHealth = async () => {
|
|
|
295
393
|
const checkExternalAPIs = async () => {
|
|
296
394
|
const apis = [
|
|
297
395
|
{ name: 'github', url: 'https://api.github.com/status' },
|
|
298
|
-
{ name: 'npm', url: 'https://registry.npmjs.org' }
|
|
396
|
+
{ name: 'npm', url: 'https://registry.npmjs.org' },
|
|
299
397
|
];
|
|
300
398
|
|
|
301
399
|
const results = await Promise.all(
|
|
302
|
-
apis.map(api =>
|
|
303
|
-
checkExternalAPI(api.url).then(result => ({
|
|
400
|
+
apis.map((api) =>
|
|
401
|
+
checkExternalAPI(api.url).then((result) => ({
|
|
402
|
+
name: api.name,
|
|
403
|
+
...result,
|
|
404
|
+
}))
|
|
304
405
|
)
|
|
305
406
|
);
|
|
306
|
-
|
|
407
|
+
|
|
307
408
|
const apiStatuses = {};
|
|
308
409
|
let allReachable = true;
|
|
309
|
-
|
|
410
|
+
|
|
310
411
|
results.forEach(({ name, ...checkResult }) => {
|
|
311
412
|
apiStatuses[name] = checkResult;
|
|
312
413
|
if (!checkResult.reachable) {
|
|
313
414
|
allReachable = false;
|
|
314
415
|
}
|
|
315
416
|
});
|
|
316
|
-
|
|
417
|
+
|
|
317
418
|
return { apiStatuses, allReachable };
|
|
318
419
|
};
|
|
319
420
|
|
|
@@ -321,7 +422,7 @@ const checkIntegrations = () => {
|
|
|
321
422
|
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
322
423
|
? moduleFactory.moduleTypes
|
|
323
424
|
: [];
|
|
324
|
-
|
|
425
|
+
|
|
325
426
|
const integrationTypes = Array.isArray(integrationFactory.integrationTypes)
|
|
326
427
|
? integrationFactory.integrationTypes
|
|
327
428
|
: [];
|
|
@@ -345,7 +446,7 @@ const buildHealthCheckResponse = (startTime) => {
|
|
|
345
446
|
status: 'healthy',
|
|
346
447
|
timestamp: new Date().toISOString(),
|
|
347
448
|
checks: {},
|
|
348
|
-
calculateResponseTime: () => Date.now() - startTime
|
|
449
|
+
calculateResponseTime: () => Date.now() - startTime,
|
|
349
450
|
};
|
|
350
451
|
};
|
|
351
452
|
|
|
@@ -353,13 +454,15 @@ router.get('/health', async (_req, res) => {
|
|
|
353
454
|
const status = {
|
|
354
455
|
status: 'ok',
|
|
355
456
|
timestamp: new Date().toISOString(),
|
|
356
|
-
service: 'frigg-core-api'
|
|
457
|
+
service: 'frigg-core-api',
|
|
357
458
|
};
|
|
358
459
|
|
|
359
460
|
res.status(200).json(status);
|
|
360
461
|
});
|
|
361
462
|
|
|
362
463
|
router.get('/health/detailed', async (_req, res) => {
|
|
464
|
+
// eslint-disable-next-line no-console
|
|
465
|
+
console.log('Starting detailed health check');
|
|
363
466
|
const startTime = Date.now();
|
|
364
467
|
const response = buildHealthCheckResponse(startTime);
|
|
365
468
|
|
|
@@ -369,12 +472,16 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
369
472
|
if (!dbState.isConnected) {
|
|
370
473
|
response.status = 'unhealthy';
|
|
371
474
|
}
|
|
475
|
+
// eslint-disable-next-line no-console
|
|
476
|
+
console.log('Database check completed:', response.checks.database);
|
|
372
477
|
} catch (error) {
|
|
373
478
|
response.checks.database = {
|
|
374
479
|
status: 'unhealthy',
|
|
375
|
-
error: error.message
|
|
480
|
+
error: error.message,
|
|
376
481
|
};
|
|
377
482
|
response.status = 'unhealthy';
|
|
483
|
+
// eslint-disable-next-line no-console
|
|
484
|
+
console.log('Database check error:', error.message);
|
|
378
485
|
}
|
|
379
486
|
|
|
380
487
|
try {
|
|
@@ -382,12 +489,16 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
382
489
|
if (response.checks.encryption.status === 'unhealthy') {
|
|
383
490
|
response.status = 'unhealthy';
|
|
384
491
|
}
|
|
492
|
+
// eslint-disable-next-line no-console
|
|
493
|
+
console.log('Encryption check completed:', response.checks.encryption);
|
|
385
494
|
} catch (error) {
|
|
386
495
|
response.checks.encryption = {
|
|
387
496
|
status: 'unhealthy',
|
|
388
|
-
error: error.message
|
|
497
|
+
error: error.message,
|
|
389
498
|
};
|
|
390
499
|
response.status = 'unhealthy';
|
|
500
|
+
// eslint-disable-next-line no-console
|
|
501
|
+
console.log('Encryption check error:', error.message);
|
|
391
502
|
}
|
|
392
503
|
|
|
393
504
|
const { apiStatuses, allReachable } = await checkExternalAPIs();
|
|
@@ -395,15 +506,24 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
395
506
|
if (!allReachable) {
|
|
396
507
|
response.status = 'unhealthy';
|
|
397
508
|
}
|
|
509
|
+
// eslint-disable-next-line no-console
|
|
510
|
+
console.log('External APIs check completed:', response.checks.externalApis);
|
|
398
511
|
|
|
399
512
|
try {
|
|
400
513
|
response.checks.integrations = checkIntegrations();
|
|
514
|
+
// eslint-disable-next-line no-console
|
|
515
|
+
console.log(
|
|
516
|
+
'Integrations check completed:',
|
|
517
|
+
response.checks.integrations
|
|
518
|
+
);
|
|
401
519
|
} catch (error) {
|
|
402
520
|
response.checks.integrations = {
|
|
403
521
|
status: 'unhealthy',
|
|
404
|
-
error: error.message
|
|
522
|
+
error: error.message,
|
|
405
523
|
};
|
|
406
524
|
response.status = 'unhealthy';
|
|
525
|
+
// eslint-disable-next-line no-console
|
|
526
|
+
console.log('Integrations check error:', error.message);
|
|
407
527
|
}
|
|
408
528
|
|
|
409
529
|
response.responseTime = response.calculateResponseTime();
|
|
@@ -411,19 +531,27 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
411
531
|
|
|
412
532
|
const statusCode = response.status === 'healthy' ? 200 : 503;
|
|
413
533
|
res.status(statusCode).json(response);
|
|
534
|
+
|
|
535
|
+
// eslint-disable-next-line no-console
|
|
536
|
+
console.log(
|
|
537
|
+
'Final health status:',
|
|
538
|
+
response.status,
|
|
539
|
+
'Response time:',
|
|
540
|
+
response.responseTime
|
|
541
|
+
);
|
|
414
542
|
});
|
|
415
543
|
|
|
416
544
|
router.get('/health/live', (_req, res) => {
|
|
417
545
|
res.status(200).json({
|
|
418
546
|
status: 'alive',
|
|
419
|
-
timestamp: new Date().toISOString()
|
|
547
|
+
timestamp: new Date().toISOString(),
|
|
420
548
|
});
|
|
421
549
|
});
|
|
422
550
|
|
|
423
551
|
router.get('/health/ready', async (_req, res) => {
|
|
424
552
|
const dbState = getDatabaseState();
|
|
425
553
|
const isDbReady = dbState.isConnected;
|
|
426
|
-
|
|
554
|
+
|
|
427
555
|
let areModulesReady = false;
|
|
428
556
|
try {
|
|
429
557
|
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
@@ -441,11 +569,11 @@ router.get('/health/ready', async (_req, res) => {
|
|
|
441
569
|
timestamp: new Date().toISOString(),
|
|
442
570
|
checks: {
|
|
443
571
|
database: isDbReady,
|
|
444
|
-
modules: areModulesReady
|
|
445
|
-
}
|
|
572
|
+
modules: areModulesReady,
|
|
573
|
+
},
|
|
446
574
|
});
|
|
447
575
|
});
|
|
448
576
|
|
|
449
577
|
const handler = createAppHandler('HTTP Event: Health', router);
|
|
450
578
|
|
|
451
|
-
module.exports = { handler, router };
|
|
579
|
+
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--canary.
|
|
4
|
+
"version": "2.0.0--canary.427.68e753a.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.
|
|
26
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
27
|
-
"@friggframework/test": "2.0.0--canary.
|
|
25
|
+
"@friggframework/eslint-config": "2.0.0--canary.427.68e753a.0",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0--canary.427.68e753a.0",
|
|
27
|
+
"@friggframework/test": "2.0.0--canary.427.68e753a.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": "
|
|
59
|
+
"gitHead": "68e753a98bf315deb2004e4f5c6ab58495e04d2b"
|
|
60
60
|
}
|