@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.dba001a.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 +450 -0
- package/handlers/routers/auth.js +23 -2
- 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 +39 -249
- package/handlers/routers/integration-defined-routers.js +4 -4
- package/handlers/routers/integration-webhook-routers.js +17 -7
- 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/websocket.js +9 -3
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- 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,7 +29,9 @@ const {
|
|
|
29
29
|
} = require('../use-cases/check-integrations-health-use-case');
|
|
30
30
|
|
|
31
31
|
const router = Router();
|
|
32
|
-
const healthCheckRepository = createHealthCheckRepository({
|
|
32
|
+
const healthCheckRepository = createHealthCheckRepository({
|
|
33
|
+
prismaClient: prisma,
|
|
34
|
+
});
|
|
33
35
|
|
|
34
36
|
// Load integrations and create factories just like auth router does
|
|
35
37
|
// This verifies the system can properly load integrations
|
|
@@ -39,14 +41,18 @@ try {
|
|
|
39
41
|
integrationClasses = appDef.integrations || [];
|
|
40
42
|
|
|
41
43
|
const moduleRepository = createModuleRepository();
|
|
42
|
-
const moduleDefinitions =
|
|
44
|
+
const moduleDefinitions =
|
|
45
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses);
|
|
43
46
|
|
|
44
47
|
moduleFactory = new ModuleFactory({
|
|
45
48
|
moduleRepository,
|
|
46
49
|
moduleDefinitions,
|
|
47
50
|
});
|
|
48
51
|
} catch (error) {
|
|
49
|
-
console.error(
|
|
52
|
+
console.error(
|
|
53
|
+
'Failed to load integrations for health check:',
|
|
54
|
+
error.message
|
|
55
|
+
);
|
|
50
56
|
// Factories will be undefined, health check will report unhealthy
|
|
51
57
|
moduleFactory = undefined;
|
|
52
58
|
integrationClasses = [];
|
|
@@ -87,256 +93,32 @@ const validateApiKey = (req, res, next) => {
|
|
|
87
93
|
|
|
88
94
|
router.use(validateApiKey);
|
|
89
95
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
isInVpc: false,
|
|
94
|
-
hasInternetAccess: false,
|
|
95
|
-
canResolvePublicDns: false,
|
|
96
|
-
canConnectToAws: false,
|
|
97
|
-
vpcEndpoints: [],
|
|
98
|
-
};
|
|
96
|
+
// Provider-specific health checks are loaded via resolveProvider()
|
|
97
|
+
// so core doesn't hardcode any provider package names.
|
|
98
|
+
const { resolveProvider } = require('../../providers/resolve-provider');
|
|
99
99
|
|
|
100
|
+
function getResolvedProvider() {
|
|
100
101
|
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);
|
|
102
|
+
return resolveProvider();
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
183
105
|
}
|
|
106
|
+
}
|
|
184
107
|
|
|
185
|
-
return results;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// KMS decrypt capability check
|
|
189
108
|
const checkKmsDecryptCapability = async () => {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
status: 'skipped',
|
|
195
|
-
reason: 'KMS_KEY_ARN not configured',
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Log environment for debugging
|
|
200
|
-
console.log('KMS Check Debug:', {
|
|
201
|
-
hasKmsKeyArn: !!KMS_KEY_ARN,
|
|
202
|
-
kmsKeyArnPrefix: KMS_KEY_ARN?.substring(0, 30),
|
|
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
|
-
};
|
|
109
|
+
const provider = getResolvedProvider();
|
|
110
|
+
if (!provider?.checkKmsDecryptCapability) {
|
|
111
|
+
return { status: 'skipped', reason: 'Provider does not support KMS health checks' };
|
|
288
112
|
}
|
|
113
|
+
return provider.checkKmsDecryptCapability();
|
|
114
|
+
};
|
|
289
115
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
};
|
|
116
|
+
const detectVpcConfiguration = async () => {
|
|
117
|
+
const provider = getResolvedProvider();
|
|
118
|
+
if (!provider?.detectVpcConfiguration) {
|
|
119
|
+
return { status: 'skipped', reason: 'Provider does not support VPC detection' };
|
|
339
120
|
}
|
|
121
|
+
return provider.detectVpcConfiguration();
|
|
340
122
|
};
|
|
341
123
|
|
|
342
124
|
router.get('/health', async (_req, res) => {
|
|
@@ -430,7 +212,8 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
430
212
|
}
|
|
431
213
|
|
|
432
214
|
try {
|
|
433
|
-
response.checks.encryption =
|
|
215
|
+
response.checks.encryption =
|
|
216
|
+
await checkEncryptionHealthUseCase.execute();
|
|
434
217
|
if (response.checks.encryption.status === 'unhealthy') {
|
|
435
218
|
response.status = 'unhealthy';
|
|
436
219
|
}
|
|
@@ -445,12 +228,16 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
445
228
|
}
|
|
446
229
|
|
|
447
230
|
try {
|
|
448
|
-
const { apiStatuses, allReachable } =
|
|
231
|
+
const { apiStatuses, allReachable } =
|
|
232
|
+
await checkExternalApisHealthUseCase.execute();
|
|
449
233
|
response.checks.externalApis = apiStatuses;
|
|
450
234
|
if (!allReachable) {
|
|
451
235
|
response.status = 'unhealthy';
|
|
452
236
|
}
|
|
453
|
-
console.log(
|
|
237
|
+
console.log(
|
|
238
|
+
'External APIs check completed:',
|
|
239
|
+
response.checks.externalApis
|
|
240
|
+
);
|
|
454
241
|
} catch (error) {
|
|
455
242
|
response.checks.externalApis = {
|
|
456
243
|
status: 'unhealthy',
|
|
@@ -462,7 +249,10 @@ router.get('/health/detailed', async (_req, res) => {
|
|
|
462
249
|
|
|
463
250
|
try {
|
|
464
251
|
response.checks.integrations = checkIntegrationsHealthUseCase.execute();
|
|
465
|
-
console.log(
|
|
252
|
+
console.log(
|
|
253
|
+
'Integrations check completed:',
|
|
254
|
+
response.checks.integrations
|
|
255
|
+
);
|
|
466
256
|
} catch (error) {
|
|
467
257
|
response.checks.integrations = {
|
|
468
258
|
status: 'unhealthy',
|
|
@@ -1,7 +1,5 @@
|
|
|
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
|
|
|
@@ -13,7 +11,9 @@ for (const IntegrationClass of integrationClasses) {
|
|
|
13
11
|
const router = Router();
|
|
14
12
|
const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
|
|
15
13
|
|
|
16
|
-
console.log(
|
|
14
|
+
console.log(
|
|
15
|
+
`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`
|
|
16
|
+
);
|
|
17
17
|
|
|
18
18
|
for (const routeDef of IntegrationClass.Definition.routes) {
|
|
19
19
|
if (typeof routeDef === 'function') {
|
|
@@ -1,7 +1,9 @@
|
|
|
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
8
|
const handlers = {};
|
|
7
9
|
const { integrations: integrationClasses } = loadAppDefinition();
|
|
@@ -10,20 +12,27 @@ for (const IntegrationClass of integrationClasses) {
|
|
|
10
12
|
const webhookConfig = IntegrationClass.Definition.webhooks;
|
|
11
13
|
|
|
12
14
|
// Skip if webhooks not enabled
|
|
13
|
-
if (
|
|
15
|
+
if (
|
|
16
|
+
!webhookConfig ||
|
|
17
|
+
(typeof webhookConfig === 'object' && !webhookConfig.enabled)
|
|
18
|
+
) {
|
|
14
19
|
continue;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
const router = Router();
|
|
18
23
|
const basePath = `/api/${IntegrationClass.Definition.name}-integration/webhooks`;
|
|
19
24
|
|
|
20
|
-
console.log(
|
|
25
|
+
console.log(
|
|
26
|
+
`\n│ Configuring webhook routes for ${IntegrationClass.Definition.name}:`
|
|
27
|
+
);
|
|
21
28
|
|
|
22
29
|
// General webhook route (no integration ID)
|
|
23
30
|
router.post(basePath, async (req, res, next) => {
|
|
24
31
|
try {
|
|
25
32
|
const integrationInstance = new IntegrationClass();
|
|
26
|
-
const dispatcher = new IntegrationEventDispatcher(
|
|
33
|
+
const dispatcher = new IntegrationEventDispatcher(
|
|
34
|
+
integrationInstance
|
|
35
|
+
);
|
|
27
36
|
await dispatcher.dispatchHttp({
|
|
28
37
|
event: 'WEBHOOK_RECEIVED',
|
|
29
38
|
req,
|
|
@@ -40,7 +49,9 @@ for (const IntegrationClass of integrationClasses) {
|
|
|
40
49
|
router.post(`${basePath}/:integrationId`, async (req, res, next) => {
|
|
41
50
|
try {
|
|
42
51
|
const integrationInstance = new IntegrationClass();
|
|
43
|
-
const dispatcher = new IntegrationEventDispatcher(
|
|
52
|
+
const dispatcher = new IntegrationEventDispatcher(
|
|
53
|
+
integrationInstance
|
|
54
|
+
);
|
|
44
55
|
await dispatcher.dispatchHttp({
|
|
45
56
|
event: 'WEBHOOK_RECEIVED',
|
|
46
57
|
req,
|
|
@@ -58,10 +69,9 @@ for (const IntegrationClass of integrationClasses) {
|
|
|
58
69
|
handler: createAppHandler(
|
|
59
70
|
`HTTP Event: ${IntegrationClass.Definition.name} Webhook`,
|
|
60
71
|
router,
|
|
61
|
-
false
|
|
72
|
+
false // shouldUseDatabase = false
|
|
62
73
|
),
|
|
63
74
|
};
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
module.exports = { handlers };
|
|
67
|
-
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const catchAsyncError = require('express-async-handler');
|
|
2
|
+
const {
|
|
3
|
+
GetUserFromBearerToken,
|
|
4
|
+
} = require('../../../user/use-cases/get-user-from-bearer-token');
|
|
5
|
+
const {
|
|
6
|
+
createUserRepository,
|
|
7
|
+
} = require('../../../user/repositories/user-repository-factory');
|
|
8
|
+
const { loadAppDefinition } = require('../../app-definition-loader');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load user from bearer token middleware
|
|
12
|
+
* Uses DDD pattern: Handler → Use Case → Repository
|
|
13
|
+
*/
|
|
14
|
+
module.exports = catchAsyncError(async (req, res, next) => {
|
|
15
|
+
const authorizationHeader = req.headers.authorization;
|
|
16
|
+
|
|
17
|
+
if (authorizationHeader) {
|
|
18
|
+
// Initialize dependencies following DDD pattern
|
|
19
|
+
const { userConfig } = loadAppDefinition();
|
|
20
|
+
const userRepository = createUserRepository({ userConfig });
|
|
21
|
+
const getUserFromBearerToken = new GetUserFromBearerToken({
|
|
22
|
+
userRepository,
|
|
23
|
+
userConfig,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Execute use case to load user
|
|
28
|
+
req.user = await getUserFromBearerToken.execute(
|
|
29
|
+
authorizationHeader
|
|
30
|
+
);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Don't fail - just leave req.user undefined
|
|
33
|
+
// Let requireLoggedInUser middleware handle auth failures
|
|
34
|
+
console.debug('Failed to load user from token:', error.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return next();
|
|
39
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to require admin API key authentication.
|
|
3
|
+
* Checks for x-frigg-admin-api-key header matching ADMIN_API_KEY environment variable.
|
|
4
|
+
* In non-production environments, allows all requests through for easier development.
|
|
5
|
+
*
|
|
6
|
+
* Uses the same header convention as validateAdminApiKey (handlers/middleware/admin-auth.js).
|
|
7
|
+
*
|
|
8
|
+
* @param {import('express').Request} req - Express request object
|
|
9
|
+
* @param {import('express').Response} res - Express response object
|
|
10
|
+
* @param {import('express').NextFunction} next - Express next middleware function
|
|
11
|
+
*/
|
|
12
|
+
const requireAdmin = (req, res, next) => {
|
|
13
|
+
// Allow access in local development (when NODE_ENV is not production)
|
|
14
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
15
|
+
console.log('[requireAdmin] Development mode - bypassing admin auth');
|
|
16
|
+
return next();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const apiKey = req.headers['x-frigg-admin-api-key'];
|
|
20
|
+
|
|
21
|
+
if (!apiKey) {
|
|
22
|
+
console.error('[requireAdmin] Missing x-frigg-admin-api-key header');
|
|
23
|
+
return res.status(401).json({
|
|
24
|
+
status: 'error',
|
|
25
|
+
message: 'Unauthorized - Admin API key required',
|
|
26
|
+
code: 'MISSING_API_KEY',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (apiKey !== process.env.ADMIN_API_KEY) {
|
|
31
|
+
console.error('[requireAdmin] Invalid API key provided');
|
|
32
|
+
return res.status(401).json({
|
|
33
|
+
status: 'error',
|
|
34
|
+
message: 'Unauthorized - Invalid admin API key',
|
|
35
|
+
code: 'INVALID_API_KEY',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log('[requireAdmin] Admin authentication successful');
|
|
40
|
+
next();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
module.exports = { requireAdmin };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Require logged in user middleware
|
|
5
|
+
* Ensures req.user was successfully loaded by loadUser middleware
|
|
6
|
+
*
|
|
7
|
+
* Uses DDD pattern: Middleware checks domain entity existence
|
|
8
|
+
* req.user is populated by loadUser middleware using GetUserFromBearerToken use case
|
|
9
|
+
*/
|
|
10
|
+
const requireLoggedInUser = (req, res, next) => {
|
|
11
|
+
// Check if user was successfully loaded by loadUser middleware
|
|
12
|
+
if (!req.user || !req.user.getId()) {
|
|
13
|
+
throw Boom.unauthorized('Invalid Token');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
next();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = { requireLoggedInUser };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const { createHandler } = require('@friggframework/core');
|
|
2
|
-
const {
|
|
2
|
+
const {
|
|
3
|
+
createWebsocketConnectionRepository,
|
|
4
|
+
} = require('../../database/websocket-connection-repository-factory');
|
|
3
5
|
|
|
4
6
|
const websocketConnectionRepository = createWebsocketConnectionRepository();
|
|
5
7
|
|
|
@@ -10,7 +12,9 @@ const handleWebSocketConnection = async (event, context) => {
|
|
|
10
12
|
// Handle new connection
|
|
11
13
|
try {
|
|
12
14
|
const connectionId = event.requestContext.connectionId;
|
|
13
|
-
await websocketConnectionRepository.createConnection(
|
|
15
|
+
await websocketConnectionRepository.createConnection(
|
|
16
|
+
connectionId
|
|
17
|
+
);
|
|
14
18
|
console.log(`Stored new connection: ${connectionId}`);
|
|
15
19
|
return { statusCode: 200, body: 'Connected.' };
|
|
16
20
|
} catch (error) {
|
|
@@ -22,7 +26,9 @@ const handleWebSocketConnection = async (event, context) => {
|
|
|
22
26
|
// Handle disconnection
|
|
23
27
|
try {
|
|
24
28
|
const connectionId = event.requestContext.connectionId;
|
|
25
|
-
await websocketConnectionRepository.deleteConnection(
|
|
29
|
+
await websocketConnectionRepository.deleteConnection(
|
|
30
|
+
connectionId
|
|
31
|
+
);
|
|
26
32
|
console.log(`Removed connection: ${connectionId}`);
|
|
27
33
|
return { statusCode: 200, body: 'Disconnected.' };
|
|
28
34
|
} catch (error) {
|
|
@@ -5,9 +5,10 @@ class CheckIntegrationsHealthUseCase {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
execute() {
|
|
8
|
-
const moduleDefinitions =
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const moduleDefinitions =
|
|
9
|
+
this.moduleFactory && this.moduleFactory.moduleDefinitions
|
|
10
|
+
? this.moduleFactory.moduleDefinitions
|
|
11
|
+
: [];
|
|
11
12
|
|
|
12
13
|
const integrationClasses = Array.isArray(this.integrationClasses)
|
|
13
14
|
? this.integrationClasses
|
|
@@ -15,13 +16,19 @@ class CheckIntegrationsHealthUseCase {
|
|
|
15
16
|
|
|
16
17
|
// Extract module names from definitions
|
|
17
18
|
const moduleTypes = Array.isArray(moduleDefinitions)
|
|
18
|
-
? moduleDefinitions.map(
|
|
19
|
+
? moduleDefinitions.map(
|
|
20
|
+
(def) => def.moduleName || def.name || def.label || 'Unknown'
|
|
21
|
+
)
|
|
19
22
|
: [];
|
|
20
23
|
|
|
21
24
|
// Extract integration names from classes
|
|
22
|
-
const integrationNames = integrationClasses.map(IntegrationClass => {
|
|
25
|
+
const integrationNames = integrationClasses.map((IntegrationClass) => {
|
|
23
26
|
try {
|
|
24
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
IntegrationClass.Definition?.name ||
|
|
29
|
+
IntegrationClass.name ||
|
|
30
|
+
'Unknown'
|
|
31
|
+
);
|
|
25
32
|
} catch {
|
|
26
33
|
return 'Unknown';
|
|
27
34
|
}
|