@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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/CHANGELOG.md +66 -67
- package/CLAUDE.md +148 -124
- package/README.md +165 -130
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
- package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
- package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
- package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
- package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
- package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
- package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/admin-script-commands.js +348 -0
- package/application/commands/integration-commands.js +12 -6
- package/application/index.js +5 -3
- package/assertions/get.js +17 -7
- package/associations/association.js +59 -57
- package/core/CLAUDE.md +222 -156
- package/core/Worker.js +33 -12
- package/credential/credential-router.js +262 -0
- package/credential/repositories/credential-repository-documentdb.js +71 -1
- package/credential/repositories/credential-repository-factory.js +4 -6
- package/credential/repositories/credential-repository-interface.js +8 -2
- package/credential/repositories/credential-repository-mongo.js +45 -4
- package/credential/repositories/credential-repository-postgres.js +44 -3
- package/credential/repositories/credential-repository.js +3 -1
- package/credential/use-cases/delete-credential-for-user.js +50 -0
- package/credential/use-cases/get-credential-for-user.js +21 -3
- package/credential/use-cases/list-credentials-for-user.js +31 -0
- package/credential/use-cases/reauthorize-credential.js +103 -0
- package/credential/use-cases/update-authentication-status.js +5 -2
- package/database/MONGODB_TRANSACTION_FIX.md +72 -52
- package/database/config.js +13 -59
- package/database/documentdb-encryption-service.js +29 -13
- package/database/documentdb-utils.js +21 -12
- package/database/encryption/README.md +53 -36
- package/database/encryption/encryption-schema-registry.js +29 -23
- package/database/encryption/field-encryption-service.js +15 -4
- package/database/encryption/logger.js +3 -2
- package/database/encryption/prisma-encryption-extension.js +5 -8
- package/database/prisma.js +22 -14
- package/database/repositories/health-check-repository-factory.js +7 -5
- package/database/repositories/health-check-repository-interface.js +15 -5
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/use-cases/check-database-health-use-case.js +3 -2
- package/database/use-cases/check-database-state-use-case.js +8 -7
- package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
- package/database/use-cases/get-migration-status-use-case.js +4 -2
- package/database/use-cases/run-database-migration-use-case.js +41 -9
- package/database/use-cases/test-encryption-use-case.js +4 -5
- package/database/use-cases/trigger-database-migration-use-case.js +9 -8
- package/database/utils/mongodb-collection-utils.js +11 -3
- package/database/utils/mongodb-schema-init.js +10 -4
- package/database/utils/prisma-runner.js +144 -74
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
- package/encrypt/Cryptor.js +54 -49
- package/encrypt/aes-encryption-key-provider.js +82 -0
- package/encrypt/encryption-key-provider-interface.js +50 -0
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +59 -4
- package/generated/prisma-mongodb/index-browser.js +49 -1
- package/generated/prisma-mongodb/index.d.ts +4023 -61
- package/generated/prisma-mongodb/index.js +59 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +87 -0
- package/generated/prisma-mongodb/wasm.js +59 -4
- package/generated/prisma-postgresql/edge.js +58 -4
- package/generated/prisma-postgresql/index-browser.js +48 -1
- package/generated/prisma-postgresql/index.d.ts +4738 -54
- package/generated/prisma-postgresql/index.js +58 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +85 -0
- package/generated/prisma-postgresql/wasm.js +58 -4
- package/handlers/WEBHOOKS.md +104 -81
- package/handlers/app-definition-loader.js +53 -10
- package/handlers/app-handler-helpers.js +14 -3
- package/handlers/backend-utils.js +0 -1
- package/handlers/database-migration-handler.js +64 -45
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +170 -135
- package/handlers/routers/admin.js +474 -0
- package/handlers/routers/auth.js +51 -9
- package/handlers/routers/db-migration.handler.js +2 -3
- package/handlers/routers/db-migration.js +102 -66
- package/handlers/routers/docs.js +180 -0
- package/handlers/routers/health.js +84 -283
- package/handlers/routers/integration-defined-routers.js +44 -33
- package/handlers/routers/integration-webhook-routers.js +73 -52
- package/handlers/routers/middleware/loadUser.js +39 -0
- package/handlers/routers/middleware/requireAdmin.js +43 -0
- package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
- package/handlers/routers/user.js +30 -14
- package/handlers/routers/websocket.js +18 -4
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- package/handlers/workers/integration-defined-workers.js +31 -20
- package/index.js +50 -16
- package/infrastructure/scheduler/index.js +15 -11
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
- package/integrations/WEBHOOK-QUICKSTART.md +21 -14
- package/integrations/index.js +4 -0
- package/integrations/integration-base.js +9 -6
- package/integrations/integration-router.js +1828 -157
- package/integrations/options.js +47 -20
- package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
- package/integrations/repositories/integration-repository-documentdb.js +53 -24
- package/integrations/repositories/integration-repository-factory.js +6 -6
- package/integrations/repositories/integration-repository-interface.js +27 -9
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +4 -7
- package/integrations/repositories/process-repository-interface.js +9 -5
- package/integrations/repositories/process-repository-mongo.js +7 -3
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +9 -8
- package/integrations/tests/doubles/dummy-integration-class.js +81 -8
- package/integrations/tests/doubles/test-integration-repository.js +51 -11
- package/integrations/use-cases/create-integration.js +36 -32
- package/integrations/use-cases/create-process.js +20 -9
- package/integrations/use-cases/delete-integration-for-user.js +5 -4
- package/integrations/use-cases/execute-proxy-request.js +564 -0
- package/integrations/use-cases/get-integration-for-user.js +25 -10
- package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
- package/integrations/use-cases/get-integrations-for-user.js +4 -1
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +5 -6
- package/integrations/use-cases/index.js +3 -1
- package/integrations/use-cases/load-integration-context.js +1 -5
- package/integrations/use-cases/update-process-state.js +10 -8
- package/integrations/utils/map-integration-dto.js +9 -3
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +32 -30
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/domain/entities/AuthorizationSession.js +131 -0
- package/modules/domain/entities/index.js +10 -0
- package/modules/module.js +4 -9
- package/modules/repositories/authorization-session-repository-factory.js +53 -0
- package/modules/repositories/authorization-session-repository-interface.js +75 -0
- package/modules/repositories/authorization-session-repository-mongo.js +169 -0
- package/modules/repositories/authorization-session-repository-postgres.js +170 -0
- package/modules/repositories/module-repository-documentdb.js +89 -41
- package/modules/repositories/module-repository-factory.js +4 -6
- package/modules/repositories/module-repository-interface.js +13 -0
- package/modules/repositories/module-repository-mongo.js +40 -5
- package/modules/repositories/module-repository-postgres.js +42 -5
- package/modules/repositories/module-repository.js +28 -0
- package/modules/requester/api-key.js +0 -2
- package/modules/requester/basic.js +0 -1
- package/modules/requester/oauth-2.js +3 -14
- package/modules/test/mock-api/definition.js +15 -10
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +10 -4
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/delete-module-entity.js +23 -0
- package/modules/use-cases/get-authorization-requirements.js +110 -0
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +8 -6
- package/modules/use-cases/get-module-entity-by-id.js +16 -0
- package/modules/use-cases/get-module.js +12 -9
- package/modules/use-cases/process-authorization-callback.js +12 -6
- package/modules/use-cases/process-authorization-step.js +160 -0
- package/modules/use-cases/refresh-entity-options.js +8 -6
- package/modules/use-cases/start-authorization-session.js +86 -0
- package/modules/use-cases/test-module-auth.js +8 -6
- package/modules/use-cases/update-module-entity.js +27 -0
- package/modules/utils/map-module-dto.js +2 -2
- package/openapi/openapi-spec-generator.js +370 -0
- package/openapi/openapi-v1.yaml +1360 -0
- package/openapi/openapi-v2.yaml +1535 -0
- package/package.json +7 -9
- package/prisma-mongodb/schema.prisma +87 -0
- package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
- package/prisma-postgresql/schema.prisma +85 -0
- package/providers/index.js +13 -0
- package/providers/resolve-provider.js +93 -0
- package/queues/index.js +19 -0
- package/queues/providers/index.js +7 -0
- package/queues/providers/netlify-background-provider.js +76 -0
- package/queues/providers/qstash-queue-provider.js +122 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-provider-factory.js +93 -0
- package/queues/queue-provider.js +46 -0
- package/queues/queuer-util.js +33 -25
- package/syncs/repositories/sync-repository-documentdb.js +39 -19
- package/syncs/repositories/sync-repository-factory.js +4 -4
- package/syncs/repositories/sync-repository-interface.js +6 -2
- package/syncs/repositories/sync-repository-postgres.js +19 -13
- package/syncs/sync.js +104 -100
- package/token/repositories/token-repository-factory.js +4 -6
- package/types/assertions/index.d.ts +72 -72
- package/types/core/index.d.ts +98 -53
- package/types/errors/index.d.ts +61 -54
- package/types/eslint-config/index.d.ts +40 -40
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/prettier-config/index.d.ts +5 -5
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +95 -93
- package/user/repositories/user-repository-factory.js +4 -4
- package/user/tests/doubles/test-user-repository.js +5 -2
- package/user/use-cases/authenticate-user.js +2 -4
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +8 -7
- package/user/use-cases/create-organization-user.js +1 -1
- package/user/use-cases/create-token-for-user-id.js +1 -1
- package/user/use-cases/delete-user.js +47 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
- package/user/use-cases/get-user-from-bearer-token.js +7 -3
- package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +18 -4
- package/utils/backend-path.js +2 -2
- package/utils/index.js +4 -1
- package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
- package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
- package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
- package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
- package/websocket/repositories/websocket-connection-repository.js +26 -20
- package/websocket/websocket-message-sender-interface.js +38 -0
- package/database/adapters/lambda-invoker.js +0 -97
- package/database/repositories/migration-status-repository-s3.js +0 -137
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
|
@@ -29,43 +29,58 @@ const {
|
|
|
29
29
|
} = require('../use-cases/check-integrations-health-use-case');
|
|
30
30
|
|
|
31
31
|
const router = Router();
|
|
32
|
-
const healthCheckRepository = createHealthCheckRepository({ prismaClient: prisma });
|
|
33
32
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
integrationClasses = appDef.integrations || [];
|
|
33
|
+
// Lazy-initialized use cases (deferred until first request)
|
|
34
|
+
let _checkDatabaseHealthUseCase,
|
|
35
|
+
_checkEncryptionHealthUseCase,
|
|
36
|
+
_checkExternalApisHealthUseCase,
|
|
37
|
+
_checkIntegrationsHealthUseCase;
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
function ensureInitialized() {
|
|
40
|
+
if (!_checkDatabaseHealthUseCase) {
|
|
41
|
+
const healthCheckRepository = createHealthCheckRepository({
|
|
42
|
+
prismaClient: prisma,
|
|
43
|
+
});
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error('Failed to load integrations for health check:', error.message);
|
|
50
|
-
// Factories will be undefined, health check will report unhealthy
|
|
51
|
-
moduleFactory = undefined;
|
|
52
|
-
integrationClasses = [];
|
|
53
|
-
}
|
|
45
|
+
let moduleFactory, integrationClasses;
|
|
46
|
+
try {
|
|
47
|
+
const appDef = loadAppDefinition();
|
|
48
|
+
integrationClasses = appDef.integrations || [];
|
|
54
49
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
const moduleRepository = createModuleRepository();
|
|
51
|
+
const moduleDefinitions =
|
|
52
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses);
|
|
53
|
+
|
|
54
|
+
moduleFactory = new ModuleFactory({
|
|
55
|
+
moduleRepository,
|
|
56
|
+
moduleDefinitions,
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(
|
|
60
|
+
'Failed to load integrations for health check:',
|
|
61
|
+
error.message
|
|
62
|
+
);
|
|
63
|
+
moduleFactory = undefined;
|
|
64
|
+
integrationClasses = [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const testEncryptionUseCase = new TestEncryptionUseCase({
|
|
68
|
+
healthCheckRepository,
|
|
69
|
+
});
|
|
70
|
+
_checkDatabaseHealthUseCase = new CheckDatabaseHealthUseCase({
|
|
71
|
+
healthCheckRepository,
|
|
72
|
+
});
|
|
73
|
+
_checkEncryptionHealthUseCase = new CheckEncryptionHealthUseCase({
|
|
74
|
+
testEncryptionUseCase,
|
|
75
|
+
});
|
|
76
|
+
_checkExternalApisHealthUseCase =
|
|
77
|
+
new CheckExternalApisHealthUseCase();
|
|
78
|
+
_checkIntegrationsHealthUseCase = new CheckIntegrationsHealthUseCase({
|
|
79
|
+
moduleFactory,
|
|
80
|
+
integrationClasses,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
69
84
|
|
|
70
85
|
const validateApiKey = (req, res, next) => {
|
|
71
86
|
const apiKey = req.headers['x-frigg-health-api-key'];
|
|
@@ -87,256 +102,32 @@ const validateApiKey = (req, res, next) => {
|
|
|
87
102
|
|
|
88
103
|
router.use(validateApiKey);
|
|
89
104
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
isInVpc: false,
|
|
94
|
-
hasInternetAccess: false,
|
|
95
|
-
canResolvePublicDns: false,
|
|
96
|
-
canConnectToAws: false,
|
|
97
|
-
vpcEndpoints: [],
|
|
98
|
-
};
|
|
105
|
+
// Provider-specific health checks are loaded via resolveProvider()
|
|
106
|
+
// so core doesn't hardcode any provider package names.
|
|
107
|
+
const { resolveProvider } = require('../../providers/resolve-provider');
|
|
99
108
|
|
|
109
|
+
function getResolvedProvider() {
|
|
100
110
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Test 1: Can we resolve public DNS? (indicates DNS configuration)
|
|
106
|
-
try {
|
|
107
|
-
await Promise.race([
|
|
108
|
-
dns.resolve4('www.google.com'),
|
|
109
|
-
new Promise((_, reject) =>
|
|
110
|
-
setTimeout(() => reject(new Error('timeout')), 2000)
|
|
111
|
-
),
|
|
112
|
-
]);
|
|
113
|
-
results.canResolvePublicDns = true;
|
|
114
|
-
} catch (e) {
|
|
115
|
-
console.log('Public DNS resolution failed:', e.message);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Test 2: Can we reach internet? (indicates NAT gateway)
|
|
119
|
-
try {
|
|
120
|
-
const https = require('https');
|
|
121
|
-
await new Promise((resolve, reject) => {
|
|
122
|
-
const req = https.get(
|
|
123
|
-
'https://www.google.com',
|
|
124
|
-
{ timeout: 2000 },
|
|
125
|
-
(res) => {
|
|
126
|
-
res.destroy();
|
|
127
|
-
resolve(true);
|
|
128
|
-
}
|
|
129
|
-
);
|
|
130
|
-
req.on('error', reject);
|
|
131
|
-
req.on('timeout', () => {
|
|
132
|
-
req.destroy();
|
|
133
|
-
reject(new Error('timeout'));
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
results.hasInternetAccess = true;
|
|
137
|
-
} catch (e) {
|
|
138
|
-
console.log('Internet connectivity test failed:', e.message);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Test 3: Check for VPC endpoints by trying to resolve internal AWS endpoints
|
|
142
|
-
const region = process.env.AWS_REGION; // Lambda always provides this
|
|
143
|
-
const vpcEndpointDomains = [
|
|
144
|
-
`com.amazonaws.${region}.kms`,
|
|
145
|
-
`com.amazonaws.vpce.${region}`,
|
|
146
|
-
`kms.${region}.amazonaws.com`,
|
|
147
|
-
];
|
|
148
|
-
|
|
149
|
-
for (const domain of vpcEndpointDomains) {
|
|
150
|
-
try {
|
|
151
|
-
const addresses = await Promise.race([
|
|
152
|
-
dns.resolve4(domain).catch(() => dns.resolve6(domain)),
|
|
153
|
-
new Promise((_, reject) =>
|
|
154
|
-
setTimeout(() => reject(new Error('timeout')), 1000)
|
|
155
|
-
),
|
|
156
|
-
]);
|
|
157
|
-
if (addresses && addresses.length > 0) {
|
|
158
|
-
// Check if it's a private IP (VPC endpoint indicator)
|
|
159
|
-
const isPrivateIp = addresses.some(
|
|
160
|
-
(ip) =>
|
|
161
|
-
ip.startsWith('10.') ||
|
|
162
|
-
ip.startsWith('172.') ||
|
|
163
|
-
ip.startsWith('192.168.')
|
|
164
|
-
);
|
|
165
|
-
if (isPrivateIp) {
|
|
166
|
-
results.vpcEndpoints.push(domain);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} catch (e) {
|
|
170
|
-
// Expected for non-existent endpoints
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Check if Lambda is in VPC using VPC_ENABLED env var set by infrastructure
|
|
175
|
-
results.isInVpc = process.env.VPC_ENABLED === 'true' ||
|
|
176
|
-
(!results.hasInternetAccess && results.canResolvePublicDns) ||
|
|
177
|
-
results.vpcEndpoints.length > 0;
|
|
178
|
-
|
|
179
|
-
results.canConnectToAws =
|
|
180
|
-
results.hasInternetAccess || results.vpcEndpoints.length > 0;
|
|
181
|
-
} catch (error) {
|
|
182
|
-
console.error('VPC detection error:', error.message);
|
|
111
|
+
return resolveProvider();
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
183
114
|
}
|
|
115
|
+
}
|
|
184
116
|
|
|
185
|
-
return results;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// KMS decrypt capability check
|
|
189
117
|
const checkKmsDecryptCapability = async () => {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
status: 'skipped',
|
|
195
|
-
reason: 'KMS_KEY_ARN not configured',
|
|
196
|
-
};
|
|
118
|
+
const provider = getResolvedProvider();
|
|
119
|
+
if (!provider?.checkKmsDecryptCapability) {
|
|
120
|
+
return { status: 'skipped', reason: 'Provider does not support KMS health checks' };
|
|
197
121
|
}
|
|
122
|
+
return provider.checkKmsDecryptCapability();
|
|
123
|
+
};
|
|
198
124
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
awsRegion: process.env.AWS_REGION,
|
|
204
|
-
hasDiscoveryKey: !!process.env.AWS_DISCOVERY_KMS_KEY_ID,
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// First, detect VPC configuration
|
|
208
|
-
const vpcConfig = await detectVpcConfiguration();
|
|
209
|
-
console.log('VPC Configuration:', vpcConfig);
|
|
210
|
-
|
|
211
|
-
// Test DNS resolution for KMS endpoint
|
|
212
|
-
try {
|
|
213
|
-
const dns = require('dns').promises;
|
|
214
|
-
const region = process.env.AWS_REGION; // Lambda always provides this
|
|
215
|
-
const kmsEndpoint = `kms.${region}.amazonaws.com`;
|
|
216
|
-
console.log('Testing DNS resolution for:', kmsEndpoint);
|
|
217
|
-
|
|
218
|
-
// Wrap DNS resolution in a timeout
|
|
219
|
-
const dnsPromise = dns.resolve4(kmsEndpoint);
|
|
220
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
221
|
-
setTimeout(() => reject(new Error('DNS resolution timeout')), 3000)
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
const addresses = await Promise.race([dnsPromise, timeoutPromise]);
|
|
225
|
-
console.log('KMS endpoint resolved to:', addresses);
|
|
226
|
-
|
|
227
|
-
// Check if resolved to private IP (VPC endpoint)
|
|
228
|
-
const isVpcEndpoint = addresses.some(
|
|
229
|
-
(ip) =>
|
|
230
|
-
ip.startsWith('10.') ||
|
|
231
|
-
ip.startsWith('172.') ||
|
|
232
|
-
ip.startsWith('192.168.')
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
if (isVpcEndpoint) {
|
|
236
|
-
console.log(
|
|
237
|
-
'KMS VPC Endpoint detected - using private connectivity'
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Test TCP connectivity to KMS (port 443)
|
|
242
|
-
const net = require('net');
|
|
243
|
-
const testConnection = () =>
|
|
244
|
-
new Promise((resolve) => {
|
|
245
|
-
const socket = new net.Socket();
|
|
246
|
-
const connectionTimeout = setTimeout(() => {
|
|
247
|
-
socket.destroy();
|
|
248
|
-
resolve({ connected: false, error: 'Connection timeout' });
|
|
249
|
-
}, 3000);
|
|
250
|
-
|
|
251
|
-
socket.on('connect', () => {
|
|
252
|
-
clearTimeout(connectionTimeout);
|
|
253
|
-
socket.destroy();
|
|
254
|
-
resolve({ connected: true });
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
socket.on('error', (err) => {
|
|
258
|
-
clearTimeout(connectionTimeout);
|
|
259
|
-
resolve({ connected: false, error: err.message });
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// Try connecting to first resolved address on HTTPS port
|
|
263
|
-
socket.connect(443, addresses[0]);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const connResult = await testConnection();
|
|
267
|
-
console.log('TCP connectivity test:', connResult);
|
|
268
|
-
|
|
269
|
-
if (!connResult.connected) {
|
|
270
|
-
return {
|
|
271
|
-
status: 'unhealthy',
|
|
272
|
-
error: `Cannot connect to KMS endpoint: ${connResult.error}`,
|
|
273
|
-
dnsResolved: true,
|
|
274
|
-
tcpConnection: false,
|
|
275
|
-
vpcConfig,
|
|
276
|
-
latencyMs: Date.now() - start,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
} catch (dnsError) {
|
|
280
|
-
console.error('DNS resolution failed:', dnsError.message);
|
|
281
|
-
return {
|
|
282
|
-
status: 'unhealthy',
|
|
283
|
-
error: `Cannot resolve KMS endpoint: ${dnsError.message}`,
|
|
284
|
-
dnsResolved: false,
|
|
285
|
-
vpcConfig,
|
|
286
|
-
latencyMs: Date.now() - start,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
// Use AWS SDK v3 for consistency with the rest of the codebase
|
|
292
|
-
// eslint-disable-next-line global-require
|
|
293
|
-
const {
|
|
294
|
-
KMSClient,
|
|
295
|
-
GenerateDataKeyCommand,
|
|
296
|
-
DecryptCommand,
|
|
297
|
-
} = require('@aws-sdk/client-kms');
|
|
298
|
-
|
|
299
|
-
// Lambda always provides AWS_REGION
|
|
300
|
-
const region = process.env.AWS_REGION;
|
|
301
|
-
|
|
302
|
-
const kms = new KMSClient({
|
|
303
|
-
region,
|
|
304
|
-
requestHandler: {
|
|
305
|
-
connectionTimeout: 10000, // 10 second connection timeout
|
|
306
|
-
requestTimeout: 25000, // 25 second timeout for slow VPC connections
|
|
307
|
-
},
|
|
308
|
-
maxAttempts: 1, // No retries on health checks
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
// Generate a data key (without plaintext logging) then immediately decrypt ciphertext to ensure decrypt perms.
|
|
312
|
-
const dataKeyResp = await kms.send(
|
|
313
|
-
new GenerateDataKeyCommand({
|
|
314
|
-
KeyId: KMS_KEY_ARN,
|
|
315
|
-
KeySpec: 'AES_256',
|
|
316
|
-
})
|
|
317
|
-
);
|
|
318
|
-
const decryptResp = await kms.send(
|
|
319
|
-
new DecryptCommand({ CiphertextBlob: dataKeyResp.CiphertextBlob })
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
const success = Boolean(
|
|
323
|
-
dataKeyResp.CiphertextBlob && decryptResp.Plaintext
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
status: success ? 'healthy' : 'unhealthy',
|
|
328
|
-
kmsKeyArnSuffix: KMS_KEY_ARN.slice(-12),
|
|
329
|
-
vpcConfig,
|
|
330
|
-
latencyMs: Date.now() - start,
|
|
331
|
-
};
|
|
332
|
-
} catch (error) {
|
|
333
|
-
return {
|
|
334
|
-
status: 'unhealthy',
|
|
335
|
-
error: error.message,
|
|
336
|
-
vpcConfig,
|
|
337
|
-
latencyMs: Date.now() - start,
|
|
338
|
-
};
|
|
125
|
+
const detectVpcConfiguration = async () => {
|
|
126
|
+
const provider = getResolvedProvider();
|
|
127
|
+
if (!provider?.detectVpcConfiguration) {
|
|
128
|
+
return { status: 'skipped', reason: 'Provider does not support VPC detection' };
|
|
339
129
|
}
|
|
130
|
+
return provider.detectVpcConfiguration();
|
|
340
131
|
};
|
|
341
132
|
|
|
342
133
|
router.get('/health', async (_req, res) => {
|
|
@@ -350,6 +141,7 @@ router.get('/health', async (_req, res) => {
|
|
|
350
141
|
});
|
|
351
142
|
|
|
352
143
|
router.get('/health/detailed', async (_req, res) => {
|
|
144
|
+
ensureInitialized();
|
|
353
145
|
console.log('Starting detailed health check');
|
|
354
146
|
const startTime = Date.now();
|
|
355
147
|
|
|
@@ -415,7 +207,7 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
415
207
|
}
|
|
416
208
|
|
|
417
209
|
try {
|
|
418
|
-
response.checks.database = await
|
|
210
|
+
response.checks.database = await _checkDatabaseHealthUseCase.execute();
|
|
419
211
|
if (response.checks.database.status === 'unhealthy') {
|
|
420
212
|
response.status = 'unhealthy';
|
|
421
213
|
}
|
|
@@ -430,7 +222,8 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
430
222
|
}
|
|
431
223
|
|
|
432
224
|
try {
|
|
433
|
-
response.checks.encryption =
|
|
225
|
+
response.checks.encryption =
|
|
226
|
+
await _checkEncryptionHealthUseCase.execute();
|
|
434
227
|
if (response.checks.encryption.status === 'unhealthy') {
|
|
435
228
|
response.status = 'unhealthy';
|
|
436
229
|
}
|
|
@@ -445,12 +238,16 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
445
238
|
}
|
|
446
239
|
|
|
447
240
|
try {
|
|
448
|
-
const { apiStatuses, allReachable } =
|
|
241
|
+
const { apiStatuses, allReachable } =
|
|
242
|
+
await _checkExternalApisHealthUseCase.execute();
|
|
449
243
|
response.checks.externalApis = apiStatuses;
|
|
450
244
|
if (!allReachable) {
|
|
451
245
|
response.status = 'unhealthy';
|
|
452
246
|
}
|
|
453
|
-
console.log(
|
|
247
|
+
console.log(
|
|
248
|
+
'External APIs check completed:',
|
|
249
|
+
response.checks.externalApis
|
|
250
|
+
);
|
|
454
251
|
} catch (error) {
|
|
455
252
|
response.checks.externalApis = {
|
|
456
253
|
status: 'unhealthy',
|
|
@@ -461,8 +258,11 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
461
258
|
}
|
|
462
259
|
|
|
463
260
|
try {
|
|
464
|
-
response.checks.integrations =
|
|
465
|
-
console.log(
|
|
261
|
+
response.checks.integrations = _checkIntegrationsHealthUseCase.execute();
|
|
262
|
+
console.log(
|
|
263
|
+
'Integrations check completed:',
|
|
264
|
+
response.checks.integrations
|
|
265
|
+
);
|
|
466
266
|
} catch (error) {
|
|
467
267
|
response.checks.integrations = {
|
|
468
268
|
status: 'unhealthy',
|
|
@@ -493,10 +293,11 @@ router.get('/health/live', (_req, res) => {
|
|
|
493
293
|
});
|
|
494
294
|
|
|
495
295
|
router.get('/health/ready', async (_req, res) => {
|
|
496
|
-
|
|
296
|
+
ensureInitialized();
|
|
297
|
+
const dbHealth = await _checkDatabaseHealthUseCase.execute();
|
|
497
298
|
const isDbReady = dbHealth.status === 'healthy';
|
|
498
299
|
|
|
499
|
-
const integrationsHealth =
|
|
300
|
+
const integrationsHealth = _checkIntegrationsHealthUseCase.execute();
|
|
500
301
|
const areModulesReady = integrationsHealth.modules.count > 0;
|
|
501
302
|
|
|
502
303
|
const isReady = isDbReady && areModulesReady;
|
|
@@ -1,45 +1,56 @@
|
|
|
1
1
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
2
|
-
const {
|
|
3
|
-
loadAppDefinition,
|
|
4
|
-
} = require('../app-definition-loader');
|
|
2
|
+
const { loadAppDefinition } = require('../app-definition-loader');
|
|
5
3
|
const { Router } = require('express');
|
|
6
4
|
const { loadRouterFromObject } = require('../backend-utils');
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
const { integrations: integrationClasses } = loadAppDefinition();
|
|
6
|
+
let _handlers;
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
function ensureHandlers() {
|
|
9
|
+
if (!_handlers) {
|
|
10
|
+
_handlers = {};
|
|
11
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
//todo: this should be in a use case class
|
|
14
|
+
for (const IntegrationClass of integrationClasses) {
|
|
15
|
+
const router = Router();
|
|
16
|
+
const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
router.use(basePath, routeDef(IntegrationClass));
|
|
21
|
-
console.log(`│ ANY ${basePath}/* (function handler)`);
|
|
22
|
-
} else if (typeof routeDef === 'object') {
|
|
23
|
-
router.use(
|
|
24
|
-
basePath,
|
|
25
|
-
loadRouterFromObject(IntegrationClass, routeDef)
|
|
18
|
+
console.log(
|
|
19
|
+
`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`
|
|
26
20
|
);
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
|
|
22
|
+
for (const routeDef of IntegrationClass.Definition.routes) {
|
|
23
|
+
if (typeof routeDef === 'function') {
|
|
24
|
+
router.use(basePath, routeDef(IntegrationClass));
|
|
25
|
+
console.log(`│ ANY ${basePath}/* (function handler)`);
|
|
26
|
+
} else if (typeof routeDef === 'object') {
|
|
27
|
+
router.use(
|
|
28
|
+
basePath,
|
|
29
|
+
loadRouterFromObject(IntegrationClass, routeDef)
|
|
30
|
+
);
|
|
31
|
+
const method = (routeDef.method || 'ANY').toUpperCase();
|
|
32
|
+
const fullPath = `${basePath}${routeDef.path}`;
|
|
33
|
+
console.log(`│ ${method} ${fullPath}`);
|
|
34
|
+
} else if (routeDef instanceof express.Router) {
|
|
35
|
+
router.use(basePath, routeDef);
|
|
36
|
+
console.log(`│ ANY ${basePath}/* (express router)`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
console.log('│');
|
|
40
|
+
|
|
41
|
+
_handlers[`${IntegrationClass.Definition.name}`] = {
|
|
42
|
+
handler: createAppHandler(
|
|
43
|
+
`HTTP Event: ${IntegrationClass.Definition.name}`,
|
|
44
|
+
router
|
|
45
|
+
),
|
|
46
|
+
};
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
handlers[`${IntegrationClass.Definition.name}`] = {
|
|
38
|
-
handler: createAppHandler(
|
|
39
|
-
`HTTP Event: ${IntegrationClass.Definition.name}`,
|
|
40
|
-
router
|
|
41
|
-
),
|
|
42
|
-
};
|
|
49
|
+
return _handlers;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
module.exports = {
|
|
52
|
+
module.exports = {
|
|
53
|
+
get handlers() {
|
|
54
|
+
return ensureHandlers();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -1,67 +1,88 @@
|
|
|
1
1
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
2
2
|
const { loadAppDefinition } = require('../app-definition-loader');
|
|
3
3
|
const { Router } = require('express');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
IntegrationEventDispatcher,
|
|
6
|
+
} = require('../integration-event-dispatcher');
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
const { integrations: integrationClasses } = loadAppDefinition();
|
|
8
|
+
let _handlers;
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
function ensureHandlers() {
|
|
11
|
+
if (!_handlers) {
|
|
12
|
+
_handlers = {};
|
|
13
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
for (const IntegrationClass of integrationClasses) {
|
|
16
|
+
const webhookConfig = IntegrationClass.Definition.webhooks;
|
|
17
|
+
|
|
18
|
+
// Skip if webhooks not enabled
|
|
19
|
+
if (
|
|
20
|
+
!webhookConfig ||
|
|
21
|
+
(typeof webhookConfig === 'object' && !webhookConfig.enabled)
|
|
22
|
+
) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
const router = Router();
|
|
27
|
+
const basePath = `/api/${IntegrationClass.Definition.name}-integration/webhooks`;
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
console.log(
|
|
30
|
+
`\n│ Configuring webhook routes for ${IntegrationClass.Definition.name}:`
|
|
31
|
+
);
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
// General webhook route (no integration ID)
|
|
34
|
+
router.post(basePath, async (req, res, next) => {
|
|
35
|
+
try {
|
|
36
|
+
const integrationInstance = new IntegrationClass();
|
|
37
|
+
const dispatcher = new IntegrationEventDispatcher(
|
|
38
|
+
integrationInstance
|
|
39
|
+
);
|
|
40
|
+
await dispatcher.dispatchHttp({
|
|
41
|
+
event: 'WEBHOOK_RECEIVED',
|
|
42
|
+
req,
|
|
43
|
+
res,
|
|
44
|
+
next,
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
next(error);
|
|
48
|
+
}
|
|
32
49
|
});
|
|
33
|
-
|
|
34
|
-
next(error);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
console.log(`│ POST ${basePath}`);
|
|
50
|
+
console.log(`│ POST ${basePath}`);
|
|
38
51
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
// Integration-specific webhook route (with integration ID)
|
|
53
|
+
router.post(`${basePath}/:integrationId`, async (req, res, next) => {
|
|
54
|
+
try {
|
|
55
|
+
const integrationInstance = new IntegrationClass();
|
|
56
|
+
const dispatcher = new IntegrationEventDispatcher(
|
|
57
|
+
integrationInstance
|
|
58
|
+
);
|
|
59
|
+
await dispatcher.dispatchHttp({
|
|
60
|
+
event: 'WEBHOOK_RECEIVED',
|
|
61
|
+
req,
|
|
62
|
+
res,
|
|
63
|
+
next,
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
next(error);
|
|
67
|
+
}
|
|
49
68
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
console.log(`│ POST ${basePath}/:integrationId`);
|
|
55
|
-
console.log('│');
|
|
69
|
+
console.log(`│ POST ${basePath}/:integrationId`);
|
|
70
|
+
console.log('│');
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
_handlers[`${IntegrationClass.Definition.name}Webhook`] = {
|
|
73
|
+
handler: createAppHandler(
|
|
74
|
+
`HTTP Event: ${IntegrationClass.Definition.name} Webhook`,
|
|
75
|
+
router,
|
|
76
|
+
false // shouldUseDatabase = false
|
|
77
|
+
),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return _handlers;
|
|
64
82
|
}
|
|
65
83
|
|
|
66
|
-
module.exports = {
|
|
67
|
-
|
|
84
|
+
module.exports = {
|
|
85
|
+
get handlers() {
|
|
86
|
+
return ensureHandlers();
|
|
87
|
+
},
|
|
88
|
+
};
|