@friggframework/core 2.0.0-next.6 → 2.0.0-next.61
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/CLAUDE.md +694 -0
- package/README.md +959 -50
- package/application/commands/README.md +451 -0
- package/application/commands/credential-commands.js +245 -0
- package/application/commands/entity-commands.js +336 -0
- package/application/commands/integration-commands.js +210 -0
- package/application/commands/user-commands.js +283 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +8 -21
- package/core/create-handler.js +14 -7
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +269 -0
- package/credential/repositories/credential-repository-postgres.js +291 -0
- package/credential/repositories/credential-repository.js +302 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +154 -0
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +268 -0
- package/database/encryption/field-encryption-service.js +226 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/prisma-encryption-extension.js +222 -0
- package/database/index.js +61 -21
- package/database/models/WebsocketConnection.js +16 -10
- package/database/models/readme.md +1 -0
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +134 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/health-check-repository.js +108 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- 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 +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -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 +477 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/encrypt/test-encrypt.js +0 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- package/errors/index.js +2 -0
- 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 +22903 -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 +360 -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 +25077 -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 +343 -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/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +56 -0
- package/handlers/backend-utils.js +186 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +15 -0
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +326 -0
- package/handlers/routers/health.js +516 -0
- package/handlers/routers/integration-defined-routers.js +45 -0
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/routers/user.js +63 -0
- package/handlers/routers/websocket.js +57 -0
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +352 -0
- package/handlers/workers/integration-defined-workers.js +27 -0
- package/index.js +77 -22
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +326 -55
- package/integrations/integration-router.js +374 -179
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-documentdb.js +210 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +127 -0
- package/integrations/repositories/integration-repository-mongo.js +303 -0
- package/integrations/repositories/integration-repository-postgres.js +352 -0
- package/integrations/repositories/process-repository-documentdb.js +243 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +90 -0
- package/integrations/repositories/process-repository-mongo.js +190 -0
- package/integrations/repositories/process-repository-postgres.js +217 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +105 -0
- package/integrations/tests/doubles/test-integration-repository.js +99 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/create-process.js +128 -0
- package/integrations/use-cases/delete-integration-for-user.js +101 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +87 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +201 -0
- package/integrations/use-cases/update-process-state.js +119 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/logs/logger.js +0 -4
- package/{module-plugin → modules}/entity.js +1 -1
- package/{module-plugin → modules}/index.js +0 -8
- package/modules/module-factory.js +56 -0
- package/modules/module.js +221 -0
- package/modules/repositories/module-repository-documentdb.js +307 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +377 -0
- package/modules/repositories/module-repository-postgres.js +426 -0
- package/modules/repositories/module-repository.js +316 -0
- package/modules/requester/api-key.js +52 -0
- package/{module-plugin → modules}/requester/requester.js +1 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +31 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +133 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +82 -50
- package/prisma-mongodb/schema.prisma +360 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +343 -0
- package/queues/queuer-util.js +27 -22
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/core/index.d.ts +2 -2
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +5 -59
- package/types/syncs/index.d.ts +0 -2
- package/user/repositories/user-repository-documentdb.js +441 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +201 -0
- package/user/repositories/user-repository-mongo.js +308 -0
- package/user/repositories/user-repository-postgres.js +360 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/mongo.js +0 -45
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- 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/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
class CheckEncryptionHealthUseCase {
|
|
2
|
+
constructor({ testEncryptionUseCase }) {
|
|
3
|
+
this.testEncryptionUseCase = testEncryptionUseCase;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async execute() {
|
|
7
|
+
const config = this._getEncryptionConfiguration();
|
|
8
|
+
|
|
9
|
+
if (config.isBypassed || config.mode === 'none') {
|
|
10
|
+
const testResult = config.isBypassed
|
|
11
|
+
? 'Encryption bypassed for this stage'
|
|
12
|
+
: 'No encryption keys configured';
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
status: 'disabled',
|
|
16
|
+
mode: config.mode,
|
|
17
|
+
bypassed: config.isBypassed,
|
|
18
|
+
stage: config.stage,
|
|
19
|
+
testResult,
|
|
20
|
+
encryptionWorks: false,
|
|
21
|
+
debug: {
|
|
22
|
+
hasKMS: config.hasKMS,
|
|
23
|
+
hasAES: config.hasAES,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const testResults = await this.testEncryptionUseCase.execute();
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
...testResults,
|
|
33
|
+
mode: config.mode,
|
|
34
|
+
bypassed: config.isBypassed,
|
|
35
|
+
stage: config.stage,
|
|
36
|
+
debug: {
|
|
37
|
+
hasKMS: config.hasKMS,
|
|
38
|
+
hasAES: config.hasAES,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
status: 'unhealthy',
|
|
44
|
+
mode: config.mode,
|
|
45
|
+
bypassed: config.isBypassed,
|
|
46
|
+
stage: config.stage,
|
|
47
|
+
testResult: `Encryption test failed: ${error.message}`,
|
|
48
|
+
encryptionWorks: false,
|
|
49
|
+
debug: {
|
|
50
|
+
hasKMS: config.hasKMS,
|
|
51
|
+
hasAES: config.hasAES,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_getEncryptionConfiguration() {
|
|
58
|
+
const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } =
|
|
59
|
+
process.env;
|
|
60
|
+
|
|
61
|
+
const defaultBypassStages = ['dev', 'test', 'local'];
|
|
62
|
+
const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
|
|
63
|
+
const bypassStages = useEnv
|
|
64
|
+
? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
|
|
65
|
+
: defaultBypassStages;
|
|
66
|
+
|
|
67
|
+
const isBypassed = bypassStages.includes(STAGE);
|
|
68
|
+
const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
|
|
69
|
+
const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '';
|
|
70
|
+
// Prefer KMS over AES when both are configured (KMS is more secure)
|
|
71
|
+
const mode = hasKMS ? 'kms' : hasAES ? 'aes' : 'none';
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
stage: STAGE || null,
|
|
75
|
+
isBypassed,
|
|
76
|
+
hasAES,
|
|
77
|
+
hasKMS,
|
|
78
|
+
mode,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { CheckEncryptionHealthUseCase };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get Database State Via Worker Use Case
|
|
3
|
+
*
|
|
4
|
+
* Domain logic for getting database state by invoking the worker Lambda.
|
|
5
|
+
* This use case delegates to the worker Lambda which has Prisma CLI installed,
|
|
6
|
+
* keeping the router Lambda lightweight.
|
|
7
|
+
*
|
|
8
|
+
* Architecture: Hexagonal/Clean
|
|
9
|
+
* - Use Case (Domain Layer)
|
|
10
|
+
* - Depends on LambdaInvoker (Infrastructure abstraction)
|
|
11
|
+
* - Called by Router (Adapter Layer)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Domain Use Case: Get database state by invoking worker Lambda
|
|
16
|
+
*
|
|
17
|
+
* This use case delegates database state checking to the worker Lambda,
|
|
18
|
+
* which has Prisma CLI installed. Keeps the router Lambda lightweight.
|
|
19
|
+
*/
|
|
20
|
+
class GetDatabaseStateViaWorkerUseCase {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Object} dependencies
|
|
23
|
+
* @param {LambdaInvoker} dependencies.lambdaInvoker - Lambda invocation adapter
|
|
24
|
+
* @param {string} dependencies.workerFunctionName - Worker Lambda function name
|
|
25
|
+
*/
|
|
26
|
+
constructor({ lambdaInvoker, workerFunctionName }) {
|
|
27
|
+
if (!lambdaInvoker) {
|
|
28
|
+
throw new Error('lambdaInvoker dependency is required');
|
|
29
|
+
}
|
|
30
|
+
if (!workerFunctionName) {
|
|
31
|
+
throw new Error('workerFunctionName is required');
|
|
32
|
+
}
|
|
33
|
+
this.lambdaInvoker = lambdaInvoker;
|
|
34
|
+
this.workerFunctionName = workerFunctionName;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute database state check via worker Lambda
|
|
39
|
+
*
|
|
40
|
+
* @param {string} stage - Deployment stage (prod, dev, etc)
|
|
41
|
+
* @returns {Promise<Object>} Database state result
|
|
42
|
+
*/
|
|
43
|
+
async execute(stage = 'production') {
|
|
44
|
+
const dbType = process.env.DB_TYPE || 'postgresql';
|
|
45
|
+
|
|
46
|
+
console.log(`Invoking worker Lambda to check database state: ${this.workerFunctionName}`);
|
|
47
|
+
|
|
48
|
+
// Invoke worker Lambda with checkStatus action
|
|
49
|
+
const result = await this.lambdaInvoker.invoke(this.workerFunctionName, {
|
|
50
|
+
action: 'checkStatus',
|
|
51
|
+
dbType,
|
|
52
|
+
stage,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { GetDatabaseStateViaWorkerUseCase };
|
|
60
|
+
|
|
61
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get Migration Status Use Case
|
|
3
|
+
*
|
|
4
|
+
* Retrieves the status of a database migration by process ID.
|
|
5
|
+
* Formats the Process record for migration-specific response.
|
|
6
|
+
*
|
|
7
|
+
* This use case follows the Frigg hexagonal architecture pattern where:
|
|
8
|
+
* - Routers (adapters) call use cases
|
|
9
|
+
* - Use cases contain business logic and formatting
|
|
10
|
+
* - Use cases call repositories for data access
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class GetMigrationStatusUseCase {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} dependencies
|
|
16
|
+
* @param {Object} dependencies.migrationStatusRepository - Repository for migration status (S3)
|
|
17
|
+
*/
|
|
18
|
+
constructor({ migrationStatusRepository }) {
|
|
19
|
+
if (!migrationStatusRepository) {
|
|
20
|
+
throw new Error('migrationStatusRepository dependency is required');
|
|
21
|
+
}
|
|
22
|
+
this.migrationStatusRepository = migrationStatusRepository;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Execute get migration status
|
|
27
|
+
*
|
|
28
|
+
* @param {string} migrationId - Migration ID to retrieve
|
|
29
|
+
* @param {string} [stage] - Deployment stage (defaults to env.STAGE)
|
|
30
|
+
* @returns {Promise<Object>} Migration status from S3
|
|
31
|
+
* @throws {NotFoundError} If migration not found
|
|
32
|
+
* @throws {ValidationError} If migrationId is invalid
|
|
33
|
+
*/
|
|
34
|
+
async execute(migrationId, stage = null) {
|
|
35
|
+
// Validation
|
|
36
|
+
this._validateParams(migrationId);
|
|
37
|
+
|
|
38
|
+
const effectiveStage = stage || process.env.STAGE || 'production';
|
|
39
|
+
|
|
40
|
+
// Get migration status from S3
|
|
41
|
+
try {
|
|
42
|
+
const migrationStatus = await this.migrationStatusRepository.get(migrationId, effectiveStage);
|
|
43
|
+
return migrationStatus;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error.message.includes('not found')) {
|
|
46
|
+
throw new NotFoundError(`Migration not found: ${migrationId}`);
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate parameters
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
_validateParams(migrationId) {
|
|
57
|
+
if (!migrationId) {
|
|
58
|
+
throw new ValidationError('migrationId is required');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof migrationId !== 'string') {
|
|
62
|
+
throw new ValidationError('migrationId must be a string');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Custom error for validation failures
|
|
69
|
+
*/
|
|
70
|
+
class ValidationError extends Error {
|
|
71
|
+
constructor(message) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = 'ValidationError';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Custom error for not found resources
|
|
79
|
+
*/
|
|
80
|
+
class NotFoundError extends Error {
|
|
81
|
+
constructor(message) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = 'NotFoundError';
|
|
84
|
+
this.statusCode = 404;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
GetMigrationStatusUseCase,
|
|
90
|
+
ValidationError,
|
|
91
|
+
NotFoundError,
|
|
92
|
+
};
|
|
93
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Database Migration Use Case
|
|
3
|
+
*
|
|
4
|
+
* Business logic for running Prisma database migrations.
|
|
5
|
+
* Orchestrates Prisma client generation and migration execution.
|
|
6
|
+
*
|
|
7
|
+
* This use case follows the Frigg hexagonal architecture pattern where:
|
|
8
|
+
* - Handlers (adapters) call use cases
|
|
9
|
+
* - Use cases contain business logic and orchestration
|
|
10
|
+
* - Use cases call repositories/utilities for data access
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class RunDatabaseMigrationUseCase {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} dependencies
|
|
16
|
+
* @param {Object} dependencies.prismaRunner - Prisma runner utilities
|
|
17
|
+
*/
|
|
18
|
+
constructor({ prismaRunner }) {
|
|
19
|
+
if (!prismaRunner) {
|
|
20
|
+
throw new Error('prismaRunner dependency is required');
|
|
21
|
+
}
|
|
22
|
+
this.prismaRunner = prismaRunner;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Execute database migration
|
|
27
|
+
*
|
|
28
|
+
* @param {Object} params
|
|
29
|
+
* @param {string} params.dbType - Database type ('postgresql', 'mongodb', or 'documentdb')
|
|
30
|
+
* @param {string} params.stage - Deployment stage (determines migration command)
|
|
31
|
+
* @param {boolean} [params.verbose=false] - Enable verbose output
|
|
32
|
+
* @returns {Promise<Object>} Migration result { success, dbType, stage, command, message }
|
|
33
|
+
* @throws {MigrationError} If migration fails
|
|
34
|
+
* @throws {ValidationError} If parameters are invalid
|
|
35
|
+
*/
|
|
36
|
+
async execute({ dbType, stage, verbose = false }) {
|
|
37
|
+
// Validation
|
|
38
|
+
this._validateParams({ dbType, stage });
|
|
39
|
+
|
|
40
|
+
// Step 1: Generate Prisma client
|
|
41
|
+
const generateResult = await this.prismaRunner.runPrismaGenerate(dbType, verbose);
|
|
42
|
+
|
|
43
|
+
if (!generateResult.success) {
|
|
44
|
+
throw new MigrationError(
|
|
45
|
+
`Failed to generate Prisma client: ${generateResult.error || 'Unknown error'}`,
|
|
46
|
+
{ dbType, stage, step: 'generate', output: generateResult.output }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Step 2: Run migrations based on database type
|
|
51
|
+
let migrationResult;
|
|
52
|
+
let migrationCommand;
|
|
53
|
+
|
|
54
|
+
if (dbType === 'postgresql') {
|
|
55
|
+
migrationCommand = this.prismaRunner.getMigrationCommand(stage);
|
|
56
|
+
migrationResult = await this.prismaRunner.runPrismaMigrate(migrationCommand, verbose);
|
|
57
|
+
|
|
58
|
+
if (!migrationResult.success) {
|
|
59
|
+
throw new MigrationError(
|
|
60
|
+
`PostgreSQL migration failed: ${migrationResult.error || 'Unknown error'}`,
|
|
61
|
+
{ dbType, stage, command: migrationCommand, step: 'migrate', output: migrationResult.output }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} else if (dbType === 'mongodb' || dbType === 'documentdb') {
|
|
65
|
+
migrationCommand = 'db push';
|
|
66
|
+
// Use non-interactive mode for automated/Lambda environments
|
|
67
|
+
migrationResult = await this.prismaRunner.runPrismaDbPush(verbose, true);
|
|
68
|
+
|
|
69
|
+
if (!migrationResult.success) {
|
|
70
|
+
throw new MigrationError(
|
|
71
|
+
`Mongo-compatible push failed: ${migrationResult.error || 'Unknown error'}`,
|
|
72
|
+
{ dbType, stage, command: migrationCommand, step: 'push', output: migrationResult.output }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
throw new ValidationError(
|
|
77
|
+
`Unsupported database type: ${dbType}. Must be 'postgresql', 'mongodb', or 'documentdb'.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Return success result
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
dbType,
|
|
85
|
+
stage,
|
|
86
|
+
command: migrationCommand,
|
|
87
|
+
message: 'Database migration completed successfully',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate execution parameters
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
_validateParams({ dbType, stage }) {
|
|
96
|
+
if (!dbType) {
|
|
97
|
+
throw new ValidationError('dbType is required');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (typeof dbType !== 'string') {
|
|
101
|
+
throw new ValidationError('dbType must be a string');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!stage) {
|
|
105
|
+
throw new ValidationError('stage is required');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof stage !== 'string') {
|
|
109
|
+
throw new ValidationError('stage must be a string');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Custom error for migration failures
|
|
116
|
+
*/
|
|
117
|
+
class MigrationError extends Error {
|
|
118
|
+
constructor(message, context = {}) {
|
|
119
|
+
super(message);
|
|
120
|
+
this.name = 'MigrationError';
|
|
121
|
+
this.context = context;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Custom error for validation failures
|
|
127
|
+
*/
|
|
128
|
+
class ValidationError extends Error {
|
|
129
|
+
constructor(message) {
|
|
130
|
+
super(message);
|
|
131
|
+
this.name = 'ValidationError';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
RunDatabaseMigrationUseCase,
|
|
137
|
+
MigrationError,
|
|
138
|
+
ValidationError,
|
|
139
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use Case for testing encryption functionality.
|
|
3
|
+
* Contains business logic for verifying that encryption and decryption work correctly.
|
|
4
|
+
*
|
|
5
|
+
* Follows DDD/Hexagonal Architecture:
|
|
6
|
+
* - Application Layer (this use case)
|
|
7
|
+
* - Depends on Infrastructure Layer (HealthCheckRepository)
|
|
8
|
+
*/
|
|
9
|
+
class TestEncryptionUseCase {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Object} params
|
|
12
|
+
* @param {import('../health-check-repository-interface').HealthCheckRepositoryInterface} params.healthCheckRepository
|
|
13
|
+
*/
|
|
14
|
+
constructor({ healthCheckRepository }) {
|
|
15
|
+
this.repository = healthCheckRepository;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute encryption test
|
|
20
|
+
* Orchestrates the full encryption test workflow using Prisma
|
|
21
|
+
* @returns {Promise<Object>} Test results with status and details
|
|
22
|
+
*/
|
|
23
|
+
async execute() {
|
|
24
|
+
const testData = {
|
|
25
|
+
testSecret: 'This is a secret value that should be encrypted',
|
|
26
|
+
normalField: 'This is a normal field that should not be encrypted',
|
|
27
|
+
nestedSecret: {
|
|
28
|
+
value: 'This is a nested secret that should be encrypted',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const credentialData = this._mapTestDataToCredential(testData);
|
|
33
|
+
|
|
34
|
+
const credential = await this._withTimeout(
|
|
35
|
+
this.repository.createCredential(credentialData),
|
|
36
|
+
5000,
|
|
37
|
+
'Save operation timed out'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const retrievedCredential = await this._withTimeout(
|
|
42
|
+
this.repository.findCredentialById(credential.id),
|
|
43
|
+
5000,
|
|
44
|
+
'Find operation timed out'
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const retrievedTestData =
|
|
48
|
+
this._mapCredentialToTestData(retrievedCredential);
|
|
49
|
+
const decryptionWorks = this._verifyDecryption(
|
|
50
|
+
retrievedTestData,
|
|
51
|
+
testData
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const rawCredential = await this._withTimeout(
|
|
55
|
+
this.repository.getRawCredentialById(credential.id),
|
|
56
|
+
5000,
|
|
57
|
+
'Database verification timed out'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const rawTestData = this._mapRawCredentialToTestData(rawCredential);
|
|
61
|
+
const encryptionResults = this._verifyEncryptionInDatabase(
|
|
62
|
+
rawTestData,
|
|
63
|
+
testData
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return this._evaluateEncryptionResults(
|
|
67
|
+
decryptionWorks,
|
|
68
|
+
encryptionResults
|
|
69
|
+
);
|
|
70
|
+
} finally {
|
|
71
|
+
await this._withTimeout(
|
|
72
|
+
this.repository.deleteCredential(credential.id),
|
|
73
|
+
5000,
|
|
74
|
+
'Delete operation timed out'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Map test data format to Credential model format
|
|
81
|
+
* @param {Object} testData - Test data with testSecret, normalField, nestedSecret
|
|
82
|
+
* @returns {Object} Credential data structure
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
_mapTestDataToCredential(testData) {
|
|
86
|
+
// Note: Using camelCase for Prisma compatibility (both MongoDB and PostgreSQL)
|
|
87
|
+
// Changed from snake_case (user_id, entity_id) to camelCase (userId, externalId)
|
|
88
|
+
return {
|
|
89
|
+
externalId: 'test-encryption-entity',
|
|
90
|
+
data: {
|
|
91
|
+
access_token: testData.testSecret, // Encrypted field
|
|
92
|
+
refresh_token: testData.nestedSecret?.value, // Encrypted field
|
|
93
|
+
domain: testData.normalField, // Not encrypted
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Map Credential model format to test data format
|
|
100
|
+
* @param {Object} credential - Credential from database
|
|
101
|
+
* @returns {Object} Test data format
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
_mapCredentialToTestData(credential) {
|
|
105
|
+
if (!credential) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
id: credential.id,
|
|
111
|
+
testSecret: credential.data.access_token,
|
|
112
|
+
normalField: credential.data.domain,
|
|
113
|
+
nestedSecret: {
|
|
114
|
+
value: credential.data.refresh_token,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Map raw Credential data to test data format
|
|
121
|
+
* @param {Object} rawCredential - Raw credential from database
|
|
122
|
+
* @returns {Object} Test data format with raw encrypted values
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
_mapRawCredentialToTestData(rawCredential) {
|
|
126
|
+
if (!rawCredential) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
testSecret: rawCredential.data?.access_token,
|
|
132
|
+
normalField: rawCredential.data?.domain,
|
|
133
|
+
nestedSecret: {
|
|
134
|
+
value: rawCredential.data?.refresh_token,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Verify that a document was decrypted correctly
|
|
141
|
+
* @param {Object} retrievedDoc - Document retrieved from database
|
|
142
|
+
* @param {Object} originalData - Original unencrypted data
|
|
143
|
+
* @returns {boolean} True if decryption worked correctly
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
_verifyDecryption(retrievedDoc, originalData) {
|
|
147
|
+
return (
|
|
148
|
+
retrievedDoc &&
|
|
149
|
+
retrievedDoc.testSecret === originalData.testSecret &&
|
|
150
|
+
retrievedDoc.normalField === originalData.normalField &&
|
|
151
|
+
retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Verify that data was encrypted in the database
|
|
157
|
+
* Business rule: Encrypted fields should contain ':' and differ from original
|
|
158
|
+
* @param {Object} rawDoc - Raw document from database
|
|
159
|
+
* @param {Object} originalData - Original unencrypted data
|
|
160
|
+
* @returns {Object} Encryption verification results
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
_verifyEncryptionInDatabase(rawDoc, originalData) {
|
|
164
|
+
const secretIsEncrypted =
|
|
165
|
+
rawDoc &&
|
|
166
|
+
typeof rawDoc.testSecret === 'string' &&
|
|
167
|
+
rawDoc.testSecret.includes(':') &&
|
|
168
|
+
rawDoc.testSecret !== originalData.testSecret;
|
|
169
|
+
|
|
170
|
+
const nestedIsEncrypted =
|
|
171
|
+
rawDoc?.nestedSecret?.value &&
|
|
172
|
+
typeof rawDoc.nestedSecret.value === 'string' &&
|
|
173
|
+
rawDoc.nestedSecret.value.includes(':') &&
|
|
174
|
+
rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
|
|
175
|
+
|
|
176
|
+
const normalNotEncrypted =
|
|
177
|
+
rawDoc && rawDoc.normalField === originalData.normalField;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
secretIsEncrypted,
|
|
181
|
+
nestedIsEncrypted,
|
|
182
|
+
normalNotEncrypted,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Evaluate encryption test results
|
|
188
|
+
* Business logic for determining if encryption is healthy
|
|
189
|
+
* @param {boolean} decryptionWorks - Whether decryption succeeded
|
|
190
|
+
* @param {Object} encryptionResults - Encryption verification results
|
|
191
|
+
* @returns {Object} Test status and result message
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
_evaluateEncryptionResults(decryptionWorks, encryptionResults) {
|
|
195
|
+
const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } =
|
|
196
|
+
encryptionResults;
|
|
197
|
+
|
|
198
|
+
if (
|
|
199
|
+
decryptionWorks &&
|
|
200
|
+
secretIsEncrypted &&
|
|
201
|
+
nestedIsEncrypted &&
|
|
202
|
+
normalNotEncrypted
|
|
203
|
+
) {
|
|
204
|
+
return {
|
|
205
|
+
status: 'enabled',
|
|
206
|
+
testResult:
|
|
207
|
+
'Encryption and decryption verified successfully',
|
|
208
|
+
encryptionWorks: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (decryptionWorks && (!secretIsEncrypted || !nestedIsEncrypted)) {
|
|
213
|
+
return {
|
|
214
|
+
status: 'unhealthy',
|
|
215
|
+
testResult: 'Fields are not being encrypted in database',
|
|
216
|
+
encryptionWorks: false,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (decryptionWorks && !normalNotEncrypted) {
|
|
221
|
+
return {
|
|
222
|
+
status: 'unhealthy',
|
|
223
|
+
testResult: 'Normal fields are being incorrectly encrypted',
|
|
224
|
+
encryptionWorks: false,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
status: 'unhealthy',
|
|
230
|
+
testResult: 'Decryption failed or data mismatch',
|
|
231
|
+
encryptionWorks: false,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Execute promise with timeout
|
|
237
|
+
* @param {Promise} promise - Promise to execute
|
|
238
|
+
* @param {number} ms - Timeout in milliseconds
|
|
239
|
+
* @param {string} errorMessage - Error message for timeout
|
|
240
|
+
* @returns {Promise} Promise that rejects on timeout
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
_withTimeout(promise, ms, errorMessage) {
|
|
244
|
+
return Promise.race([
|
|
245
|
+
promise,
|
|
246
|
+
new Promise((_, reject) =>
|
|
247
|
+
setTimeout(() => reject(new Error(errorMessage)), ms)
|
|
248
|
+
),
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = { TestEncryptionUseCase };
|