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