@friggframework/core 2.0.0-next.45 → 2.0.0-next.46

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 (163) hide show
  1. package/README.md +28 -0
  2. package/application/commands/integration-commands.js +19 -0
  3. package/core/Worker.js +8 -21
  4. package/credential/repositories/credential-repository-mongo.js +14 -8
  5. package/credential/repositories/credential-repository-postgres.js +14 -8
  6. package/credential/repositories/credential-repository.js +3 -8
  7. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  8. package/database/adapters/lambda-invoker.js +97 -0
  9. package/database/config.js +11 -2
  10. package/database/models/WebsocketConnection.js +11 -10
  11. package/database/prisma.js +63 -3
  12. package/database/repositories/health-check-repository-mongodb.js +3 -0
  13. package/database/repositories/migration-status-repository-s3.js +137 -0
  14. package/database/use-cases/check-database-state-use-case.js +81 -0
  15. package/database/use-cases/check-encryption-health-use-case.js +3 -2
  16. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  17. package/database/use-cases/get-migration-status-use-case.js +93 -0
  18. package/database/use-cases/run-database-migration-use-case.js +137 -0
  19. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  20. package/database/utils/mongodb-collection-utils.js +91 -0
  21. package/database/utils/mongodb-schema-init.js +106 -0
  22. package/database/utils/prisma-runner.js +400 -0
  23. package/database/utils/prisma-schema-parser.js +182 -0
  24. package/encrypt/Cryptor.js +14 -16
  25. package/generated/prisma-mongodb/client.d.ts +1 -0
  26. package/generated/prisma-mongodb/client.js +4 -0
  27. package/generated/prisma-mongodb/default.d.ts +1 -0
  28. package/generated/prisma-mongodb/default.js +4 -0
  29. package/generated/prisma-mongodb/edge.d.ts +1 -0
  30. package/generated/prisma-mongodb/edge.js +334 -0
  31. package/generated/prisma-mongodb/index-browser.js +316 -0
  32. package/generated/prisma-mongodb/index.d.ts +22897 -0
  33. package/generated/prisma-mongodb/index.js +359 -0
  34. package/generated/prisma-mongodb/package.json +183 -0
  35. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  36. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  37. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  38. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  39. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  40. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  41. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  42. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  43. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  44. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  45. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  46. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  47. package/generated/prisma-mongodb/schema.prisma +362 -0
  48. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  49. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  50. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  51. package/generated/prisma-mongodb/wasm.js +341 -0
  52. package/generated/prisma-postgresql/client.d.ts +1 -0
  53. package/generated/prisma-postgresql/client.js +4 -0
  54. package/generated/prisma-postgresql/default.d.ts +1 -0
  55. package/generated/prisma-postgresql/default.js +4 -0
  56. package/generated/prisma-postgresql/edge.d.ts +1 -0
  57. package/generated/prisma-postgresql/edge.js +356 -0
  58. package/generated/prisma-postgresql/index-browser.js +338 -0
  59. package/generated/prisma-postgresql/index.d.ts +25071 -0
  60. package/generated/prisma-postgresql/index.js +381 -0
  61. package/generated/prisma-postgresql/package.json +183 -0
  62. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  63. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  64. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  65. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  66. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  67. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  68. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  69. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  70. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  71. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  72. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  73. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  74. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  75. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  76. package/generated/prisma-postgresql/schema.prisma +345 -0
  77. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  78. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  79. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  80. package/generated/prisma-postgresql/wasm.js +363 -0
  81. package/handlers/database-migration-handler.js +227 -0
  82. package/handlers/routers/auth.js +1 -1
  83. package/handlers/routers/db-migration.handler.js +29 -0
  84. package/handlers/routers/db-migration.js +256 -0
  85. package/handlers/routers/health.js +41 -6
  86. package/handlers/routers/integration-webhook-routers.js +2 -2
  87. package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
  88. package/handlers/workers/db-migration.js +352 -0
  89. package/index.js +12 -0
  90. package/integrations/integration-router.js +60 -70
  91. package/integrations/repositories/integration-repository-interface.js +12 -0
  92. package/integrations/repositories/integration-repository-mongo.js +32 -0
  93. package/integrations/repositories/integration-repository-postgres.js +33 -0
  94. package/integrations/repositories/process-repository-postgres.js +2 -2
  95. package/integrations/tests/doubles/test-integration-repository.js +2 -2
  96. package/logs/logger.js +0 -4
  97. package/modules/entity.js +0 -1
  98. package/modules/repositories/module-repository-mongo.js +3 -12
  99. package/modules/repositories/module-repository-postgres.js +0 -11
  100. package/modules/repositories/module-repository.js +1 -12
  101. package/modules/use-cases/get-entity-options-by-id.js +1 -1
  102. package/modules/use-cases/get-module.js +1 -2
  103. package/modules/use-cases/refresh-entity-options.js +1 -1
  104. package/modules/use-cases/test-module-auth.js +1 -1
  105. package/package.json +82 -66
  106. package/prisma-mongodb/schema.prisma +21 -21
  107. package/prisma-postgresql/schema.prisma +15 -15
  108. package/queues/queuer-util.js +24 -21
  109. package/types/core/index.d.ts +2 -2
  110. package/types/module-plugin/index.d.ts +0 -2
  111. package/user/use-cases/authenticate-user.js +127 -0
  112. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  113. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  114. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  115. package/user/user.js +16 -0
  116. package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
  117. package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
  118. package/websocket/repositories/websocket-connection-repository.js +11 -10
  119. package/application/commands/integration-commands.test.js +0 -123
  120. package/database/encryption/encryption-integration.test.js +0 -553
  121. package/database/encryption/encryption-schema-registry.test.js +0 -392
  122. package/database/encryption/field-encryption-service.test.js +0 -525
  123. package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
  124. package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
  125. package/database/encryption/postgres-relation-decryption.test.js +0 -245
  126. package/database/encryption/prisma-encryption-extension.test.js +0 -439
  127. package/errors/base-error.test.js +0 -32
  128. package/errors/fetch-error.test.js +0 -79
  129. package/errors/halt-error.test.js +0 -11
  130. package/errors/validation-errors.test.js +0 -120
  131. package/handlers/auth-flow.integration.test.js +0 -147
  132. package/handlers/integration-event-dispatcher.test.js +0 -209
  133. package/handlers/routers/health.test.js +0 -210
  134. package/handlers/routers/integration-webhook-routers.test.js +0 -126
  135. package/handlers/webhook-flow.integration.test.js +0 -356
  136. package/handlers/workers/integration-defined-workers.test.js +0 -184
  137. package/integrations/tests/use-cases/create-integration.test.js +0 -131
  138. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
  139. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
  140. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
  141. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
  142. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
  143. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  144. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  145. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  146. package/integrations/tests/use-cases/update-integration.test.js +0 -141
  147. package/integrations/use-cases/create-process.test.js +0 -178
  148. package/integrations/use-cases/get-process.test.js +0 -190
  149. package/integrations/use-cases/load-integration-context-full.test.js +0 -329
  150. package/integrations/use-cases/load-integration-context.test.js +0 -114
  151. package/integrations/use-cases/update-process-metrics.test.js +0 -308
  152. package/integrations/use-cases/update-process-state.test.js +0 -256
  153. package/lambda/TimeoutCatcher.test.js +0 -68
  154. package/logs/logger.test.js +0 -76
  155. package/modules/module-hydration.test.js +0 -205
  156. package/modules/requester/requester.test.js +0 -28
  157. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  158. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  159. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  160. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  161. package/user/tests/use-cases/login-user.test.js +0 -220
  162. package/user/tests/user-password-encryption-isolation.test.js +0 -237
  163. package/user/tests/user-password-hashing.test.js +0 -235
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Database Migration Lambda Handler
3
+ *
4
+ * Lambda function that runs Prisma database migrations from within the VPC,
5
+ * enabling CI/CD pipelines to migrate databases without requiring public access.
6
+ *
7
+ * This handler uses the prisma-runner utilities from @friggframework/core,
8
+ * ensuring consistency with the `frigg db:setup` command.
9
+ *
10
+ * Environment Variables Required:
11
+ * - DATABASE_URL: PostgreSQL connection string (automatically set from Secrets Manager)
12
+ * - DB_TYPE: Database type ('postgresql' or 'mongodb')
13
+ * - STAGE: Deployment stage (determines migration command: 'dev' or 'deploy')
14
+ *
15
+ * Invocation:
16
+ * aws lambda invoke \
17
+ * --function-name my-app-production-dbMigrate \
18
+ * --region us-east-1 \
19
+ * response.json
20
+ *
21
+ * Success Response:
22
+ * {
23
+ * "statusCode": 200,
24
+ * "body": {
25
+ * "success": true,
26
+ * "message": "Database migration completed successfully",
27
+ * "dbType": "postgresql",
28
+ * "stage": "production",
29
+ * "migrationCommand": "deploy"
30
+ * }
31
+ * }
32
+ *
33
+ * Error Response:
34
+ * {
35
+ * "statusCode": 500,
36
+ * "body": {
37
+ * "success": false,
38
+ * "error": "Migration failed: ...",
39
+ * "stack": "Error: ..."
40
+ * }
41
+ * }
42
+ */
43
+
44
+ const {
45
+ RunDatabaseMigrationUseCase,
46
+ MigrationError,
47
+ ValidationError,
48
+ } = require('../../database/use-cases/run-database-migration-use-case');
49
+ const {
50
+ CheckDatabaseStateUseCase,
51
+ } = require('../../database/use-cases/check-database-state-use-case');
52
+ const {
53
+ MigrationStatusRepositoryS3,
54
+ } = require('../../database/repositories/migration-status-repository-s3');
55
+
56
+ // Inject prisma-runner as dependency
57
+ const prismaRunner = require('../../database/utils/prisma-runner');
58
+
59
+ // Use S3 repository for migration status tracking (no User table dependency)
60
+ const bucketName = process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
61
+ const migrationStatusRepository = new MigrationStatusRepositoryS3(bucketName);
62
+
63
+ /**
64
+ * Sanitizes error messages to prevent credential leaks
65
+ * @param {string} errorMessage - Error message that might contain credentials
66
+ * @returns {string} Sanitized error message
67
+ */
68
+ function sanitizeError(errorMessage) {
69
+ if (!errorMessage) return 'Unknown error';
70
+
71
+ return String(errorMessage)
72
+ // Remove PostgreSQL connection strings
73
+ .replace(/postgresql:\/\/[^@\s]+@[^\s/]+/gi, 'postgresql://***:***@***')
74
+ // Remove MongoDB connection strings
75
+ .replace(/mongodb(\+srv)?:\/\/[^@\s]+@[^\s/]+/gi, 'mongodb$1://***:***@***')
76
+ // Remove password parameters
77
+ .replace(/password[=:]\s*[^\s,;)]+/gi, 'password=***')
78
+ // Remove API keys
79
+ .replace(/apikey[=:]\s*[^\s,;)]+/gi, 'apikey=***')
80
+ .replace(/api[_-]?key[=:]\s*[^\s,;)]+/gi, 'api_key=***')
81
+ // Remove tokens
82
+ .replace(/token[=:]\s*[^\s,;)]+/gi, 'token=***')
83
+ .replace(/bearer\s+[^\s,;)]+/gi, 'bearer ***');
84
+ }
85
+
86
+ /**
87
+ * Sanitizes DATABASE_URL for safe logging
88
+ * @param {string} url - Database URL
89
+ * @returns {string} Sanitized URL
90
+ */
91
+ function sanitizeDatabaseUrl(url) {
92
+ if (!url) return '';
93
+
94
+ // Replace credentials in connection string
95
+ return url.replace(/(:\/\/)([^:]+):([^@]+)@/, '$1***:***@');
96
+ }
97
+
98
+ /**
99
+ * Extract migration parameters from SQS event or direct invocation
100
+ * @param {Object} event - Lambda event (SQS or direct)
101
+ * @returns {Object} Extracted parameters { migrationId, dbType, stage }
102
+ */
103
+ function extractMigrationParams(event) {
104
+ let migrationId = null;
105
+ let stage = null;
106
+
107
+ // Migration infrastructure is PostgreSQL-only, so hardcode dbType
108
+ const dbType = 'postgresql';
109
+
110
+ // Check if this is an SQS event
111
+ if (event.Records && event.Records.length > 0) {
112
+ // SQS event - extract from message body
113
+ const message = JSON.parse(event.Records[0].body);
114
+ migrationId = message.migrationId;
115
+ stage = message.stage;
116
+
117
+ console.log('SQS event detected');
118
+ console.log(` Migration ID: ${migrationId}`);
119
+ console.log(` DB Type: ${dbType} (hardcoded - PostgreSQL-only)`);
120
+ console.log(` Stage: ${stage}`);
121
+ } else {
122
+ // Direct invocation - use event properties or environment variables
123
+ migrationId = event.migrationId || null;
124
+ stage = event.stage || process.env.STAGE || 'production';
125
+
126
+ console.log('Direct invocation detected');
127
+ if (migrationId) {
128
+ console.log(` Migration ID: ${migrationId}`);
129
+ }
130
+ console.log(` DB Type: ${dbType} (hardcoded - PostgreSQL-only)`);
131
+ console.log(` Stage: ${stage}`);
132
+ }
133
+
134
+ return { migrationId, dbType, stage };
135
+ }
136
+
137
+ /**
138
+ * Lambda handler entry point
139
+ * @param {Object} event - Lambda event (SQS message or direct invocation)
140
+ * @param {Object} context - Lambda context (contains AWS request ID, timeout info)
141
+ * @returns {Promise<Object>} Response with statusCode and body
142
+ */
143
+ exports.handler = async (event, context) => {
144
+ console.log('========================================');
145
+ console.log('Database Migration Lambda Started');
146
+ console.log('========================================');
147
+ console.log('Event:', JSON.stringify(event, null, 2));
148
+ console.log('Context:', JSON.stringify({
149
+ requestId: context.requestId,
150
+ functionName: context.functionName,
151
+ remainingTimeInMillis: context.getRemainingTimeInMillis(),
152
+ }, null, 2));
153
+
154
+ // Extract migration parameters from event
155
+ const { migrationId, dbType, stage } = extractMigrationParams(event);
156
+
157
+ // Check for action parameter (direct invocation for status checks)
158
+ const action = event.action || 'migrate'; // Default to migration
159
+
160
+ // Handle checkStatus action
161
+ if (action === 'checkStatus') {
162
+ console.log(`\n========================================`);
163
+ console.log(`Action: checkStatus (dbType=${dbType}, stage=${stage})`);
164
+ console.log(`========================================`);
165
+
166
+ try {
167
+ const checkDbStateUseCase = new CheckDatabaseStateUseCase({ prismaRunner });
168
+ const status = await checkDbStateUseCase.execute(dbType, stage);
169
+
170
+ console.log('✓ Database state check completed');
171
+ console.log(` Up to date: ${status.upToDate}`);
172
+ console.log(` Pending migrations: ${status.pendingMigrations}`);
173
+
174
+ return {
175
+ statusCode: 200,
176
+ body: status,
177
+ };
178
+ } catch (error) {
179
+ console.error('❌ Database state check failed:', error.message);
180
+ return {
181
+ statusCode: 500,
182
+ body: {
183
+ success: false,
184
+ error: sanitizeError(error.message),
185
+ upToDate: false,
186
+ },
187
+ };
188
+ }
189
+ }
190
+
191
+ // Otherwise, handle migration (existing code)
192
+ console.log(`\n========================================`);
193
+ console.log(`Action: migrate (migrationId=${migrationId || 'new'})`);
194
+ console.log(`========================================`);
195
+
196
+ // Get environment variables
197
+ const databaseUrl = process.env.DATABASE_URL;
198
+
199
+ try {
200
+ // Validate DATABASE_URL is set
201
+ if (!databaseUrl) {
202
+ const error = 'DATABASE_URL environment variable is not set';
203
+ console.error('❌ Validation failed:', error);
204
+ return {
205
+ statusCode: 500,
206
+ body: JSON.stringify({
207
+ success: false,
208
+ error,
209
+ }),
210
+ };
211
+ }
212
+
213
+ console.log('✓ Environment validated');
214
+ console.log(` Database Type: ${dbType}`);
215
+ console.log(` Stage: ${stage}`);
216
+ console.log(` Database URL: ${sanitizeDatabaseUrl(databaseUrl)}`);
217
+
218
+ // Update migration status to RUNNING (if migrationId provided)
219
+ if (migrationId) {
220
+ console.log(`\n✓ Updating migration status to RUNNING: ${migrationId}`);
221
+ await migrationStatusRepository.update({
222
+ migrationId,
223
+ stage,
224
+ state: 'RUNNING',
225
+ progress: 10,
226
+ startedAt: new Date().toISOString(),
227
+ });
228
+ }
229
+
230
+ // Create use case with dependencies (Dependency Injection)
231
+ const runDatabaseMigrationUseCase = new RunDatabaseMigrationUseCase({
232
+ prismaRunner,
233
+ });
234
+
235
+ console.log('\n========================================');
236
+ console.log('Executing Database Migration');
237
+ console.log('========================================');
238
+
239
+ // Execute use case (business logic layer)
240
+ const result = await runDatabaseMigrationUseCase.execute({
241
+ dbType,
242
+ stage,
243
+ verbose: true, // Enable verbose output for Lambda CloudWatch logs
244
+ });
245
+
246
+ console.log('✓ Database migration completed successfully');
247
+ console.log('\n========================================');
248
+ console.log('Migration Summary');
249
+ console.log('========================================');
250
+ console.log(` Status: Success`);
251
+ console.log(` Database: ${result.dbType}`);
252
+ console.log(` Stage: ${result.stage}`);
253
+ console.log(` Command: ${result.command}`);
254
+ console.log('========================================');
255
+
256
+ // Update migration status to COMPLETED (if migrationId provided)
257
+ if (migrationId) {
258
+ console.log(`\n✓ Updating migration status to COMPLETED: ${migrationId}`);
259
+ await migrationStatusRepository.update({
260
+ migrationId,
261
+ stage,
262
+ state: 'COMPLETED',
263
+ progress: 100,
264
+ completedAt: new Date().toISOString(),
265
+ migrationCommand: result.command,
266
+ });
267
+ }
268
+
269
+ // Return success response (adapter layer - HTTP mapping)
270
+ const responseBody = {
271
+ success: true,
272
+ message: result.message,
273
+ dbType: result.dbType,
274
+ stage: result.stage,
275
+ migrationCommand: result.command,
276
+ timestamp: new Date().toISOString(),
277
+ };
278
+
279
+ if (migrationId) {
280
+ responseBody.migrationId = migrationId;
281
+ }
282
+
283
+ return {
284
+ statusCode: 200,
285
+ body: JSON.stringify(responseBody),
286
+ };
287
+
288
+ } catch (error) {
289
+ console.error('\n========================================');
290
+ console.error('Migration Failed');
291
+ console.error('========================================');
292
+ console.error('Error:', error.name, error.message);
293
+
294
+ // Log full stack trace to CloudWatch (only visible to developers)
295
+ if (error.stack) {
296
+ console.error('Stack:', error.stack);
297
+ }
298
+
299
+ // Log context if available (from MigrationError)
300
+ if (error.context) {
301
+ console.error('Context:', JSON.stringify(error.context, null, 2));
302
+ }
303
+
304
+ // Map domain errors to HTTP status codes (adapter layer)
305
+ let statusCode = 500;
306
+ let errorMessage = error.message || 'Unknown error occurred';
307
+
308
+ if (error instanceof ValidationError) {
309
+ statusCode = 400; // Bad Request for validation errors
310
+ } else if (error instanceof MigrationError) {
311
+ statusCode = 500; // Internal Server Error for migration failures
312
+ }
313
+
314
+ // Sanitize error message before returning
315
+ const sanitizedError = sanitizeError(errorMessage);
316
+
317
+ // Update migration status to FAILED (if migrationId provided)
318
+ if (migrationId) {
319
+ try {
320
+ console.log(`\n✓ Updating migration status to FAILED: ${migrationId}`);
321
+ await migrationStatusRepository.update({
322
+ migrationId,
323
+ stage,
324
+ state: 'FAILED',
325
+ progress: 0,
326
+ error: sanitizedError,
327
+ failedAt: new Date().toISOString(),
328
+ });
329
+ } catch (updateError) {
330
+ console.error('Failed to update migration status:', updateError.message);
331
+ // Continue - don't let status update failure block error response
332
+ }
333
+ }
334
+
335
+ const errorBody = {
336
+ success: false,
337
+ error: sanitizedError,
338
+ errorType: error.name || 'Error',
339
+ // Only include stack traces in development environments
340
+ ...(stage === 'dev' || stage === 'local' || stage === 'test' ? { stack: error.stack } : {}),
341
+ };
342
+
343
+ if (migrationId) {
344
+ errorBody.migrationId = migrationId;
345
+ }
346
+
347
+ return {
348
+ statusCode,
349
+ body: JSON.stringify(errorBody),
350
+ };
351
+ }
352
+ };
package/index.js CHANGED
@@ -33,6 +33,15 @@ const {
33
33
  UserRepositoryMongo,
34
34
  UserRepositoryPostgres,
35
35
  } = require('./user/repositories/user-repository-factory');
