@friggframework/core 2.0.0-next.40 → 2.0.0-next.42
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/CLAUDE.md +693 -0
- package/README.md +931 -50
- package/application/commands/README.md +421 -0
- package/application/commands/credential-commands.js +224 -0
- package/application/commands/entity-commands.js +315 -0
- package/application/commands/integration-commands.js +160 -0
- package/application/commands/integration-commands.test.js +123 -0
- package/application/commands/user-commands.js +213 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/create-handler.js +0 -6
- package/credential/repositories/credential-repository-factory.js +47 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +301 -0
- package/credential/repositories/credential-repository-postgres.js +307 -0
- package/credential/repositories/credential-repository.js +307 -0
- package/credential/use-cases/get-credential-for-user.js +21 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/config.js +117 -0
- package/database/encryption/README.md +683 -0
- package/database/encryption/encryption-integration.test.js +553 -0
- package/database/encryption/encryption-schema-registry.js +141 -0
- package/database/encryption/encryption-schema-registry.test.js +392 -0
- package/database/encryption/field-encryption-service.js +226 -0
- package/database/encryption/field-encryption-service.test.js +525 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/mongo-decryption-fix-verification.test.js +348 -0
- package/database/encryption/postgres-decryption-fix-verification.test.js +371 -0
- package/database/encryption/postgres-relation-decryption.test.js +245 -0
- package/database/encryption/prisma-encryption-extension.js +222 -0
- package/database/encryption/prisma-encryption-extension.test.js +439 -0
- package/database/index.js +25 -12
- package/database/models/readme.md +1 -0
- package/database/prisma.js +162 -0
- package/database/repositories/health-check-repository-factory.js +38 -0
- package/database/repositories/health-check-repository-interface.js +86 -0
- package/database/repositories/health-check-repository-mongodb.js +72 -0
- package/database/repositories/health-check-repository-postgres.js +75 -0
- package/database/repositories/health-check-repository.js +108 -0
- package/database/use-cases/check-database-health-use-case.js +34 -0
- package/database/use-cases/check-encryption-health-use-case.js +82 -0
- package/database/use-cases/test-encryption-use-case.js +252 -0
- package/encrypt/Cryptor.js +20 -152
- package/encrypt/index.js +1 -2
- package/encrypt/test-encrypt.js +0 -2
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +0 -3
- package/handlers/auth-flow.integration.test.js +147 -0
- package/handlers/backend-utils.js +25 -45
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/integration-event-dispatcher.test.js +141 -0
- package/handlers/routers/HEALTHCHECK.md +103 -1
- package/handlers/routers/auth.js +3 -14
- package/handlers/routers/health.js +63 -424
- package/handlers/routers/health.test.js +7 -0
- package/handlers/routers/integration-defined-routers.js +8 -5
- package/handlers/routers/user.js +25 -5
- package/handlers/routers/websocket.js +5 -3
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +32 -0
- package/handlers/workers/integration-defined-workers.js +6 -3
- package/index.js +45 -22
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +224 -53
- package/integrations/integration-router.js +386 -178
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-factory.js +44 -0
- package/integrations/repositories/integration-repository-interface.js +115 -0
- package/integrations/repositories/integration-repository-mongo.js +271 -0
- package/integrations/repositories/integration-repository-postgres.js +319 -0
- package/integrations/tests/doubles/dummy-integration-class.js +90 -0
- package/integrations/tests/doubles/test-integration-repository.js +99 -0
- package/integrations/tests/use-cases/create-integration.test.js +131 -0
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +150 -0
- package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +92 -0
- package/integrations/tests/use-cases/get-integration-for-user.test.js +150 -0
- package/integrations/tests/use-cases/get-integration-instance.test.js +176 -0
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +176 -0
- package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
- package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
- package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
- package/integrations/tests/use-cases/update-integration.test.js +141 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/delete-integration-for-user.js +73 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +87 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/index.js +11 -0
- package/integrations/use-cases/load-integration-context-full.test.js +329 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/load-integration-context.test.js +114 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +93 -0
- package/integrations/utils/map-integration-dto.js +36 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/{module-plugin → modules}/entity.js +1 -0
- package/{module-plugin → modules}/index.js +0 -8
- package/modules/module-factory.js +56 -0
- package/modules/module-hydration.test.js +205 -0
- package/modules/module.js +221 -0
- package/modules/repositories/module-repository-factory.js +33 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +386 -0
- package/modules/repositories/module-repository-postgres.js +437 -0
- package/modules/repositories/module-repository.js +327 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +59 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +31 -0
- package/modules/use-cases/get-module.js +56 -0
- package/modules/use-cases/process-authorization-callback.js +121 -0
- package/modules/use-cases/refresh-entity-options.js +59 -0
- package/modules/use-cases/test-module-auth.js +55 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +14 -6
- package/prisma-mongodb/schema.prisma +321 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +303 -0
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-factory.js +38 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/token/repositories/token-repository-factory.js +33 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +212 -0
- package/token/repositories/token-repository-postgres.js +257 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +5 -57
- package/types/syncs/index.d.ts +0 -2
- package/user/repositories/user-repository-factory.js +46 -0
- package/user/repositories/user-repository-interface.js +198 -0
- package/user/repositories/user-repository-mongo.js +250 -0
- package/user/repositories/user-repository-postgres.js +311 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/tests/use-cases/create-individual-user.test.js +24 -0
- package/user/tests/use-cases/create-organization-user.test.js +28 -0
- package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
- package/user/tests/use-cases/login-user.test.js +140 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +77 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
- package/websocket/repositories/websocket-connection-repository.js +160 -0
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/mongo.js +0 -171
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -104
- package/encrypt/encrypt.test.js +0 -1069
- package/handlers/routers/middleware/loadUser.js +0 -15
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/test/auther.test.js +0 -97
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/api-key.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/requester/requester.js +0 -0
- /package/{module-plugin → modules}/requester/requester.test.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
const { Router } = require('express');
|
|
2
|
-
const mongoose = require('mongoose');
|
|
3
|
-
const https = require('https');
|
|
4
|
-
const http = require('http');
|
|
5
2
|
const { moduleFactory, integrationFactory } = require('./../backend-utils');
|
|
6
3
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
4
|
+
const {
|
|
5
|
+
createHealthCheckRepository,
|
|
6
|
+
} = require('../../database/repositories/health-check-repository-factory');
|
|
7
|
+
const {
|
|
8
|
+
TestEncryptionUseCase,
|
|
9
|
+
} = require('../../database/use-cases/test-encryption-use-case');
|
|
10
|
+
const {
|
|
11
|
+
CheckDatabaseHealthUseCase,
|
|
12
|
+
} = require('../../database/use-cases/check-database-health-use-case');
|
|
13
|
+
const {
|
|
14
|
+
CheckEncryptionHealthUseCase,
|
|
15
|
+
} = require('../../database/use-cases/check-encryption-health-use-case');
|
|
16
|
+
const {
|
|
17
|
+
CheckExternalApisHealthUseCase,
|
|
18
|
+
} = require('../use-cases/check-external-apis-health-use-case');
|
|
19
|
+
const {
|
|
20
|
+
CheckIntegrationsHealthUseCase,
|
|
21
|
+
} = require('../use-cases/check-integrations-health-use-case');
|
|
7
22
|
|
|
8
23
|
const router = Router();
|
|
24
|
+
const healthCheckRepository = createHealthCheckRepository();
|
|
25
|
+
const testEncryptionUseCase = new TestEncryptionUseCase({
|
|
26
|
+
healthCheckRepository,
|
|
27
|
+
});
|
|
28
|
+
const checkDatabaseHealthUseCase = new CheckDatabaseHealthUseCase({
|
|
29
|
+
healthCheckRepository,
|
|
30
|
+
});
|
|
31
|
+
const checkEncryptionHealthUseCase = new CheckEncryptionHealthUseCase({
|
|
32
|
+
testEncryptionUseCase,
|
|
33
|
+
});
|
|
34
|
+
const checkExternalApisHealthUseCase = new CheckExternalApisHealthUseCase();
|
|
35
|
+
const checkIntegrationsHealthUseCase = new CheckIntegrationsHealthUseCase({
|
|
36
|
+
moduleFactory,
|
|
37
|
+
integrationFactory,
|
|
38
|
+
});
|
|
9
39
|
|
|
10
40
|
const validateApiKey = (req, res, next) => {
|
|
11
41
|
const apiKey = req.headers['x-api-key'];
|
|
@@ -27,385 +57,6 @@ const validateApiKey = (req, res, next) => {
|
|
|
27
57
|
|
|
28
58
|
router.use(validateApiKey);
|
|
29
59
|
|
|
30
|
-
const checkExternalAPI = (url, timeout = 5000) => {
|
|
31
|
-
return new Promise((resolve) => {
|
|
32
|
-
const protocol = url.startsWith('https:') ? https : http;
|
|
33
|
-
const startTime = Date.now();
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const request = protocol.get(url, { timeout }, (res) => {
|
|
37
|
-
const responseTime = Date.now() - startTime;
|
|
38
|
-
resolve({
|
|
39
|
-
status: 'healthy',
|
|
40
|
-
statusCode: res.statusCode,
|
|
41
|
-
responseTime,
|
|
42
|
-
reachable: res.statusCode < 500,
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
request.on('error', (error) => {
|
|
47
|
-
resolve({
|
|
48
|
-
status: 'unhealthy',
|
|
49
|
-
error: error.message,
|
|
50
|
-
responseTime: Date.now() - startTime,
|
|
51
|
-
reachable: false,
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
request.on('timeout', () => {
|
|
56
|
-
request.destroy();
|
|
57
|
-
resolve({
|
|
58
|
-
status: 'timeout',
|
|
59
|
-
error: 'Request timeout',
|
|
60
|
-
responseTime: timeout,
|
|
61
|
-
reachable: false,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
resolve({
|
|
66
|
-
status: 'error',
|
|
67
|
-
error: error.message,
|
|
68
|
-
responseTime: Date.now() - startTime,
|
|
69
|
-
reachable: false,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const getDatabaseState = () => {
|
|
76
|
-
const stateMap = {
|
|
77
|
-
0: 'disconnected',
|
|
78
|
-
1: 'connected',
|
|
79
|
-
2: 'connecting',
|
|
80
|
-
3: 'disconnecting',
|
|
81
|
-
};
|
|
82
|
-
const readyState = mongoose.connection.readyState;
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
readyState,
|
|
86
|
-
stateName: stateMap[readyState],
|
|
87
|
-
isConnected: readyState === 1,
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const checkDatabaseHealth = async () => {
|
|
92
|
-
const { stateName, isConnected } = getDatabaseState();
|
|
93
|
-
const result = {
|
|
94
|
-
status: isConnected ? 'healthy' : 'unhealthy',
|
|
95
|
-
state: stateName,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
if (isConnected) {
|
|
99
|
-
const pingStart = Date.now();
|
|
100
|
-
await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
|
|
101
|
-
result.responseTime = Date.now() - pingStart;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return result;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const getEncryptionConfiguration = () => {
|
|
108
|
-
const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } =
|
|
109
|
-
process.env;
|
|
110
|
-
|
|
111
|
-
const defaultBypassStages = ['dev', 'test', 'local'];
|
|
112
|
-
const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
|
|
113
|
-
const bypassStages = useEnv
|
|
114
|
-
? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
|
|
115
|
-
: defaultBypassStages;
|
|
116
|
-
|
|
117
|
-
const isBypassed = bypassStages.includes(STAGE);
|
|
118
|
-
const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
|
|
119
|
-
const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '' && !hasAES;
|
|
120
|
-
const mode = hasAES ? 'aes' : hasKMS ? 'kms' : 'none';
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
stage: STAGE || null,
|
|
124
|
-
isBypassed,
|
|
125
|
-
hasAES,
|
|
126
|
-
hasKMS,
|
|
127
|
-
mode,
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const createTestEncryptionModel = () => {
|
|
132
|
-
const { Encrypt } = require('./../../encrypt');
|
|
133
|
-
|
|
134
|
-
const testSchema = new mongoose.Schema(
|
|
135
|
-
{
|
|
136
|
-
testSecret: { type: String, lhEncrypt: true },
|
|
137
|
-
normalField: { type: String },
|
|
138
|
-
nestedSecret: {
|
|
139
|
-
value: { type: String, lhEncrypt: true },
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
{ timestamps: false }
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
testSchema.plugin(Encrypt);
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
mongoose.models.TestEncryption ||
|
|
149
|
-
mongoose.model('TestEncryption', testSchema)
|
|
150
|
-
);
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const verifyDecryption = (retrievedDoc, originalData) => {
|
|
154
|
-
return (
|
|
155
|
-
retrievedDoc &&
|
|
156
|
-
retrievedDoc.testSecret === originalData.testSecret &&
|
|
157
|
-
retrievedDoc.normalField === originalData.normalField &&
|
|
158
|
-
retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value
|
|
159
|
-
);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
|
|
163
|
-
const collectionName = TestModel.collection.name;
|
|
164
|
-
const rawDoc = await mongoose.connection.db
|
|
165
|
-
.collection(collectionName)
|
|
166
|
-
.findOne({ _id: testDoc._id });
|
|
167
|
-
|
|
168
|
-
const secretIsEncrypted =
|
|
169
|
-
rawDoc &&
|
|
170
|
-
typeof rawDoc.testSecret === 'string' &&
|
|
171
|
-
rawDoc.testSecret.includes(':') &&
|
|
172
|
-
rawDoc.testSecret !== originalData.testSecret;
|
|
173
|
-
|
|
174
|
-
const nestedIsEncrypted =
|
|
175
|
-
rawDoc?.nestedSecret?.value &&
|
|
176
|
-
typeof rawDoc.nestedSecret.value === 'string' &&
|
|
177
|
-
rawDoc.nestedSecret.value.includes(':') &&
|
|
178
|
-
rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
|
|
179
|
-
|
|
180
|
-
const normalNotEncrypted =
|
|
181
|
-
rawDoc && rawDoc.normalField === originalData.normalField;
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
secretIsEncrypted,
|
|
185
|
-
nestedIsEncrypted,
|
|
186
|
-
normalNotEncrypted,
|
|
187
|
-
};
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const evaluateEncryptionTestResults = (decryptionWorks, encryptionResults) => {
|
|
191
|
-
const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } =
|
|
192
|
-
encryptionResults;
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
decryptionWorks &&
|
|
196
|
-
secretIsEncrypted &&
|
|
197
|
-
nestedIsEncrypted &&
|
|
198
|
-
normalNotEncrypted
|
|
199
|
-
) {
|
|
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 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
|
-
const testEncryption = async () => {
|
|
236
|
-
// eslint-disable-next-line no-console
|
|
237
|
-
console.log('Starting encryption test');
|
|
238
|
-
const TestModel = createTestEncryptionModel();
|
|
239
|
-
// eslint-disable-next-line no-console
|
|
240
|
-
console.log('Test model created');
|
|
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
|
-
|
|
255
|
-
try {
|
|
256
|
-
const retrievedDoc = await withTimeout(
|
|
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');
|
|
263
|
-
const decryptionWorks = verifyDecryption(retrievedDoc, testData);
|
|
264
|
-
const encryptionResults = await withTimeout(
|
|
265
|
-
verifyEncryptionInDatabase(testDoc, testData, TestModel),
|
|
266
|
-
5000,
|
|
267
|
-
'Database verification timed out'
|
|
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
|
-
|
|
277
|
-
return {
|
|
278
|
-
...evaluation,
|
|
279
|
-
encryptionWorks: decryptionWorks,
|
|
280
|
-
};
|
|
281
|
-
} finally {
|
|
282
|
-
await withTimeout(
|
|
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');
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const checkEncryptionHealth = async () => {
|
|
293
|
-
const config = getEncryptionConfiguration();
|
|
294
|
-
|
|
295
|
-
if (config.isBypassed || config.mode === 'none') {
|
|
296
|
-
// eslint-disable-next-line no-console
|
|
297
|
-
console.log('Encryption check bypassed:', {
|
|
298
|
-
stage: config.stage,
|
|
299
|
-
mode: config.mode,
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
const testResult = config.isBypassed
|
|
303
|
-
? 'Encryption bypassed for this stage'
|
|
304
|
-
: 'No encryption keys configured';
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
status: 'disabled',
|
|
308
|
-
mode: config.mode,
|
|
309
|
-
bypassed: config.isBypassed,
|
|
310
|
-
stage: config.stage,
|
|
311
|
-
testResult,
|
|
312
|
-
encryptionWorks: false,
|
|
313
|
-
debug: {
|
|
314
|
-
hasKMS: config.hasKMS,
|
|
315
|
-
hasAES: config.hasAES,
|
|
316
|
-
},
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
const testResults = await testEncryption();
|
|
322
|
-
|
|
323
|
-
return {
|
|
324
|
-
...testResults,
|
|
325
|
-
mode: config.mode,
|
|
326
|
-
bypassed: config.isBypassed,
|
|
327
|
-
stage: config.stage,
|
|
328
|
-
debug: {
|
|
329
|
-
hasKMS: config.hasKMS,
|
|
330
|
-
hasAES: config.hasAES,
|
|
331
|
-
},
|
|
332
|
-
};
|
|
333
|
-
} catch (error) {
|
|
334
|
-
return {
|
|
335
|
-
status: 'unhealthy',
|
|
336
|
-
mode: config.mode,
|
|
337
|
-
bypassed: config.isBypassed,
|
|
338
|
-
stage: config.stage,
|
|
339
|
-
testResult: `Encryption test failed: ${error.message}`,
|
|
340
|
-
encryptionWorks: false,
|
|
341
|
-
debug: {
|
|
342
|
-
hasKMS: config.hasKMS,
|
|
343
|
-
hasAES: config.hasAES,
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const checkExternalAPIs = async () => {
|
|
350
|
-
const apis = [
|
|
351
|
-
{ name: 'github', url: 'https://api.github.com/status' },
|
|
352
|
-
{ name: 'npm', url: 'https://registry.npmjs.org' },
|
|
353
|
-
];
|
|
354
|
-
|
|
355
|
-
const results = await Promise.all(
|
|
356
|
-
apis.map((api) =>
|
|
357
|
-
checkExternalAPI(api.url).then((result) => ({
|
|
358
|
-
name: api.name,
|
|
359
|
-
...result,
|
|
360
|
-
}))
|
|
361
|
-
)
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
const apiStatuses = {};
|
|
365
|
-
let allReachable = true;
|
|
366
|
-
|
|
367
|
-
results.forEach(({ name, ...checkResult }) => {
|
|
368
|
-
apiStatuses[name] = checkResult;
|
|
369
|
-
if (!checkResult.reachable) {
|
|
370
|
-
allReachable = false;
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
return { apiStatuses, allReachable };
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
const checkIntegrations = () => {
|
|
378
|
-
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
379
|
-
? moduleFactory.moduleTypes
|
|
380
|
-
: [];
|
|
381
|
-
|
|
382
|
-
const integrationTypes = Array.isArray(integrationFactory.integrationTypes)
|
|
383
|
-
? integrationFactory.integrationTypes
|
|
384
|
-
: [];
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
status: 'healthy',
|
|
388
|
-
modules: {
|
|
389
|
-
count: moduleTypes.length,
|
|
390
|
-
available: moduleTypes,
|
|
391
|
-
},
|
|
392
|
-
integrations: {
|
|
393
|
-
count: integrationTypes.length,
|
|
394
|
-
available: integrationTypes,
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
const buildHealthCheckResponse = (startTime) => {
|
|
400
|
-
return {
|
|
401
|
-
service: 'frigg-core-api',
|
|
402
|
-
status: 'healthy',
|
|
403
|
-
timestamp: new Date().toISOString(),
|
|
404
|
-
checks: {},
|
|
405
|
-
calculateResponseTime: () => Date.now() - startTime,
|
|
406
|
-
};
|
|
407
|
-
};
|
|
408
|
-
|
|
409
60
|
// Helper to detect VPC configuration
|
|
410
61
|
const detectVpcConfiguration = async () => {
|
|
411
62
|
const results = {
|
|
@@ -666,12 +317,16 @@ router.get('/health', async (_req, res) => {
|
|
|
666
317
|
});
|
|
667
318
|
|
|
668
319
|
router.get('/health/detailed', async (_req, res) => {
|
|
669
|
-
// eslint-disable-next-line no-console
|
|
670
320
|
console.log('Starting detailed health check');
|
|
671
321
|
const startTime = Date.now();
|
|
672
|
-
const response = buildHealthCheckResponse(startTime);
|
|
673
322
|
|
|
674
|
-
|
|
323
|
+
const response = {
|
|
324
|
+
service: 'frigg-core-api',
|
|
325
|
+
status: 'healthy',
|
|
326
|
+
timestamp: new Date().toISOString(),
|
|
327
|
+
checks: {},
|
|
328
|
+
};
|
|
329
|
+
|
|
675
330
|
console.log('Health Check Environment:', {
|
|
676
331
|
hasKmsKeyArn: !!process.env.KMS_KEY_ARN,
|
|
677
332
|
awsRegion: process.env.AWS_REGION,
|
|
@@ -680,7 +335,6 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
680
335
|
stage: process.env.STAGE,
|
|
681
336
|
});
|
|
682
337
|
|
|
683
|
-
// 1. Network diagnostics (run first to understand connectivity)
|
|
684
338
|
try {
|
|
685
339
|
console.log('Running network diagnostics...');
|
|
686
340
|
const networkStart = Date.now();
|
|
@@ -703,10 +357,8 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
703
357
|
console.log('Network diagnostics error:', error.message);
|
|
704
358
|
}
|
|
705
359
|
|
|
706
|
-
// 2. KMS decrypt capability (must succeed before DB assumed healthy if encryption depends on KMS)
|
|
707
360
|
try {
|
|
708
361
|
console.log('About to check KMS capability...');
|
|
709
|
-
// Wrap the entire KMS check in a timeout (allow up to 25 seconds for slow VPC)
|
|
710
362
|
const kmsCheckPromise = checkKmsDecryptCapability();
|
|
711
363
|
const kmsTimeoutPromise = new Promise((_, reject) =>
|
|
712
364
|
setTimeout(
|
|
@@ -722,22 +374,18 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
722
374
|
if (response.checks.kms.status === 'unhealthy') {
|
|
723
375
|
response.status = 'unhealthy';
|
|
724
376
|
}
|
|
725
|
-
// eslint-disable-next-line no-console
|
|
726
377
|
console.log('KMS check completed:', response.checks.kms);
|
|
727
378
|
} catch (error) {
|
|
728
379
|
response.checks.kms = { status: 'unhealthy', error: error.message };
|
|
729
380
|
response.status = 'unhealthy';
|
|
730
|
-
// eslint-disable-next-line no-console
|
|
731
381
|
console.log('KMS check error:', error.message);
|
|
732
382
|
}
|
|
733
383
|
|
|
734
384
|
try {
|
|
735
|
-
response.checks.database = await
|
|
736
|
-
|
|
737
|
-
if (!dbState.isConnected) {
|
|
385
|
+
response.checks.database = await checkDatabaseHealthUseCase.execute();
|
|
386
|
+
if (response.checks.database.status === 'unhealthy') {
|
|
738
387
|
response.status = 'unhealthy';
|
|
739
388
|
}
|
|
740
|
-
// eslint-disable-next-line no-console
|
|
741
389
|
console.log('Database check completed:', response.checks.database);
|
|
742
390
|
} catch (error) {
|
|
743
391
|
response.checks.database = {
|
|
@@ -745,16 +393,14 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
745
393
|
error: error.message,
|
|
746
394
|
};
|
|
747
395
|
response.status = 'unhealthy';
|
|
748
|
-
// eslint-disable-next-line no-console
|
|
749
396
|
console.log('Database check error:', error.message);
|
|
750
397
|
}
|
|
751
398
|
|
|
752
399
|
try {
|
|
753
|
-
response.checks.encryption = await
|
|
400
|
+
response.checks.encryption = await checkEncryptionHealthUseCase.execute();
|
|
754
401
|
if (response.checks.encryption.status === 'unhealthy') {
|
|
755
402
|
response.status = 'unhealthy';
|
|
756
403
|
}
|
|
757
|
-
// eslint-disable-next-line no-console
|
|
758
404
|
console.log('Encryption check completed:', response.checks.encryption);
|
|
759
405
|
} catch (error) {
|
|
760
406
|
response.checks.encryption = {
|
|
@@ -762,42 +408,42 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
762
408
|
error: error.message,
|
|
763
409
|
};
|
|
764
410
|
response.status = 'unhealthy';
|
|
765
|
-
// eslint-disable-next-line no-console
|
|
766
411
|
console.log('Encryption check error:', error.message);
|
|
767
412
|
}
|
|
768
413
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
414
|
+
try {
|
|
415
|
+
const { apiStatuses, allReachable } = await checkExternalApisHealthUseCase.execute();
|
|
416
|
+
response.checks.externalApis = apiStatuses;
|
|
417
|
+
if (!allReachable) {
|
|
418
|
+
response.status = 'unhealthy';
|
|
419
|
+
}
|
|
420
|
+
console.log('External APIs check completed:', response.checks.externalApis);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
response.checks.externalApis = {
|
|
423
|
+
status: 'unhealthy',
|
|
424
|
+
error: error.message,
|
|
425
|
+
};
|
|
772
426
|
response.status = 'unhealthy';
|
|
427
|
+
console.log('External APIs check error:', error.message);
|
|
773
428
|
}
|
|
774
|
-
// eslint-disable-next-line no-console
|
|
775
|
-
console.log('External APIs check completed:', response.checks.externalApis);
|
|
776
429
|
|
|
777
430
|
try {
|
|
778
|
-
response.checks.integrations =
|
|
779
|
-
|
|
780
|
-
console.log(
|
|
781
|
-
'Integrations check completed:',
|
|
782
|
-
response.checks.integrations
|
|
783
|
-
);
|
|
431
|
+
response.checks.integrations = checkIntegrationsHealthUseCase.execute();
|
|
432
|
+
console.log('Integrations check completed:', response.checks.integrations);
|
|
784
433
|
} catch (error) {
|
|
785
434
|
response.checks.integrations = {
|
|
786
435
|
status: 'unhealthy',
|
|
787
436
|
error: error.message,
|
|
788
437
|
};
|
|
789
438
|
response.status = 'unhealthy';
|
|
790
|
-
// eslint-disable-next-line no-console
|
|
791
439
|
console.log('Integrations check error:', error.message);
|
|
792
440
|
}
|
|
793
441
|
|
|
794
|
-
response.responseTime =
|
|
795
|
-
delete response.calculateResponseTime;
|
|
442
|
+
response.responseTime = Date.now() - startTime;
|
|
796
443
|
|
|
797
444
|
const statusCode = response.status === 'healthy' ? 200 : 503;
|
|
798
445
|
res.status(statusCode).json(response);
|
|
799
446
|
|
|
800
|
-
// eslint-disable-next-line no-console
|
|
801
447
|
console.log(
|
|
802
448
|
'Final health status:',
|
|
803
449
|
response.status,
|
|
@@ -814,18 +460,11 @@ router.get('/health/live', (_req, res) => {
|
|
|
814
460
|
});
|
|
815
461
|
|
|
816
462
|
router.get('/health/ready', async (_req, res) => {
|
|
817
|
-
const
|
|
818
|
-
const isDbReady =
|
|
463
|
+
const dbHealth = await checkDatabaseHealthUseCase.execute();
|
|
464
|
+
const isDbReady = dbHealth.status === 'healthy';
|
|
819
465
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
|
|
823
|
-
? moduleFactory.moduleTypes
|
|
824
|
-
: [];
|
|
825
|
-
areModulesReady = moduleTypes.length > 0;
|
|
826
|
-
} catch (error) {
|
|
827
|
-
areModulesReady = false;
|
|
828
|
-
}
|
|
466
|
+
const integrationsHealth = checkIntegrationsHealthUseCase.execute();
|
|
467
|
+
const areModulesReady = integrationsHealth.modules.count > 0;
|
|
829
468
|
|
|
830
469
|
const isReady = isDbReady && areModulesReady;
|
|
831
470
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
process.env.HEALTH_API_KEY = 'test-api-key';
|
|
2
2
|
|
|
3
|
+
jest.mock('../../database/config', () => ({
|
|
4
|
+
DB_TYPE: 'mongodb',
|
|
5
|
+
getDatabaseType: jest.fn(() => 'mongodb'),
|
|
6
|
+
PRISMA_LOG_LEVEL: 'error,warn',
|
|
7
|
+
PRISMA_QUERY_LOGGING: false,
|
|
8
|
+
}));
|
|
9
|
+
|
|
3
10
|
jest.mock('mongoose', () => ({
|
|
4
11
|
set: jest.fn(),
|
|
5
12
|
connection: {
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
2
2
|
const {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} = require('./../backend-utils');
|
|
3
|
+
loadAppDefinition,
|
|
4
|
+
} = require('../app-definition-loader');
|
|
6
5
|
const { Router } = require('express');
|
|
6
|
+
const { loadRouterFromObject } = require('../backend-utils');
|
|
7
7
|
|
|
8
8
|
const handlers = {};
|
|
9
|
-
|
|
9
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
10
|
+
|
|
11
|
+
//todo: this should be in a use case class
|
|
12
|
+
for (const IntegrationClass of integrationClasses) {
|
|
10
13
|
const router = Router();
|
|
11
14
|
const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
|
|
12
|
-
|
|
15
|
+
|
|
13
16
|
console.log(`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`);
|
|
14
17
|
|
|
15
18
|
for (const routeDef of IntegrationClass.Definition.routes) {
|
package/handlers/routers/user.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const { createAppHandler } = require('../app-handler-helpers');
|
|
3
3
|
const { checkRequiredParams } = require('@friggframework/core');
|
|
4
|
-
const {
|
|
4
|
+
const { createUserRepository } = require('../../user/user-repository-factory');
|
|
5
|
+
const {
|
|
6
|
+
CreateIndividualUser,
|
|
7
|
+
} = require('../../user/use-cases/create-individual-user');
|
|
8
|
+
const { LoginUser } = require('../../user/use-cases/login-user');
|
|
9
|
+
const {
|
|
10
|
+
CreateTokenForUserId,
|
|
11
|
+
} = require('../../user/use-cases/create-token-for-user-id');
|
|
5
12
|
const catchAsyncError = require('express-async-handler');
|
|
13
|
+
const { loadAppDefinition } = require('../app-definition-loader');
|
|
6
14
|
|
|
7
15
|
const router = express();
|
|
16
|
+
const { userConfig } = loadAppDefinition();
|
|
17
|
+
const userRepository = createUserRepository();
|
|
18
|
+
const createIndividualUser = new CreateIndividualUser({
|
|
19
|
+
userRepository,
|
|
20
|
+
userConfig,
|
|
21
|
+
});
|
|
22
|
+
const loginUser = new LoginUser({
|
|
23
|
+
userRepository,
|
|
24
|
+
userConfig,
|
|
25
|
+
});
|
|
26
|
+
const createTokenForUserId = new CreateTokenForUserId({ userRepository });
|
|
8
27
|
|
|
9
28
|
// define the login endpoint
|
|
10
29
|
router.route('/user/login').post(
|
|
@@ -13,8 +32,8 @@ router.route('/user/login').post(
|
|
|
13
32
|
'username',
|
|
14
33
|
'password',
|
|
15
34
|
]);
|
|
16
|
-
const user = await
|
|
17
|
-
const token = await user.
|
|
35
|
+
const user = await loginUser.execute({ username, password });
|
|
36
|
+
const token = await createTokenForUserId.execute(user.getId(), 120);
|
|
18
37
|
res.status(201);
|
|
19
38
|
res.json({ token });
|
|
20
39
|
})
|
|
@@ -26,11 +45,12 @@ router.route('/user/create').post(
|
|
|
26
45
|
'username',
|
|
27
46
|
'password',
|
|
28
47
|
]);
|
|
29
|
-
|
|
48
|
+
|
|
49
|
+
const user = await createIndividualUser.execute({
|
|
30
50
|
username,
|
|
31
51
|
password,
|
|
32
52
|
});
|
|
33
|
-
const token = await user.
|
|
53
|
+
const token = await createTokenForUserId.execute(user.getId(), 120);
|
|
34
54
|
res.status(201);
|
|
35
55
|
res.json({ token });
|
|
36
56
|
})
|