@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.
Files changed (235) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +450 -0
  95. package/handlers/routers/auth.js +23 -2
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +39 -249
  100. package/handlers/routers/integration-defined-routers.js +4 -4
  101. package/handlers/routers/integration-webhook-routers.js +17 -7
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/websocket.js +9 -3
  106. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  107. package/handlers/workers/db-migration.js +74 -35
  108. package/index.js +50 -16
  109. package/infrastructure/scheduler/index.js +15 -11
  110. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  111. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  112. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  113. package/integrations/index.js +4 -0
  114. package/integrations/integration-base.js +9 -6
  115. package/integrations/integration-router.js +1828 -157
  116. package/integrations/options.js +47 -20
  117. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  118. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  119. package/integrations/repositories/integration-repository-factory.js +6 -6
  120. package/integrations/repositories/integration-repository-interface.js +27 -9
  121. package/integrations/repositories/integration-repository-postgres.js +3 -3
  122. package/integrations/repositories/process-repository-factory.js +4 -7
  123. package/integrations/repositories/process-repository-interface.js +9 -5
  124. package/integrations/repositories/process-repository-mongo.js +7 -3
  125. package/integrations/repositories/process-repository-postgres.js +2 -2
  126. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  127. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  128. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  129. package/integrations/use-cases/create-integration.js +36 -32
  130. package/integrations/use-cases/create-process.js +20 -9
  131. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  132. package/integrations/use-cases/execute-proxy-request.js +564 -0
  133. package/integrations/use-cases/get-integration-for-user.js +25 -10
  134. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  135. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  136. package/integrations/use-cases/get-possible-integrations.js +1 -1
  137. package/integrations/use-cases/get-process.js +5 -6
  138. package/integrations/use-cases/index.js +3 -1
  139. package/integrations/use-cases/load-integration-context.js +1 -5
  140. package/integrations/use-cases/update-process-state.js +10 -8
  141. package/integrations/utils/map-integration-dto.js +9 -3
  142. package/jest-setup.js +1 -1
  143. package/jest-teardown.js +1 -1
  144. package/lambda/TimeoutCatcher.js +32 -30
  145. package/lambda/index.js +1 -1
  146. package/logs/index.js +3 -3
  147. package/modules/domain/entities/AuthorizationSession.js +131 -0
  148. package/modules/domain/entities/index.js +10 -0
  149. package/modules/module.js +4 -9
  150. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  151. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  152. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  153. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  154. package/modules/repositories/module-repository-documentdb.js +89 -41
  155. package/modules/repositories/module-repository-factory.js +4 -6
  156. package/modules/repositories/module-repository-interface.js +13 -0
  157. package/modules/repositories/module-repository-mongo.js +40 -5
  158. package/modules/repositories/module-repository-postgres.js +42 -5
  159. package/modules/repositories/module-repository.js +28 -0
  160. package/modules/requester/api-key.js +0 -2
  161. package/modules/requester/basic.js +0 -1
  162. package/modules/requester/oauth-2.js +3 -14
  163. package/modules/test/mock-api/definition.js +15 -10
  164. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  165. package/modules/tests/doubles/test-module-factory.js +10 -4
  166. package/modules/tests/doubles/test-module-repository.js +1 -1
  167. package/modules/use-cases/delete-module-entity.js +23 -0
  168. package/modules/use-cases/get-authorization-requirements.js +110 -0
  169. package/modules/use-cases/get-entities-for-user.js +1 -1
  170. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  171. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  172. package/modules/use-cases/get-module.js +12 -9
  173. package/modules/use-cases/process-authorization-callback.js +12 -6
  174. package/modules/use-cases/process-authorization-step.js +160 -0
  175. package/modules/use-cases/refresh-entity-options.js +8 -6
  176. package/modules/use-cases/start-authorization-session.js +86 -0
  177. package/modules/use-cases/test-module-auth.js +8 -6
  178. package/modules/use-cases/update-module-entity.js +27 -0
  179. package/modules/utils/map-module-dto.js +2 -2
  180. package/openapi/openapi-spec-generator.js +370 -0
  181. package/openapi/openapi-v1.yaml +1360 -0
  182. package/openapi/openapi-v2.yaml +1535 -0
  183. package/package.json +7 -9
  184. package/prisma-mongodb/schema.prisma +87 -0
  185. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  186. package/prisma-postgresql/schema.prisma +85 -0
  187. package/providers/index.js +13 -0
  188. package/providers/resolve-provider.js +93 -0
  189. package/queues/index.js +19 -0
  190. package/queues/providers/index.js +7 -0
  191. package/queues/providers/netlify-background-provider.js +76 -0
  192. package/queues/providers/qstash-queue-provider.js +122 -0
  193. package/queues/queue-client-interface.js +55 -0
  194. package/queues/queue-provider-factory.js +93 -0
  195. package/queues/queue-provider.js +46 -0
  196. package/queues/queuer-util.js +33 -25
  197. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  198. package/syncs/repositories/sync-repository-factory.js +4 -4
  199. package/syncs/repositories/sync-repository-interface.js +6 -2
  200. package/syncs/repositories/sync-repository-postgres.js +19 -13
  201. package/syncs/sync.js +104 -100
  202. package/token/repositories/token-repository-factory.js +4 -6
  203. package/types/assertions/index.d.ts +72 -72
  204. package/types/core/index.d.ts +98 -53
  205. package/types/errors/index.d.ts +61 -54
  206. package/types/eslint-config/index.d.ts +40 -40
  207. package/types/lambda/index.d.ts +26 -26
  208. package/types/logs/index.d.ts +4 -4
  209. package/types/prettier-config/index.d.ts +5 -5
  210. package/types/test-environment/index.d.ts +14 -14
  211. package/types/tsconfig.json +95 -93
  212. package/user/repositories/user-repository-factory.js +4 -4
  213. package/user/tests/doubles/test-user-repository.js +5 -2
  214. package/user/use-cases/authenticate-user.js +2 -4
  215. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  216. package/user/use-cases/create-individual-user.js +8 -7
  217. package/user/use-cases/create-organization-user.js +1 -1
  218. package/user/use-cases/create-token-for-user-id.js +1 -1
  219. package/user/use-cases/delete-user.js +47 -0
  220. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  221. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  222. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  223. package/user/use-cases/login-user.js +8 -8
  224. package/user/user.js +18 -4
  225. package/utils/backend-path.js +2 -2
  226. package/utils/index.js +4 -1
  227. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  228. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  229. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  230. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  231. package/websocket/repositories/websocket-connection-repository.js +26 -20
  232. package/websocket/websocket-message-sender-interface.js +38 -0
  233. package/database/adapters/lambda-invoker.js +0 -97
  234. package/database/repositories/migration-status-repository-s3.js +0 -137
  235. 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({ prismaClient: prisma });
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 = getModulesDefinitionFromIntegrationClasses(integrationClasses);
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('Failed to load integrations for health check:', error.message);
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
- // Helper to detect VPC configuration
91
- const detectVpcConfiguration = async () => {
92
- const results = {
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
- // Check if we're in a VPC by looking for VPC-specific environment
102
- // Lambda in VPC has specific network interface configuration
103
- const dns = require('dns').promises;
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 start = Date.now();
191
- const { KMS_KEY_ARN } = process.env;
192
- if (!KMS_KEY_ARN) {
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
- 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
- };
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 = await checkEncryptionHealthUseCase.execute();
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 } = await checkExternalApisHealthUseCase.execute();
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('External APIs check completed:', response.checks.externalApis);
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('Integrations check completed:', response.checks.integrations);
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(`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`);
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 { IntegrationEventDispatcher } = require('../integration-event-dispatcher');
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 (!webhookConfig || (typeof webhookConfig === 'object' && !webhookConfig.enabled)) {
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(`\n│ Configuring webhook routes for ${IntegrationClass.Definition.name}:`);
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(integrationInstance);
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(integrationInstance);
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 // shouldUseDatabase = 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 { createWebsocketConnectionRepository } = require('../../database/websocket-connection-repository-factory');
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(connectionId);
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(connectionId);
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 = (this.moduleFactory && this.moduleFactory.moduleDefinitions)
9
- ? this.moduleFactory.moduleDefinitions
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(def => def.moduleName || def.name || def.label || 'Unknown')
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 IntegrationClass.Definition?.name || IntegrationClass.name || 'Unknown';
27
+ return (
28
+ IntegrationClass.Definition?.name ||
29
+ IntegrationClass.name ||
30
+ 'Unknown'
31
+ );
25
32
  } catch {
26
33
  return 'Unknown';
27
34
  }