36
+ const {
37
+ GetUserFromXFriggHeaders,
38
+ } = require('./user/use-cases/get-user-from-x-frigg-headers');
39
+ const {
40
+ GetUserFromAdopterJwt,
41
+ } = require('./user/use-cases/get-user-from-adopter-jwt');
42
+ const {
43
+ AuthenticateUser,
44
+ } = require('./user/use-cases/authenticate-user');
36
45
 
37
46
  const {
38
47
  CredentialRepository,
@@ -123,6 +132,9 @@ module.exports = {
123
132
  createUserRepository,
124
133
  UserRepositoryMongo,
125
134
  UserRepositoryPostgres,
135
+ GetUserFromXFriggHeaders,
136
+ GetUserFromAdopterJwt,
137
+ AuthenticateUser,
126
138
  CredentialRepository,
127
139
  ModuleRepository,
128
140
  IntegrationMappingRepository,
@@ -56,6 +56,18 @@ const {
56
56
  const {
57
57
  GetUserFromBearerToken,
58
58
  } = require('../user/use-cases/get-user-from-bearer-token');
59
+ const {
60
+ GetUserFromXFriggHeaders,
61
+ } = require('../user/use-cases/get-user-from-x-frigg-headers');
62
+ const {
63
+ GetUserFromAdopterJwt,
64
+ } = require('../user/use-cases/get-user-from-adopter-jwt');
65
+ const {
66
+ AuthenticateWithSharedSecret,
67
+ } = require('../user/use-cases/authenticate-with-shared-secret');
68
+ const {
69
+ AuthenticateUser,
70
+ } = require('../user/use-cases/authenticate-user');
59
71
  const {
60
72
  ProcessAuthorizationCallback,
61
73
  } = require('../modules/use-cases/process-authorization-callback');
@@ -73,6 +85,26 @@ function createIntegrationRouter() {
73
85
  userConfig,
74
86
  });
75
87
 
88
+ const getUserFromXFriggHeaders = new GetUserFromXFriggHeaders({
89
+ userRepository,
90
+ userConfig,
91
+ });
92
+
93
+ const getUserFromAdopterJwt = new GetUserFromAdopterJwt({
94
+ userRepository,
95
+ userConfig,
96
+ });
97
+
98
+ const authenticateWithSharedSecret = new AuthenticateWithSharedSecret();
99
+
100
+ const authenticateUser = new AuthenticateUser({
101
+ getUserFromBearerToken,
102
+ getUserFromXFriggHeaders,
103
+ getUserFromAdopterJwt,
104
+ authenticateWithSharedSecret,
105
+ userConfig,
106
+ });
107
+
76
108
  const moduleFactory = new ModuleFactory({
77
109
  moduleRepository,
78
110
  moduleDefinitions:
@@ -165,7 +197,7 @@ function createIntegrationRouter() {
165
197
 
166
198
  const router = express();
167
199
 
168
- setIntegrationRoutes(router, getUserFromBearerToken, {
200
+ setIntegrationRoutes(router, authenticateUser, {
169
201
  createIntegration,
170
202
  deleteIntegrationForUser,
171
203
  getIntegrationsForUser,
@@ -174,7 +206,7 @@ function createIntegrationRouter() {
174
206
  updateIntegration,
175
207
  getPossibleIntegrations,
176
208
  });
177
- setEntityRoutes(router, getUserFromBearerToken, {
209
+ setEntityRoutes(router, authenticateUser, {
178
210
  getCredentialForUser,
179
211
  getModuleInstanceFromType,
180
212
  getEntityOptionsByType,
@@ -201,10 +233,8 @@ function checkRequiredParams(params, requiredKeys) {
201
233
 
202
234
  if (missingKeys.length > 0) {
203
235
  throw Boom.badRequest(
204
- `Missing Parameter${
205
- missingKeys.length === 1 ? '' : 's'
206
- }: ${missingKeys.join(', ')} ${
207
- missingKeys.length === 1 ? 'is' : 'are'
236
+ `Missing Parameter${missingKeys.length === 1 ? '' : 's'
237
+ }: ${missingKeys.join(', ')} ${missingKeys.length === 1 ? 'is' : 'are'
208
238
  } required.`
209
239
  );
210
240
  }
@@ -214,10 +244,10 @@ function checkRequiredParams(params, requiredKeys) {
214
244
  /**
215
245
  * Sets up integration-related routes on the provided Express router
216
246
  * @param {express.Router} router - Express router instance to add routes to
217
- * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
247
+ * @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
218
248
  * @param {Object} useCases - use cases for integration management
219
249
  */
220
- function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
250
+ function setIntegrationRoutes(router, authenticateUser, useCases) {
221
251
  const {
222
252
  createIntegration,
223
253
  deleteIntegrationForUser,
@@ -229,9 +259,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
229
259
  } = useCases;
230
260
  router.route('/api/integrations').get(
231
261
  catchAsyncError(async (req, res) => {
232
- const user = await getUserFromBearerToken.execute(
233
- req.headers.authorization
234
- );
262
+ const user = await authenticateUser.execute(req);
235
263
  const userId = user.getId();
236
264
  const integrations = await getIntegrationsForUser.execute(userId);
237
265
  const results = {
@@ -248,9 +276,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
248
276
 
249
277
  router.route('/api/integrations').post(
250
278
  catchAsyncError(async (req, res) => {
251
- const user = await getUserFromBearerToken.execute(
252
- req.headers.authorization
253
- );
279
+ const user = await authenticateUser.execute(req);
254
280
  const userId = user.getId();
255
281
  const params = checkRequiredParams(req.body, [
256
282
  'entities',
@@ -271,9 +297,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
271
297
 
272
298
  router.route('/api/integrations/:integrationId').patch(
273
299
  catchAsyncError(async (req, res) => {
274
- const user = await getUserFromBearerToken.execute(
275
- req.headers.authorization
276
- );
300
+ const user = await authenticateUser.execute(req);
277
301
  const userId = user.getId();
278
302
  const params = checkRequiredParams(req.body, ['config']);
279
303
 
@@ -288,9 +312,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
288
312
 
289
313
  router.route('/api/integrations/:integrationId').delete(
290
314
  catchAsyncError(async (req, res) => {
291
- const user = await getUserFromBearerToken.execute(
292
- req.headers.authorization
293
- );
315
+ const user = await authenticateUser.execute(req);
294
316
  const params = checkRequiredParams(req.params, ['integrationId']);
295
317
  await deleteIntegrationForUser.execute(
296
318
  params.integrationId,
@@ -302,9 +324,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
302
324
 
303
325
  router.route('/api/integrations/:integrationId/config/options').get(
304
326
  catchAsyncError(async (req, res) => {
305
- const user = await getUserFromBearerToken.execute(
306
- req.headers.authorization
307
- );
327
+ const user = await authenticateUser.execute(req);
308
328
  const params = checkRequiredParams(req.params, ['integrationId']);
309
329
  const integration = await getIntegrationInstance.execute(
310
330
  params.integrationId,
@@ -318,9 +338,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
318
338
  .route('/api/integrations/:integrationId/config/options/refresh')
319
339
  .post(
320
340
  catchAsyncError(async (req, res) => {
321
- const user = await getUserFromBearerToken.execute(
322
- req.headers.authorization
323
- );
341
+ const user = await authenticateUser.execute(req);
324
342
  const params = checkRequiredParams(req.params, [
325
343
  'integrationId',
326
344
  ]);
@@ -336,9 +354,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
336
354
  );
337
355
  router.route('/api/integrations/:integrationId/actions').all(
338
356
  catchAsyncError(async (req, res) => {
339
- const user = await getUserFromBearerToken.execute(
340
- req.headers.authorization
341
- );
357
+ const user = await authenticateUser.execute(req);
342
358
  const params = checkRequiredParams(req.params, ['integrationId']);
343
359
  const integration = await getIntegrationInstance.execute(
344
360
  params.integrationId,
@@ -352,9 +368,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
352
368
  .route('/api/integrations/:integrationId/actions/:actionId/options')
353
369
  .all(
354
370
  catchAsyncError(async (req, res) => {
355
- const user = await getUserFromBearerToken.execute(
356
- req.headers.authorization
357
- );
371
+ const user = await authenticateUser.execute(req);
358
372
  const params = checkRequiredParams(req.params, [
359
373
  'integrationId',
360
374
  'actionId',
@@ -379,9 +393,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
379
393
  )
380
394
  .post(
381
395
  catchAsyncError(async (req, res) => {
382
- const user = await getUserFromBearerToken.execute(
383
- req.headers.authorization
384
- );
396
+ const user = await authenticateUser.execute(req);
385
397
  const params = checkRequiredParams(req.params, [
386
398
  'integrationId',
387
399
  'actionId',
@@ -402,9 +414,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
402
414
 
403
415
  router.route('/api/integrations/:integrationId/actions/:actionId').post(
404
416
  catchAsyncError(async (req, res) => {
405
- const user = await getUserFromBearerToken.execute(
406
- req.headers.authorization
407
- );
417
+ const user = await authenticateUser.execute(req);
408
418
  const params = checkRequiredParams(req.params, [
409
419
  'integrationId',
410
420
  'actionId',
@@ -419,9 +429,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
419
429
 
420
430
  router.route('/api/integrations/:integrationId').get(
421
431
  catchAsyncError(async (req, res) => {
422
- const user = await getUserFromBearerToken.execute(
423
- req.headers.authorization
424
- );
432
+ const user = await authenticateUser.execute(req);
425
433
 
426
434
  if (!user) {
427
435
  throw Boom.forbidden('User not found');
@@ -446,9 +454,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
446
454
 
447
455
  router.route('/api/integrations/:integrationId/test-auth').get(
448
456
  catchAsyncError(async (req, res) => {
449
- const user = await getUserFromBearerToken.execute(
450
- req.headers.authorization
451
- );
457
+ const user = await authenticateUser.execute(req);
452
458
  const params = checkRequiredParams(req.params, ['integrationId']);
453
459
  const instance = await getIntegrationInstance.execute(
454
460
  params.integrationId,
@@ -478,9 +484,9 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
478
484
  /**
479
485
  * Sets up entity-related routes for the integration router
480
486
  * @param {Object} router - Express router instance
481
- * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
487
+ * @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
482
488
  */
483
- function setEntityRoutes(router, getUserFromBearerToken, useCases) {
489
+ function setEntityRoutes(router, authenticateUser, useCases) {
484
490
  const {
485
491
  getCredentialForUser,
486
492
  getModuleInstanceFromType,
@@ -494,9 +500,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
494
500
 
495
501
  router.route('/api/authorize').get(
496
502
  catchAsyncError(async (req, res) => {
497
- const user = await getUserFromBearerToken.execute(
498
- req.headers.authorization
499
- );
503
+ const user = await authenticateUser.execute(req);
500
504
  const userId = user.getId();
501
505
  const params = checkRequiredParams(req.query, ['entityType']);
502
506
  const module = await getModuleInstanceFromType.execute(
@@ -517,9 +521,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
517
521
 
518
522
  router.route('/api/authorize').post(
519
523
  catchAsyncError(async (req, res) => {
520
- const user = await getUserFromBearerToken.execute(
521
- req.headers.authorization
522
- );
524
+ const user = await authenticateUser.execute(req);
523
525
  const userId = user.getId();
524
526
  const params = checkRequiredParams(req.body, [
525
527
  'entityType',
@@ -538,9 +540,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
538
540
 
539
541
  router.route('/api/entity').post(
540
542
  catchAsyncError(async (req, res) => {
541
- const user = await getUserFromBearerToken.execute(
542
- req.headers.authorization
543
- );
543
+ const user = await authenticateUser.execute(req);
544
544
  const userId = user.getId();
545
545
  const params = checkRequiredParams(req.body, [
546
546
  'entityType',
@@ -575,9 +575,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
575
575
 
576
576
  router.route('/api/entity/options/:credentialId').get(
577
577
  catchAsyncError(async (req, res) => {
578
- const user = await getUserFromBearerToken.execute(
579
- req.headers.authorization
580
- );
578
+ const user = await authenticateUser.execute(req);
581
579
  const userId = user.getId();
582
580
  // TODO May want to pass along the user ID as well so credential ID's can't be fished???
583
581
  // TODO **flagging this for review** -MW
@@ -601,9 +599,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
601
599
 
602
600
  router.route('/api/entities/:entityId/test-auth').get(
603
601
  catchAsyncError(async (req, res) => {
604
- const user = await getUserFromBearerToken.execute(
605
- req.headers.authorization
606
- );
602
+ const user = await authenticateUser.execute(req);
607
603
  const userId = user.getId();
608
604
  const params = checkRequiredParams(req.params, ['entityId']);
609
605
  const testAuthResponse = await testModuleAuth.execute(
@@ -630,9 +626,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
630
626
 
631
627
  router.route('/api/entities/:entityId').get(
632
628
  catchAsyncError(async (req, res) => {
633
- const user = await getUserFromBearerToken.execute(
634
- req.headers.authorization
635
- );
629
+ const user = await authenticateUser.execute(req);
636
630
  const userId = user.getId();
637
631
  const params = checkRequiredParams(req.params, ['entityId']);
638
632
  const module = await getModule.execute(params.entityId, userId);
@@ -643,9 +637,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
643
637
 
644
638
  router.route('/api/entities/:entityId/options').post(
645
639
  catchAsyncError(async (req, res) => {
646
- const user = await getUserFromBearerToken.execute(
647
- req.headers.authorization
648
- );
640
+ const user = await authenticateUser.execute(req);
649
641
  const userId = user.getId();
650
642
  const params = checkRequiredParams(req.params, ['entityId']);
651
643
 
@@ -660,9 +652,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
660
652
 
661
653
  router.route('/api/entities/:entityId/options/refresh').post(
662
654
  catchAsyncError(async (req, res) => {
663
- const user = await getUserFromBearerToken.execute(
664
- req.headers.authorization
665
- );
655
+ const user = await authenticateUser.execute(req);
666
656
  const userId = user.getId();
667
657
  const params = checkRequiredParams(req.params, ['entityId']);
668
658
  const updatedOptions = await refreshEntityOptions.execute(