@friggframework/core 2.0.0--canary.405.b87f8d8.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 +321 -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,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)'
|
|
91
128
|
};
|
|
129
|
+
};
|
|
92
130
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
};
|
|
106
147
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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'
|
|
113
154
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
155
|
+
};
|
|
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 {
|
|
116
208
|
status: 'unhealthy',
|
|
117
|
-
|
|
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'
|
|
118
217
|
};
|
|
119
|
-
checks.status = 'unhealthy';
|
|
120
218
|
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
status: 'unhealthy',
|
|
222
|
+
testResult: 'Decryption failed or data mismatch'
|
|
223
|
+
};
|
|
224
|
+
};
|
|
121
225
|
|
|
226
|
+
const testEncryption = async () => {
|
|
227
|
+
const TestModel = createTestEncryptionModel();
|
|
228
|
+
const { testDoc, testData } = await createTestDocument(TestModel);
|
|
229
|
+
|
|
122
230
|
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
|
-
}
|
|
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
|
|
240
|
+
};
|
|
241
|
+
} finally {
|
|
242
|
+
await TestModel.deleteOne({ _id: testDoc._id });
|
|
243
|
+
}
|
|
244
|
+
};
|
|
144
245
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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';
|
|
253
|
+
|
|
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
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
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
|
+
}
|
|
150
284
|
};
|
|
151
|
-
if (status === 'unhealthy') checks.status = 'unhealthy';
|
|
152
285
|
} catch (error) {
|
|
153
|
-
|
|
286
|
+
return {
|
|
154
287
|
status: 'unhealthy',
|
|
155
|
-
|
|
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
|
+
}
|
|
156
299
|
};
|
|
157
|
-
checks.status = 'unhealthy';
|
|
158
300
|
}
|
|
301
|
+
};
|
|
159
302
|
|
|
160
|
-
|
|
303
|
+
const checkExternalAPIs = async () => {
|
|
304
|
+
const apis = [
|
|
161
305
|
{ name: 'github', url: 'https://api.github.com/status' },
|
|
162
306
|
{ name: 'npm', url: 'https://registry.npmjs.org' }
|
|
163
307
|
];
|
|
164
308
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const apiChecks = await Promise.all(
|
|
168
|
-
externalAPIs.map(api =>
|
|
309
|
+
const results = await Promise.all(
|
|
310
|
+
apis.map(api =>
|
|
169
311
|
checkExternalAPI(api.url).then(result => ({ name: api.name, ...result }))
|
|
170
312
|
)
|
|
171
313
|
);
|
|
172
314
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
315
|
+
const apiStatuses = {};
|
|
316
|
+
let allReachable = true;
|
|
317
|
+
|
|
318
|
+
results.forEach(({ name, ...checkResult }) => {
|
|
319
|
+
apiStatuses[name] = checkResult;
|
|
176
320
|
if (!checkResult.reachable) {
|
|
177
|
-
|
|
321
|
+
allReachable = false;
|
|
178
322
|
}
|
|
179
323
|
});
|
|
324
|
+
|
|
325
|
+
return { apiStatuses, allReachable };
|
|
326
|
+
};
|
|
180
327
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
};
|
|
190
366
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
373
|
+
|
|
374
|
+
try {
|
|
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
|
|
201
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
|
+
}
|
|
202
393
|
} catch (error) {
|
|
203
|
-
|
|
394
|
+
response.checks.encryption = {
|
|
204
395
|
status: 'unhealthy',
|
|
205
396
|
error: error.message
|
|
206
397
|
};
|
|
207
|
-
|
|
398
|
+
response.status = 'unhealthy';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const { apiStatuses, allReachable } = await checkExternalAPIs();
|
|
402
|
+
response.checks.externalApis = apiStatuses;
|
|
403
|
+
if (!allReachable) {
|
|
404
|
+
response.status = 'unhealthy';
|
|
208
405
|
}
|
|
209
406
|
|
|
210
|
-
|
|
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
|
+
}
|
|
211
416
|
|
|
212
|
-
|
|
417
|
+
response.responseTime = response.calculateResponseTime();
|
|
418
|
+
delete response.calculateResponseTime;
|
|
213
419
|
|
|
214
|
-
|
|
420
|
+
const statusCode = response.status === 'healthy' ? 200 : 503;
|
|
421
|
+
res.status(statusCode).json(response);
|
|
215
422
|
});
|
|
216
423
|
|
|
217
424
|
router.get('/health/live', (_req, res) => {
|
|
@@ -222,28 +429,29 @@ router.get('/health/live', (_req, res) => {
|
|
|
222
429
|
});
|
|
223
430
|
|
|
224
431
|
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;
|
|
432
|
+
const dbState = getDatabaseState();
|
|
433
|
+
const isDbReady = dbState.isConnected;
|
|
233
434
|
|
|
435
|
+
let areModulesReady = false;
|
|
234
436
|
try {
|
|
235
437
|
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
236
438
|
? moduleFactory.moduleTypes
|
|
237
439
|
: [];
|
|
238
|
-
|
|
440
|
+
areModulesReady = moduleTypes.length > 0;
|
|
239
441
|
} catch (error) {
|
|
240
|
-
|
|
442
|
+
areModulesReady = false;
|
|
241
443
|
}
|
|
242
444
|
|
|
243
|
-
|
|
445
|
+
const isReady = isDbReady && areModulesReady;
|
|
244
446
|
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
});
|
|
247
455
|
});
|
|
248
456
|
|
|
249
457
|
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.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.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.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
|
}
|