@friggframework/core 2.0.0-next.26 → 2.0.0-next.27
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/encrypt/encrypt.js +5 -33
- package/handlers/routers/health.js +320 -74
- package/handlers/routers/health.test.js +2 -8
- package/package.json +5 -5
package/encrypt/encrypt.js
CHANGED
|
@@ -16,28 +16,18 @@ const findOneEvents = [
|
|
|
16
16
|
|
|
17
17
|
const shouldBypassEncryption = (STAGE) => {
|
|
18
18
|
const defaultBypassStages = ['dev', 'test', 'local'];
|
|
19
|
-
|
|
20
|
-
// If the env is set to anything or an empty string, use the env. Otherwise, use the default array
|
|
21
|
-
const useEnv = !String(bypassStageEnv) || !!bypassStageEnv;
|
|
22
|
-
const bypassStages = useEnv
|
|
23
|
-
? bypassStageEnv.split(',').map((stage) => stage.trim())
|
|
24
|
-
: defaultBypassStages;
|
|
25
|
-
return bypassStages.includes(STAGE);
|
|
19
|
+
return defaultBypassStages.includes(STAGE);
|
|
26
20
|
};
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
function Encrypt(schema, options) {
|
|
22
|
+
function Encrypt(schema) {
|
|
30
23
|
const { STAGE, KMS_KEY_ARN, AES_KEY_ID } = process.env;
|
|
31
24
|
|
|
32
25
|
if (shouldBypassEncryption(STAGE)) {
|
|
33
26
|
return;
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'Local and AWS encryption keys are both set in the environment.'
|
|
39
|
-
);
|
|
40
|
-
}
|
|
29
|
+
const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
|
|
30
|
+
const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '' && !hasAES;
|
|
41
31
|
|
|
42
32
|
const fields = Object.values(schema.paths)
|
|
43
33
|
.map(({ path, options }) => (options.lhEncrypt === true ? path : ''))
|
|
@@ -48,25 +38,17 @@ function Encrypt(schema, options) {
|
|
|
48
38
|
}
|
|
49
39
|
|
|
50
40
|
const cryptor = new Cryptor({
|
|
51
|
-
|
|
52
|
-
shouldUseAws: !!KMS_KEY_ARN,
|
|
53
|
-
// Find all the fields in the schema with lhEncrypt === true
|
|
41
|
+
shouldUseAws: hasKMS,
|
|
54
42
|
fields: fields,
|
|
55
43
|
});
|
|
56
44
|
|
|
57
|
-
// ---------------------------------------------
|
|
58
|
-
// ### Encrypt fields before save/update/insert.
|
|
59
|
-
// ---------------------------------------------
|
|
60
|
-
|
|
61
45
|
schema.pre('save', async function encryptionPreSave() {
|
|
62
|
-
// `this` will be a doc
|
|
63
46
|
await cryptor.encryptFieldsInDocuments([this]);
|
|
64
47
|
});
|
|
65
48
|
|
|
66
49
|
schema.pre(
|
|
67
50
|
'insertMany',
|
|
68
51
|
async function encryptionPreInsertMany(_, docs, options) {
|
|
69
|
-
// `this` will be the model
|
|
70
52
|
if (options?.rawResult) {
|
|
71
53
|
throw new Error(
|
|
72
54
|
'Raw result not supported for insertMany with Encrypt plugin'
|
|
@@ -78,17 +60,14 @@ function Encrypt(schema, options) {
|
|
|
78
60
|
);
|
|
79
61
|
|
|
80
62
|
schema.pre(updateOneEvents, async function encryptionPreUpdateOne() {
|
|
81
|
-
// `this` will be a query
|
|
82
63
|
await cryptor.encryptFieldsInQuery(this);
|
|
83
64
|
});
|
|
84
65
|
|
|
85
66
|
schema.pre('updateMany', async function encryptionPreUpdateMany() {
|
|
86
|
-
// `this` will be a query
|
|
87
67
|
cryptor.expectNotToUpdateManyEncrypted(this.getUpdate());
|
|
88
68
|
});
|
|
89
69
|
|
|
90
70
|
schema.pre('update', async function encryptionPreUpdate() {
|
|
91
|
-
// `this` will be a query
|
|
92
71
|
const { multiple } = this.getOptions();
|
|
93
72
|
|
|
94
73
|
if (multiple) {
|
|
@@ -99,16 +78,11 @@ function Encrypt(schema, options) {
|
|
|
99
78
|
await cryptor.encryptFieldsInQuery(this);
|
|
100
79
|
});
|
|
101
80
|
|
|
102
|
-
// --------------------------------------------
|
|
103
|
-
// ### Decrypt documents after they are loaded.
|
|
104
|
-
// --------------------------------------------
|
|
105
81
|
schema.post('save', async function encryptionPreSave() {
|
|
106
|
-
// `this` will be a doc
|
|
107
82
|
await cryptor.decryptFieldsInDocuments([this]);
|
|
108
83
|
});
|
|
109
84
|
|
|
110
85
|
schema.post(findOneEvents, async function encryptionPostFindOne(doc) {
|
|
111
|
-
// `this` will be a query
|
|
112
86
|
const { rawResult } = this.getOptions();
|
|
113
87
|
|
|
114
88
|
if (rawResult) {
|
|
@@ -119,12 +93,10 @@ function Encrypt(schema, options) {
|
|
|
119
93
|
});
|
|
120
94
|
|
|
121
95
|
schema.post('find', async function encryptionPostFind(docs) {
|
|
122
|
-
// `this` will be a query
|
|
123
96
|
await cryptor.decryptFieldsInDocuments(docs);
|
|
124
97
|
});
|
|
125
98
|
|
|
126
99
|
schema.post('insertMany', async function encryptionPostInsertMany(docs) {
|
|
127
|
-
// `this` will be the model
|
|
128
100
|
await cryptor.decryptFieldsInDocuments(docs);
|
|
129
101
|
});
|
|
130
102
|
}
|
|
@@ -71,103 +71,346 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
71
71
|
});
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
const getDatabaseState = () => {
|
|
75
|
+
const stateMap = {
|
|
76
|
+
0: 'disconnected',
|
|
77
|
+
1: 'connected',
|
|
78
|
+
2: 'connecting',
|
|
79
|
+
3: 'disconnecting'
|
|
79
80
|
};
|
|
81
|
+
const readyState = mongoose.connection.readyState;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
readyState,
|
|
85
|
+
stateName: stateMap[readyState],
|
|
86
|
+
isConnected: readyState === 1
|
|
87
|
+
};
|
|
88
|
+
};
|
|
80
89
|
|
|
81
|
-
|
|
82
|
-
});
|
|
90
|
+
const checkDatabaseHealth = async () => {
|
|
91
|
+
const { stateName, isConnected } = getDatabaseState();
|
|
92
|
+
const result = {
|
|
93
|
+
status: isConnected ? 'healthy' : 'unhealthy',
|
|
94
|
+
state: stateName
|
|
95
|
+
};
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
if (isConnected) {
|
|
98
|
+
const pingStart = Date.now();
|
|
99
|
+
await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
|
|
100
|
+
result.responseTime = Date.now() - pingStart;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getEncryptionConfiguration = () => {
|
|
107
|
+
const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } = process.env;
|
|
108
|
+
|
|
109
|
+
const defaultBypassStages = ['dev', 'test', 'local'];
|
|
110
|
+
const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
|
|
111
|
+
const bypassStages = useEnv
|
|
112
|
+
? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
|
|
113
|
+
: defaultBypassStages;
|
|
114
|
+
|
|
115
|
+
const isBypassed = bypassStages.includes(STAGE);
|
|
116
|
+
const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
|
|
117
|
+
const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '' && !hasAES;
|
|
118
|
+
const mode = hasAES ? 'aes' : hasKMS ? 'kms' : 'none';
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
stage: STAGE || null,
|
|
122
|
+
isBypassed,
|
|
123
|
+
hasAES,
|
|
124
|
+
hasKMS,
|
|
125
|
+
mode,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const createTestEncryptionModel = () => {
|
|
130
|
+
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
|
+
|
|
140
|
+
testSchema.plugin(Encrypt);
|
|
141
|
+
|
|
142
|
+
return mongoose.models.TestEncryption ||
|
|
143
|
+
mongoose.model('TestEncryption', testSchema);
|
|
144
|
+
};
|
|
145
|
+
|
|
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
|
+
}
|
|
91
153
|
};
|
|
92
154
|
|
|
155
|
+
const testDoc = new TestModel(testData);
|
|
156
|
+
await testDoc.save();
|
|
157
|
+
|
|
158
|
+
return { testDoc, testData };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const verifyDecryption = (retrievedDoc, originalData) => {
|
|
162
|
+
return retrievedDoc &&
|
|
163
|
+
retrievedDoc.testSecret === originalData.testSecret &&
|
|
164
|
+
retrievedDoc.normalField === originalData.normalField &&
|
|
165
|
+
retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
|
|
169
|
+
const collectionName = TestModel.collection.name;
|
|
170
|
+
const rawDoc = await mongoose.connection.db
|
|
171
|
+
.collection(collectionName)
|
|
172
|
+
.findOne({ _id: testDoc._id });
|
|
173
|
+
|
|
174
|
+
const secretIsEncrypted = rawDoc &&
|
|
175
|
+
typeof rawDoc.testSecret === 'string' &&
|
|
176
|
+
rawDoc.testSecret.includes(':') &&
|
|
177
|
+
rawDoc.testSecret !== originalData.testSecret;
|
|
178
|
+
|
|
179
|
+
const nestedIsEncrypted = rawDoc?.nestedSecret?.value &&
|
|
180
|
+
typeof rawDoc.nestedSecret.value === 'string' &&
|
|
181
|
+
rawDoc.nestedSecret.value.includes(':') &&
|
|
182
|
+
rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
|
|
183
|
+
|
|
184
|
+
const normalNotEncrypted = rawDoc &&
|
|
185
|
+
rawDoc.normalField === originalData.normalField;
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
secretIsEncrypted,
|
|
189
|
+
nestedIsEncrypted,
|
|
190
|
+
normalNotEncrypted
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const evaluateEncryptionTestResults = (decryptionWorks, encryptionResults) => {
|
|
195
|
+
const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } = encryptionResults;
|
|
196
|
+
|
|
197
|
+
if (decryptionWorks && secretIsEncrypted && nestedIsEncrypted && normalNotEncrypted) {
|
|
198
|
+
return {
|
|
199
|
+
status: 'enabled',
|
|
200
|
+
testResult: 'Encryption and decryption verified successfully'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (decryptionWorks && (!secretIsEncrypted || !nestedIsEncrypted)) {
|
|
205
|
+
return {
|
|
206
|
+
status: 'unhealthy',
|
|
207
|
+
testResult: 'Fields are not being encrypted in database'
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (decryptionWorks && !normalNotEncrypted) {
|
|
212
|
+
return {
|
|
213
|
+
status: 'unhealthy',
|
|
214
|
+
testResult: 'Normal fields are being incorrectly encrypted'
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
status: 'unhealthy',
|
|
220
|
+
testResult: 'Decryption failed or data mismatch'
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const testEncryption = async () => {
|
|
225
|
+
const TestModel = createTestEncryptionModel();
|
|
226
|
+
const { testDoc, testData } = await createTestDocument(TestModel);
|
|
227
|
+
|
|
93
228
|
try {
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
229
|
+
const retrievedDoc = await TestModel.findById(testDoc._id);
|
|
230
|
+
const decryptionWorks = verifyDecryption(retrievedDoc, testData);
|
|
231
|
+
const encryptionResults = await verifyEncryptionInDatabase(testDoc, testData, TestModel);
|
|
232
|
+
|
|
233
|
+
const evaluation = evaluateEncryptionTestResults(decryptionWorks, encryptionResults);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
...evaluation,
|
|
237
|
+
encryptionWorks: decryptionWorks
|
|
100
238
|
};
|
|
239
|
+
} finally {
|
|
240
|
+
await TestModel.deleteOne({ _id: testDoc._id });
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const checkEncryptionHealth = async () => {
|
|
245
|
+
const config = getEncryptionConfiguration();
|
|
246
|
+
|
|
247
|
+
if (config.isBypassed || config.mode === 'none') {
|
|
248
|
+
const testResult = config.isBypassed
|
|
249
|
+
? 'Encryption bypassed for this stage'
|
|
250
|
+
: 'No encryption keys configured';
|
|
101
251
|
|
|
102
|
-
|
|
103
|
-
status:
|
|
104
|
-
|
|
252
|
+
return {
|
|
253
|
+
status: 'disabled',
|
|
254
|
+
mode: config.mode,
|
|
255
|
+
bypassed: config.isBypassed,
|
|
256
|
+
stage: config.stage,
|
|
257
|
+
testResult,
|
|
258
|
+
encryptionWorks: false,
|
|
259
|
+
debug: {
|
|
260
|
+
hasKMS: config.hasKMS,
|
|
261
|
+
hasAES: config.hasAES
|
|
262
|
+
}
|
|
105
263
|
};
|
|
264
|
+
}
|
|
106
265
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
266
|
+
try {
|
|
267
|
+
const testResults = await testEncryption();
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
...testResults,
|
|
271
|
+
mode: config.mode,
|
|
272
|
+
bypassed: config.isBypassed,
|
|
273
|
+
stage: config.stage,
|
|
274
|
+
debug: {
|
|
275
|
+
hasKMS: config.hasKMS,
|
|
276
|
+
hasAES: config.hasAES
|
|
277
|
+
}
|
|
278
|
+
};
|
|
114
279
|
} catch (error) {
|
|
115
|
-
|
|
280
|
+
return {
|
|
116
281
|
status: 'unhealthy',
|
|
117
|
-
|
|
282
|
+
mode: config.mode,
|
|
283
|
+
bypassed: config.isBypassed,
|
|
284
|
+
stage: config.stage,
|
|
285
|
+
testResult: `Encryption test failed: ${error.message}`,
|
|
286
|
+
encryptionWorks: false,
|
|
287
|
+
debug: {
|
|
288
|
+
hasKMS: config.hasKMS,
|
|
289
|
+
hasAES: config.hasAES
|
|
290
|
+
}
|
|
118
291
|
};
|
|
119
|
-
checks.status = 'unhealthy';
|
|
120
292
|
}
|
|
293
|
+
};
|
|
121
294
|
|
|
122
|
-
|
|
295
|
+
const checkExternalAPIs = async () => {
|
|
296
|
+
const apis = [
|
|
123
297
|
{ name: 'github', url: 'https://api.github.com/status' },
|
|
124
298
|
{ name: 'npm', url: 'https://registry.npmjs.org' }
|
|
125
299
|
];
|
|
126
300
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const apiChecks = await Promise.all(
|
|
130
|
-
externalAPIs.map(api =>
|
|
301
|
+
const results = await Promise.all(
|
|
302
|
+
apis.map(api =>
|
|
131
303
|
checkExternalAPI(api.url).then(result => ({ name: api.name, ...result }))
|
|
132
304
|
)
|
|
133
305
|
);
|
|
134
306
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
307
|
+
const apiStatuses = {};
|
|
308
|
+
let allReachable = true;
|
|
309
|
+
|
|
310
|
+
results.forEach(({ name, ...checkResult }) => {
|
|
311
|
+
apiStatuses[name] = checkResult;
|
|
138
312
|
if (!checkResult.reachable) {
|
|
139
|
-
|
|
313
|
+
allReachable = false;
|
|
140
314
|
}
|
|
141
315
|
});
|
|
316
|
+
|
|
317
|
+
return { apiStatuses, allReachable };
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const checkIntegrations = () => {
|
|
321
|
+
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
322
|
+
? moduleFactory.moduleTypes
|
|
323
|
+
: [];
|
|
324
|
+
|
|
325
|
+
const integrationTypes = Array.isArray(integrationFactory.integrationTypes)
|
|
326
|
+
? integrationFactory.integrationTypes
|
|
327
|
+
: [];
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
status: 'healthy',
|
|
331
|
+
modules: {
|
|
332
|
+
count: moduleTypes.length,
|
|
333
|
+
available: moduleTypes,
|
|
334
|
+
},
|
|
335
|
+
integrations: {
|
|
336
|
+
count: integrationTypes.length,
|
|
337
|
+
available: integrationTypes,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const buildHealthCheckResponse = (startTime) => {
|
|
343
|
+
return {
|
|
344
|
+
service: 'frigg-core-api',
|
|
345
|
+
status: 'healthy',
|
|
346
|
+
timestamp: new Date().toISOString(),
|
|
347
|
+
checks: {},
|
|
348
|
+
calculateResponseTime: () => Date.now() - startTime
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
router.get('/health', async (_req, res) => {
|
|
353
|
+
const status = {
|
|
354
|
+
status: 'ok',
|
|
355
|
+
timestamp: new Date().toISOString(),
|
|
356
|
+
service: 'frigg-core-api'
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
res.status(200).json(status);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
router.get('/health/detailed', async (_req, res) => {
|
|
363
|
+
const startTime = Date.now();
|
|
364
|
+
const response = buildHealthCheckResponse(startTime);
|
|
142
365
|
|
|
143
366
|
try {
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
integrations: {
|
|
154
|
-
count: Object.keys(availableIntegrations).length,
|
|
155
|
-
available: Object.keys(availableIntegrations)
|
|
156
|
-
}
|
|
367
|
+
response.checks.database = await checkDatabaseHealth();
|
|
368
|
+
const dbState = getDatabaseState();
|
|
369
|
+
if (!dbState.isConnected) {
|
|
370
|
+
response.status = 'unhealthy';
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
response.checks.database = {
|
|
374
|
+
status: 'unhealthy',
|
|
375
|
+
error: error.message
|
|
157
376
|
};
|
|
377
|
+
response.status = 'unhealthy';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
response.checks.encryption = await checkEncryptionHealth();
|
|
382
|
+
if (response.checks.encryption.status === 'unhealthy') {
|
|
383
|
+
response.status = 'unhealthy';
|
|
384
|
+
}
|
|
158
385
|
} catch (error) {
|
|
159
|
-
|
|
386
|
+
response.checks.encryption = {
|
|
160
387
|
status: 'unhealthy',
|
|
161
388
|
error: error.message
|
|
162
389
|
};
|
|
163
|
-
|
|
390
|
+
response.status = 'unhealthy';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const { apiStatuses, allReachable } = await checkExternalAPIs();
|
|
394
|
+
response.checks.externalApis = apiStatuses;
|
|
395
|
+
if (!allReachable) {
|
|
396
|
+
response.status = 'unhealthy';
|
|
164
397
|
}
|
|
165
398
|
|
|
166
|
-
|
|
399
|
+
try {
|
|
400
|
+
response.checks.integrations = checkIntegrations();
|
|
401
|
+
} catch (error) {
|
|
402
|
+
response.checks.integrations = {
|
|
403
|
+
status: 'unhealthy',
|
|
404
|
+
error: error.message
|
|
405
|
+
};
|
|
406
|
+
response.status = 'unhealthy';
|
|
407
|
+
}
|
|
167
408
|
|
|
168
|
-
|
|
409
|
+
response.responseTime = response.calculateResponseTime();
|
|
410
|
+
delete response.calculateResponseTime;
|
|
169
411
|
|
|
170
|
-
|
|
412
|
+
const statusCode = response.status === 'healthy' ? 200 : 503;
|
|
413
|
+
res.status(statusCode).json(response);
|
|
171
414
|
});
|
|
172
415
|
|
|
173
416
|
router.get('/health/live', (_req, res) => {
|
|
@@ -178,26 +421,29 @@ router.get('/health/live', (_req, res) => {
|
|
|
178
421
|
});
|
|
179
422
|
|
|
180
423
|
router.get('/health/ready', async (_req, res) => {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
timestamp: new Date().toISOString(),
|
|
184
|
-
checks: {}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const dbState = mongoose.connection.readyState;
|
|
188
|
-
checks.checks.database = dbState === 1;
|
|
424
|
+
const dbState = getDatabaseState();
|
|
425
|
+
const isDbReady = dbState.isConnected;
|
|
189
426
|
|
|
427
|
+
let areModulesReady = false;
|
|
190
428
|
try {
|
|
191
|
-
const
|
|
192
|
-
|
|
429
|
+
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
430
|
+
? moduleFactory.moduleTypes
|
|
431
|
+
: [];
|
|
432
|
+
areModulesReady = moduleTypes.length > 0;
|
|
193
433
|
} catch (error) {
|
|
194
|
-
|
|
434
|
+
areModulesReady = false;
|
|
195
435
|
}
|
|
196
436
|
|
|
197
|
-
|
|
437
|
+
const isReady = isDbReady && areModulesReady;
|
|
198
438
|
|
|
199
|
-
|
|
200
|
-
|
|
439
|
+
res.status(isReady ? 200 : 503).json({
|
|
440
|
+
ready: isReady,
|
|
441
|
+
timestamp: new Date().toISOString(),
|
|
442
|
+
checks: {
|
|
443
|
+
database: isDbReady,
|
|
444
|
+
modules: areModulesReady
|
|
445
|
+
}
|
|
446
|
+
});
|
|
201
447
|
});
|
|
202
448
|
|
|
203
449
|
const handler = createAppHandler('HTTP Event: Health', router);
|
|
@@ -14,16 +14,10 @@ jest.mock('mongoose', () => ({
|
|
|
14
14
|
|
|
15
15
|
jest.mock('./../backend-utils', () => ({
|
|
16
16
|
moduleFactory: {
|
|
17
|
-
|
|
18
|
-
'test-module': {},
|
|
19
|
-
'another-module': {}
|
|
20
|
-
})
|
|
17
|
+
moduleTypes: ['test-module', 'another-module']
|
|
21
18
|
},
|
|
22
19
|
integrationFactory: {
|
|
23
|
-
|
|
24
|
-
'test-integration': {},
|
|
25
|
-
'another-integration': {}
|
|
26
|
-
})
|
|
20
|
+
integrationTypes: ['test-integration', 'another-integration']
|
|
27
21
|
}
|
|
28
22
|
}));
|
|
29
23
|
|
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.27",
|
|
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.27",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0-next.27",
|
|
27
|
+
"@friggframework/test": "2.0.0-next.27",
|
|
28
28
|
"@types/lodash": "4.17.15",
|
|
29
29
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
30
30
|
"chai": "^4.3.6",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
},
|
|
54
54
|
"homepage": "https://github.com/friggframework/frigg#readme",
|
|
55
55
|
"description": "",
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "82dec739e8d482b55f995eecf088ef05f7931188"
|
|
57
57
|
}
|