@friggframework/core 2.0.0--canary.405.b87f8d8.0 → 2.0.0--canary.405.b81908d.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 +313 -113
- 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,147 +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,
|
|
91
126
|
};
|
|
127
|
+
};
|
|
92
128
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
};
|
|
106
145
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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'
|
|
113
152
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
153
|
+
};
|
|
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 {
|
|
116
206
|
status: 'unhealthy',
|
|
117
|
-
|
|
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'
|
|
118
215
|
};
|
|
119
|
-
checks.status = 'unhealthy';
|
|
120
216
|
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
status: 'unhealthy',
|
|
220
|
+
testResult: 'Decryption failed or data mismatch'
|
|
221
|
+
};
|
|
222
|
+
};
|
|
121
223
|
|
|
224
|
+
const testEncryption = async () => {
|
|
225
|
+
const TestModel = createTestEncryptionModel();
|
|
226
|
+
const { testDoc, testData } = await createTestDocument(TestModel);
|
|
227
|
+
|
|
122
228
|
try {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (KMS_KEY_ARN && AES_KEY_ID) {
|
|
138
|
-
status = 'unhealthy';
|
|
139
|
-
} else if (!bypassed && mode !== 'none') {
|
|
140
|
-
status = 'enabled';
|
|
141
|
-
} else {
|
|
142
|
-
status = 'disabled';
|
|
143
|
-
}
|
|
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
|
|
238
|
+
};
|
|
239
|
+
} finally {
|
|
240
|
+
await TestModel.deleteOne({ _id: testDoc._id });
|
|
241
|
+
}
|
|
242
|
+
};
|
|
144
243
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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';
|
|
251
|
+
|
|
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
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
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
|
+
}
|
|
150
278
|
};
|
|
151
|
-
if (status === 'unhealthy') checks.status = 'unhealthy';
|
|
152
279
|
} catch (error) {
|
|
153
|
-
|
|
280
|
+
return {
|
|
154
281
|
status: 'unhealthy',
|
|
155
|
-
|
|
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
|
+
}
|
|
156
291
|
};
|
|
157
|
-
checks.status = 'unhealthy';
|
|
158
292
|
}
|
|
293
|
+
};
|
|
159
294
|
|
|
160
|
-
|
|
295
|
+
const checkExternalAPIs = async () => {
|
|
296
|
+
const apis = [
|
|
161
297
|
{ name: 'github', url: 'https://api.github.com/status' },
|
|
162
298
|
{ name: 'npm', url: 'https://registry.npmjs.org' }
|
|
163
299
|
];
|
|
164
300
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const apiChecks = await Promise.all(
|
|
168
|
-
externalAPIs.map(api =>
|
|
301
|
+
const results = await Promise.all(
|
|
302
|
+
apis.map(api =>
|
|
169
303
|
checkExternalAPI(api.url).then(result => ({ name: api.name, ...result }))
|
|
170
304
|
)
|
|
171
305
|
);
|
|
172
306
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
307
|
+
const apiStatuses = {};
|
|
308
|
+
let allReachable = true;
|
|
309
|
+
|
|
310
|
+
results.forEach(({ name, ...checkResult }) => {
|
|
311
|
+
apiStatuses[name] = checkResult;
|
|
176
312
|
if (!checkResult.reachable) {
|
|
177
|
-
|
|
313
|
+
allReachable = false;
|
|
178
314
|
}
|
|
179
315
|
});
|
|
316
|
+
|
|
317
|
+
return { apiStatuses, allReachable };
|
|
318
|
+
};
|
|
180
319
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
};
|
|
190
358
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
365
|
+
|
|
366
|
+
try {
|
|
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
|
|
201
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
|
+
}
|
|
202
385
|
} catch (error) {
|
|
203
|
-
|
|
386
|
+
response.checks.encryption = {
|
|
204
387
|
status: 'unhealthy',
|
|
205
388
|
error: error.message
|
|
206
389
|
};
|
|
207
|
-
|
|
390
|
+
response.status = 'unhealthy';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const { apiStatuses, allReachable } = await checkExternalAPIs();
|
|
394
|
+
response.checks.externalApis = apiStatuses;
|
|
395
|
+
if (!allReachable) {
|
|
396
|
+
response.status = 'unhealthy';
|
|
208
397
|
}
|
|
209
398
|
|
|
210
|
-
|
|
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
|
+
}
|
|
211
408
|
|
|
212
|
-
|
|
409
|
+
response.responseTime = response.calculateResponseTime();
|
|
410
|
+
delete response.calculateResponseTime;
|
|
213
411
|
|
|
214
|
-
|
|
412
|
+
const statusCode = response.status === 'healthy' ? 200 : 503;
|
|
413
|
+
res.status(statusCode).json(response);
|
|
215
414
|
});
|
|
216
415
|
|
|
217
416
|
router.get('/health/live', (_req, res) => {
|
|
@@ -222,28 +421,29 @@ router.get('/health/live', (_req, res) => {
|
|
|
222
421
|
});
|
|
223
422
|
|
|
224
423
|
router.get('/health/ready', async (_req, res) => {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
timestamp: new Date().toISOString(),
|
|
228
|
-
checks: {}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const dbState = mongoose.connection.readyState;
|
|
232
|
-
checks.checks.database = dbState === 1;
|
|
424
|
+
const dbState = getDatabaseState();
|
|
425
|
+
const isDbReady = dbState.isConnected;
|
|
233
426
|
|
|
427
|
+
let areModulesReady = false;
|
|
234
428
|
try {
|
|
235
429
|
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
236
430
|
? moduleFactory.moduleTypes
|
|
237
431
|
: [];
|
|
238
|
-
|
|
432
|
+
areModulesReady = moduleTypes.length > 0;
|
|
239
433
|
} catch (error) {
|
|
240
|
-
|
|
434
|
+
areModulesReady = false;
|
|
241
435
|
}
|
|
242
436
|
|
|
243
|
-
|
|
437
|
+
const isReady = isDbReady && areModulesReady;
|
|
244
438
|
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
});
|
|
247
447
|
});
|
|
248
448
|
|
|
249
449
|
const handler = createAppHandler('HTTP Event: Health', 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.405.
|
|
4
|
+
"version": "2.0.0--canary.405.b81908d.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.405.
|
|
26
|
-
"@friggframework/prettier-config": "2.0.0--canary.405.
|
|
27
|
-
"@friggframework/test": "2.0.0--canary.405.
|
|
25
|
+
"@friggframework/eslint-config": "2.0.0--canary.405.b81908d.0",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0--canary.405.b81908d.0",
|
|
27
|
+
"@friggframework/test": "2.0.0--canary.405.b81908d.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": "b81908d5f795baf937e4b382dff63dedd40ddceb"
|
|
57
57
|
}
|