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