@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.
- package/README.md +28 -0
- package/application/commands/integration-commands.js +19 -0
- package/core/Worker.js +8 -21
- package/credential/repositories/credential-repository-mongo.js +14 -8
- package/credential/repositories/credential-repository-postgres.js +14 -8
- package/credential/repositories/credential-repository.js +3 -8
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +11 -2
- package/database/models/WebsocketConnection.js +11 -10
- package/database/prisma.js +63 -3
- package/database/repositories/health-check-repository-mongodb.js +3 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +3 -2
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +137 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +91 -0
- package/database/utils/mongodb-schema-init.js +106 -0
- package/database/utils/prisma-runner.js +400 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/encrypt/Cryptor.js +14 -16
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22897 -0
- package/generated/prisma-mongodb/index.js +359 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
- package/generated/prisma-mongodb/runtime/binary.js +289 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +362 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +341 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +356 -0
- package/generated/prisma-postgresql/index-browser.js +338 -0
- package/generated/prisma-postgresql/index.d.ts +25071 -0
- package/generated/prisma-postgresql/index.js +381 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
- package/generated/prisma-postgresql/runtime/binary.js +289 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +345 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +363 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/routers/auth.js +1 -1
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +256 -0
- package/handlers/routers/health.js +41 -6
- package/handlers/routers/integration-webhook-routers.js +2 -2
- package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
- package/handlers/workers/db-migration.js +352 -0
- package/index.js +12 -0
- package/integrations/integration-router.js +60 -70
- package/integrations/repositories/integration-repository-interface.js +12 -0
- package/integrations/repositories/integration-repository-mongo.js +32 -0
- package/integrations/repositories/integration-repository-postgres.js +33 -0
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/test-integration-repository.js +2 -2
- package/logs/logger.js +0 -4
- package/modules/entity.js +0 -1
- package/modules/repositories/module-repository-mongo.js +3 -12
- package/modules/repositories/module-repository-postgres.js +0 -11
- package/modules/repositories/module-repository.js +1 -12
- package/modules/use-cases/get-entity-options-by-id.js +1 -1
- package/modules/use-cases/get-module.js +1 -2
- package/modules/use-cases/refresh-entity-options.js +1 -1
- package/modules/use-cases/test-module-auth.js +1 -1
- package/package.json +82 -66
- package/prisma-mongodb/schema.prisma +21 -21
- package/prisma-postgresql/schema.prisma +15 -15
- package/queues/queuer-util.js +24 -21
- package/types/core/index.d.ts +2 -2
- package/types/module-plugin/index.d.ts +0 -2
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
- package/user/user.js +16 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
- package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
- package/websocket/repositories/websocket-connection-repository.js +11 -10
- package/application/commands/integration-commands.test.js +0 -123
- package/database/encryption/encryption-integration.test.js +0 -553
- package/database/encryption/encryption-schema-registry.test.js +0 -392
- package/database/encryption/field-encryption-service.test.js +0 -525
- package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
- package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
- package/database/encryption/postgres-relation-decryption.test.js +0 -245
- package/database/encryption/prisma-encryption-extension.test.js +0 -439
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/handlers/auth-flow.integration.test.js +0 -147
- package/handlers/integration-event-dispatcher.test.js +0 -209
- package/handlers/routers/health.test.js +0 -210
- package/handlers/routers/integration-webhook-routers.test.js +0 -126
- package/handlers/webhook-flow.integration.test.js +0 -356
- package/handlers/workers/integration-defined-workers.test.js +0 -184
- package/integrations/tests/use-cases/create-integration.test.js +0 -131
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
- package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
- package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
- package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
- package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
- package/integrations/tests/use-cases/update-integration.test.js +0 -141
- package/integrations/use-cases/create-process.test.js +0 -178
- package/integrations/use-cases/get-process.test.js +0 -190
- package/integrations/use-cases/load-integration-context-full.test.js +0 -329
- package/integrations/use-cases/load-integration-context.test.js +0 -114
- package/integrations/use-cases/update-process-metrics.test.js +0 -308
- package/integrations/use-cases/update-process-state.test.js +0 -256
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/modules/module-hydration.test.js +0 -205
- package/modules/requester/requester.test.js +0 -28
- package/user/tests/use-cases/create-individual-user.test.js +0 -24
- package/user/tests/use-cases/create-organization-user.test.js +0 -28
- package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
- package/user/tests/use-cases/login-user.test.js +0 -220
- package/user/tests/user-password-encryption-isolation.test.js +0 -237
- 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,
|
|
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,
|
|
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
|
-
|
|
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/
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
487
|
+
* @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
|
|
482
488
|
*/
|
|
483
|
-
function setEntityRoutes(router,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|