@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -67
- package/CLAUDE.md +148 -124
- package/README.md +165 -130
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
- package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
- package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
- package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
- package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
- package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
- package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/admin-script-commands.js +348 -0
- package/application/commands/integration-commands.js +12 -6
- package/application/index.js +5 -3
- package/assertions/get.js +17 -7
- package/associations/association.js +59 -57
- package/core/CLAUDE.md +222 -156
- package/core/Worker.js +33 -12
- package/credential/credential-router.js +262 -0
- package/credential/repositories/credential-repository-documentdb.js +71 -1
- package/credential/repositories/credential-repository-factory.js +4 -6
- package/credential/repositories/credential-repository-interface.js +8 -2
- package/credential/repositories/credential-repository-mongo.js +45 -4
- package/credential/repositories/credential-repository-postgres.js +44 -3
- package/credential/repositories/credential-repository.js +3 -1
- package/credential/use-cases/delete-credential-for-user.js +50 -0
- package/credential/use-cases/get-credential-for-user.js +21 -3
- package/credential/use-cases/list-credentials-for-user.js +31 -0
- package/credential/use-cases/reauthorize-credential.js +103 -0
- package/credential/use-cases/update-authentication-status.js +5 -2
- package/database/MONGODB_TRANSACTION_FIX.md +72 -52
- package/database/config.js +13 -59
- package/database/documentdb-encryption-service.js +29 -13
- package/database/documentdb-utils.js +21 -12
- package/database/encryption/README.md +53 -36
- package/database/encryption/encryption-schema-registry.js +29 -23
- package/database/encryption/field-encryption-service.js +15 -4
- package/database/encryption/logger.js +3 -2
- package/database/encryption/prisma-encryption-extension.js +5 -8
- package/database/prisma.js +22 -14
- package/database/repositories/health-check-repository-factory.js +7 -5
- package/database/repositories/health-check-repository-interface.js +15 -5
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/use-cases/check-database-health-use-case.js +3 -2
- package/database/use-cases/check-database-state-use-case.js +8 -7
- package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
- package/database/use-cases/get-migration-status-use-case.js +4 -2
- package/database/use-cases/run-database-migration-use-case.js +41 -9
- package/database/use-cases/test-encryption-use-case.js +4 -5
- package/database/use-cases/trigger-database-migration-use-case.js +9 -8
- package/database/utils/mongodb-collection-utils.js +11 -3
- package/database/utils/mongodb-schema-init.js +10 -4
- package/database/utils/prisma-runner.js +144 -74
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
- package/encrypt/Cryptor.js +54 -49
- package/encrypt/aes-encryption-key-provider.js +82 -0
- package/encrypt/encryption-key-provider-interface.js +50 -0
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +59 -4
- package/generated/prisma-mongodb/index-browser.js +49 -1
- package/generated/prisma-mongodb/index.d.ts +4023 -61
- package/generated/prisma-mongodb/index.js +59 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +87 -0
- package/generated/prisma-mongodb/wasm.js +59 -4
- package/generated/prisma-postgresql/edge.js +58 -4
- package/generated/prisma-postgresql/index-browser.js +48 -1
- package/generated/prisma-postgresql/index.d.ts +4738 -54
- package/generated/prisma-postgresql/index.js +58 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +85 -0
- package/generated/prisma-postgresql/wasm.js +58 -4
- package/handlers/WEBHOOKS.md +104 -81
- package/handlers/app-definition-loader.js +53 -10
- package/handlers/app-handler-helpers.js +14 -3
- package/handlers/backend-utils.js +0 -1
- package/handlers/database-migration-handler.js +64 -45
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +170 -135
- package/handlers/routers/admin.js +474 -0
- package/handlers/routers/auth.js +51 -9
- package/handlers/routers/db-migration.handler.js +2 -3
- package/handlers/routers/db-migration.js +102 -66
- package/handlers/routers/docs.js +180 -0
- package/handlers/routers/health.js +84 -283
- package/handlers/routers/integration-defined-routers.js +44 -33
- package/handlers/routers/integration-webhook-routers.js +73 -52
- package/handlers/routers/middleware/loadUser.js +39 -0
- package/handlers/routers/middleware/requireAdmin.js +43 -0
- package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
- package/handlers/routers/user.js +30 -14
- package/handlers/routers/websocket.js +18 -4
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- package/handlers/workers/integration-defined-workers.js +31 -20
- package/index.js +50 -16
- package/infrastructure/scheduler/index.js +15 -11
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
- package/integrations/WEBHOOK-QUICKSTART.md +21 -14
- package/integrations/index.js +4 -0
- package/integrations/integration-base.js +9 -6
- package/integrations/integration-router.js +1828 -157
- package/integrations/options.js +47 -20
- package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
- package/integrations/repositories/integration-repository-documentdb.js +53 -24
- package/integrations/repositories/integration-repository-factory.js +6 -6
- package/integrations/repositories/integration-repository-interface.js +27 -9
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +4 -7
- package/integrations/repositories/process-repository-interface.js +9 -5
- package/integrations/repositories/process-repository-mongo.js +7 -3
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +9 -8
- package/integrations/tests/doubles/dummy-integration-class.js +81 -8
- package/integrations/tests/doubles/test-integration-repository.js +51 -11
- package/integrations/use-cases/create-integration.js +36 -32
- package/integrations/use-cases/create-process.js +20 -9
- package/integrations/use-cases/delete-integration-for-user.js +5 -4
- package/integrations/use-cases/execute-proxy-request.js +564 -0
- package/integrations/use-cases/get-integration-for-user.js +25 -10
- package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
- package/integrations/use-cases/get-integrations-for-user.js +4 -1
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +5 -6
- package/integrations/use-cases/index.js +3 -1
- package/integrations/use-cases/load-integration-context.js +1 -5
- package/integrations/use-cases/update-process-state.js +10 -8
- package/integrations/utils/map-integration-dto.js +9 -3
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +32 -30
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/domain/entities/AuthorizationSession.js +131 -0
- package/modules/domain/entities/index.js +10 -0
- package/modules/module.js +4 -9
- package/modules/repositories/authorization-session-repository-factory.js +53 -0
- package/modules/repositories/authorization-session-repository-interface.js +75 -0
- package/modules/repositories/authorization-session-repository-mongo.js +169 -0
- package/modules/repositories/authorization-session-repository-postgres.js +170 -0
- package/modules/repositories/module-repository-documentdb.js +89 -41
- package/modules/repositories/module-repository-factory.js +4 -6
- package/modules/repositories/module-repository-interface.js +13 -0
- package/modules/repositories/module-repository-mongo.js +40 -5
- package/modules/repositories/module-repository-postgres.js +42 -5
- package/modules/repositories/module-repository.js +28 -0
- package/modules/requester/api-key.js +0 -2
- package/modules/requester/basic.js +0 -1
- package/modules/requester/oauth-2.js +3 -14
- package/modules/test/mock-api/definition.js +15 -10
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +10 -4
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/delete-module-entity.js +23 -0
- package/modules/use-cases/get-authorization-requirements.js +110 -0
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +8 -6
- package/modules/use-cases/get-module-entity-by-id.js +16 -0
- package/modules/use-cases/get-module.js +12 -9
- package/modules/use-cases/process-authorization-callback.js +12 -6
- package/modules/use-cases/process-authorization-step.js +160 -0
- package/modules/use-cases/refresh-entity-options.js +8 -6
- package/modules/use-cases/start-authorization-session.js +86 -0
- package/modules/use-cases/test-module-auth.js +8 -6
- package/modules/use-cases/update-module-entity.js +27 -0
- package/modules/utils/map-module-dto.js +2 -2
- package/openapi/openapi-spec-generator.js +370 -0
- package/openapi/openapi-v1.yaml +1360 -0
- package/openapi/openapi-v2.yaml +1535 -0
- package/package.json +7 -9
- package/prisma-mongodb/schema.prisma +87 -0
- package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
- package/prisma-postgresql/schema.prisma +85 -0
- package/providers/index.js +13 -0
- package/providers/resolve-provider.js +93 -0
- package/queues/index.js +19 -0
- package/queues/providers/index.js +7 -0
- package/queues/providers/netlify-background-provider.js +76 -0
- package/queues/providers/qstash-queue-provider.js +122 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-provider-factory.js +93 -0
- package/queues/queue-provider.js +46 -0
- package/queues/queuer-util.js +33 -25
- package/syncs/repositories/sync-repository-documentdb.js +39 -19
- package/syncs/repositories/sync-repository-factory.js +4 -4
- package/syncs/repositories/sync-repository-interface.js +6 -2
- package/syncs/repositories/sync-repository-postgres.js +19 -13
- package/syncs/sync.js +104 -100
- package/token/repositories/token-repository-factory.js +4 -6
- package/types/assertions/index.d.ts +72 -72
- package/types/core/index.d.ts +98 -53
- package/types/errors/index.d.ts +61 -54
- package/types/eslint-config/index.d.ts +40 -40
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/prettier-config/index.d.ts +5 -5
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +95 -93
- package/user/repositories/user-repository-factory.js +4 -4
- package/user/tests/doubles/test-user-repository.js +5 -2
- package/user/use-cases/authenticate-user.js +2 -4
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +8 -7
- package/user/use-cases/create-organization-user.js +1 -1
- package/user/use-cases/create-token-for-user-id.js +1 -1
- package/user/use-cases/delete-user.js +47 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
- package/user/use-cases/get-user-from-bearer-token.js +7 -3
- package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +18 -4
- package/utils/backend-path.js +2 -2
- package/utils/index.js +4 -1
- package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
- package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
- package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
- package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
- package/websocket/repositories/websocket-connection-repository.js +26 -20
- package/websocket/websocket-message-sender-interface.js +38 -0
- package/database/adapters/lambda-invoker.js +0 -97
- package/database/repositories/migration-status-repository-s3.js +0 -137
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Delete Credential For User Use Case
|
|
5
|
+
* Removes a credential after verifying ownership
|
|
6
|
+
*
|
|
7
|
+
* Business Logic:
|
|
8
|
+
* - Verify credential exists
|
|
9
|
+
* - Verify credential belongs to user (authorization)
|
|
10
|
+
* - Delete the credential
|
|
11
|
+
* - Return success status
|
|
12
|
+
*/
|
|
13
|
+
class DeleteCredentialForUser {
|
|
14
|
+
constructor({ credentialRepository }) {
|
|
15
|
+
this.credentialRepository = credentialRepository;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute the use case
|
|
20
|
+
* @param {string} credentialId - Credential ID
|
|
21
|
+
* @param {string} userId - User ID (for ownership verification)
|
|
22
|
+
* @returns {Promise<Object>} Deletion result { deletedCount: number }
|
|
23
|
+
*/
|
|
24
|
+
async execute(credentialId, userId) {
|
|
25
|
+
// Check if credential exists
|
|
26
|
+
const credential = await this.credentialRepository.findCredentialById(
|
|
27
|
+
credentialId
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (!credential) {
|
|
31
|
+
throw Boom.notFound(`Credential ${credentialId} not found`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Verify ownership - compare as strings to handle both MongoDB and PostgreSQL
|
|
35
|
+
if (credential.userId.toString() !== userId.toString()) {
|
|
36
|
+
throw Boom.forbidden(
|
|
37
|
+
'You do not have permission to delete this credential'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Delete the credential
|
|
42
|
+
const result = await this.credentialRepository.deleteCredentialById(
|
|
43
|
+
credentialId
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { DeleteCredentialForUser };
|
|
@@ -1,20 +1,38 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get Credential For User Use Case
|
|
5
|
+
* Retrieves a single credential after verifying ownership
|
|
6
|
+
*
|
|
7
|
+
* Business Logic:
|
|
8
|
+
* - Verify credential exists
|
|
9
|
+
* - Verify credential belongs to user (authorization)
|
|
10
|
+
* - Return credential
|
|
11
|
+
*/
|
|
1
12
|
class GetCredentialForUser {
|
|
2
13
|
constructor({ credentialRepository }) {
|
|
3
14
|
this.credentialRepository = credentialRepository;
|
|
4
15
|
}
|
|
5
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Execute the use case
|
|
19
|
+
* @param {string} credentialId - Credential ID
|
|
20
|
+
* @param {string} userId - User ID (for ownership verification)
|
|
21
|
+
* @returns {Promise<Object>} Credential object
|
|
22
|
+
*/
|
|
6
23
|
async execute(credentialId, userId) {
|
|
7
24
|
const credential = await this.credentialRepository.findCredentialById(
|
|
8
25
|
credentialId
|
|
9
26
|
);
|
|
10
27
|
|
|
11
28
|
if (!credential) {
|
|
12
|
-
throw
|
|
29
|
+
throw Boom.notFound(`Credential ${credentialId} not found`);
|
|
13
30
|
}
|
|
14
31
|
|
|
32
|
+
// Verify ownership - compare as strings to handle both MongoDB and PostgreSQL
|
|
15
33
|
if (credential.userId.toString() !== userId.toString()) {
|
|
16
|
-
throw
|
|
17
|
-
|
|
34
|
+
throw Boom.forbidden(
|
|
35
|
+
'You do not have permission to access this credential'
|
|
18
36
|
);
|
|
19
37
|
}
|
|
20
38
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Credentials For User Use Case
|
|
3
|
+
* Retrieves all credentials belonging to a specific user
|
|
4
|
+
*
|
|
5
|
+
* Business Logic:
|
|
6
|
+
* - Fetches credentials for the authenticated user
|
|
7
|
+
* - Returns list of credentials (sensitive data filtered by handler)
|
|
8
|
+
* - Repository returns an array when filtering by userId only
|
|
9
|
+
*/
|
|
10
|
+
class ListCredentialsForUser {
|
|
11
|
+
constructor({ credentialRepository }) {
|
|
12
|
+
this.credentialRepository = credentialRepository;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Execute the use case
|
|
17
|
+
* @param {string} userId - User ID
|
|
18
|
+
* @returns {Promise<Array>} List of credentials (empty array if none found)
|
|
19
|
+
*/
|
|
20
|
+
async execute(userId) {
|
|
21
|
+
const credentials = await this.credentialRepository.findCredential({
|
|
22
|
+
userId,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Repository returns array for userId-only queries
|
|
26
|
+
// Ensure we always return an array (defensive programming)
|
|
27
|
+
return Array.isArray(credentials) ? credentials : [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { ListCredentialsForUser };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reauthorize Credential Use Case
|
|
5
|
+
* Re-authorizes an existing credential with new authentication data
|
|
6
|
+
* Supports both single-step and multi-step authorization flows
|
|
7
|
+
*
|
|
8
|
+
* Business Logic:
|
|
9
|
+
* - Verify credential exists and belongs to user
|
|
10
|
+
* - Load the appropriate module for the credential type
|
|
11
|
+
* - Process authorization callback (OAuth code, API keys, etc.)
|
|
12
|
+
* - Update credential with new tokens
|
|
13
|
+
* - Return success or next step requirements
|
|
14
|
+
*/
|
|
15
|
+
class ReauthorizeCredential {
|
|
16
|
+
constructor({ credentialRepository, moduleRepository }) {
|
|
17
|
+
this.credentialRepository = credentialRepository;
|
|
18
|
+
this.moduleRepository = moduleRepository;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Execute the use case
|
|
23
|
+
* @param {string} credentialId - Credential ID to reauthorize
|
|
24
|
+
* @param {string} userId - User ID (for ownership verification)
|
|
25
|
+
* @param {Object} authData - Authorization data
|
|
26
|
+
* @param {number} [step=1] - Current step in multi-step flow
|
|
27
|
+
* @param {string} [sessionId] - Session ID for multi-step flows
|
|
28
|
+
* @returns {Promise<Object>} Success response or next step requirements
|
|
29
|
+
*/
|
|
30
|
+
async execute(credentialId, userId, authData, step = 1, sessionId = null) {
|
|
31
|
+
// Check if credential exists
|
|
32
|
+
const credential = await this.credentialRepository.findCredentialById(
|
|
33
|
+
credentialId
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (!credential) {
|
|
37
|
+
throw Boom.notFound(`Credential ${credentialId} not found`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Verify ownership - compare as strings to handle both MongoDB and PostgreSQL
|
|
41
|
+
if (credential.userId.toString() !== userId.toString()) {
|
|
42
|
+
throw Boom.forbidden(
|
|
43
|
+
'You do not have permission to reauthorize this credential'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Load the module for this credential type
|
|
48
|
+
const module = await this.moduleRepository.findModuleById(
|
|
49
|
+
credential.id
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!module) {
|
|
53
|
+
throw Boom.badRequest(
|
|
54
|
+
`Module not found for credential type: ${
|
|
55
|
+
credential.type || 'unknown'
|
|
56
|
+
}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Process the authorization callback
|
|
61
|
+
const result = await module.processAuthorizationCallback({
|
|
62
|
+
credentialId,
|
|
63
|
+
userId,
|
|
64
|
+
data: authData,
|
|
65
|
+
step,
|
|
66
|
+
sessionId,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Multi-step flow - return next step requirements
|
|
70
|
+
if (result.step && result.step > 1) {
|
|
71
|
+
return {
|
|
72
|
+
step: result.step,
|
|
73
|
+
totalSteps: result.totalSteps,
|
|
74
|
+
sessionId: result.sessionId,
|
|
75
|
+
requirements: result.requirements,
|
|
76
|
+
message: result.message,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Single-step or final step - update credential and return success
|
|
81
|
+
if (result.success) {
|
|
82
|
+
// Fetch the updated credential to get the new authIsValid status
|
|
83
|
+
const updatedCredential =
|
|
84
|
+
await this.credentialRepository.findCredentialById(
|
|
85
|
+
credentialId
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
credential_id: credentialId,
|
|
91
|
+
authIsValid: updatedCredential?.authIsValid || true,
|
|
92
|
+
...(result.message && { message: result.message }),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// If we get here, something unexpected happened
|
|
97
|
+
throw new Error(
|
|
98
|
+
'Authorization callback did not return expected result format'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { ReauthorizeCredential };
|
|
@@ -8,8 +8,11 @@ class UpdateAuthenticationStatus {
|
|
|
8
8
|
* @param {boolean} authIsValid
|
|
9
9
|
*/
|
|
10
10
|
async execute(credentialId, authIsValid) {
|
|
11
|
-
await this.credentialRepository.updateAuthenticationStatus(
|
|
11
|
+
await this.credentialRepository.updateAuthenticationStatus(
|
|
12
|
+
credentialId,
|
|
13
|
+
authIsValid
|
|
14
|
+
);
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
module.exports = { UpdateAuthenticationStatus };
|
|
18
|
+
module.exports = { UpdateAuthenticationStatus };
|
|
@@ -15,9 +15,9 @@ MongoDB does not allow creating collections (namespaces) inside multi-document t
|
|
|
15
15
|
|
|
16
16
|
### Technical Details
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
18
|
+
- **MongoDB Constraint**: Collections must exist before being used in multi-document transactions
|
|
19
|
+
- **Prisma Behavior**: Prisma may implicitly use transactions for certain operations
|
|
20
|
+
- **Impact**: Health checks fail on fresh databases or when collections haven't been created yet
|
|
21
21
|
|
|
22
22
|
## Solution
|
|
23
23
|
|
|
@@ -37,30 +37,34 @@ This follows the **"fail fast"** principle and ensures consistent state across a
|
|
|
37
37
|
### Changes Made
|
|
38
38
|
|
|
39
39
|
1. **Created MongoDB Schema Initialization** (`packages/core/database/utils/mongodb-schema-init.js`)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
|
|
41
|
+
- `initializeMongoDBSchema()` - Ensures all Prisma collections exist at startup
|
|
42
|
+
- `getPrismaCollections()` - Returns list of all Prisma collection names
|
|
43
|
+
- `PRISMA_COLLECTIONS` - Constant array of all 13 Prisma collections
|
|
44
|
+
- Only runs for MongoDB (skips PostgreSQL)
|
|
45
|
+
- Fails fast if database not connected
|
|
45
46
|
|
|
46
47
|
2. **Created MongoDB Collection Utilities** (`packages/core/database/utils/mongodb-collection-utils.js`)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
|
|
49
|
+
- `ensureCollectionExists(collectionName)` - Ensures a single collection exists
|
|
50
|
+
- `ensureCollectionsExist(collectionNames)` - Batch creates multiple collections
|
|
51
|
+
- `collectionExists(collectionName)` - Checks if a collection exists
|
|
52
|
+
- Handles race conditions gracefully (NamespaceExists errors)
|
|
51
53
|
|
|
52
54
|
3. **Integrated into Database Connection** (`packages/core/database/prisma.js`)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
- Modified `connectPrisma()` to call `initializeMongoDBSchema()` after connection
|
|
57
|
+
- Ensures all collections exist before application handles requests
|
|
55
58
|
|
|
56
59
|
4. **Updated Health Check Repository** (`packages/core/database/repositories/health-check-repository-mongodb.js`)
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
|
|
61
|
+
- Removed per-operation collection existence checks
|
|
62
|
+
- Added documentation noting schema is initialized at startup
|
|
59
63
|
|
|
60
64
|
5. **Added Comprehensive Tests**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
- `mongodb-schema-init.test.js` - Tests schema initialization system
|
|
66
|
+
- `mongodb-collection-utils.test.js` - Tests collection utility functions
|
|
67
|
+
- Tests error handling, race conditions, and edge cases
|
|
64
68
|
|
|
65
69
|
### Implementation Flow
|
|
66
70
|
|
|
@@ -93,23 +97,26 @@ await prisma.credential.create({ data: {...} }); // Works without namespace erro
|
|
|
93
97
|
## Benefits
|
|
94
98
|
|
|
95
99
|
### Immediate Benefits
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
100
|
+
|
|
101
|
+
- ✅ Fixes encryption health check failures on fresh databases
|
|
102
|
+
- ✅ Prevents transaction namespace errors across **all** Prisma operations
|
|
103
|
+
- ✅ No per-operation overhead - collections created once at startup
|
|
104
|
+
- ✅ Fail fast - database issues discovered immediately at startup
|
|
105
|
+
- ✅ Idempotent - safe to run multiple times and across multiple instances
|
|
101
106
|
|
|
102
107
|
### Architectural Benefits
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
+
|
|
109
|
+
- ✅ **Clean separation of concerns**: Schema initialization is infrastructure concern, handled at startup
|
|
110
|
+
- ✅ **Follows DDD/Hexagonal Architecture**: Infrastructure layer handles database setup, repositories focus on business operations
|
|
111
|
+
- ✅ **Consistent across all environments**: Dev, test, staging, production all follow same pattern
|
|
112
|
+
- ✅ **No repository-level checks needed**: All repositories benefit automatically
|
|
113
|
+
- ✅ **Well-tested and documented**: Comprehensive test coverage and documentation
|
|
108
114
|
|
|
109
115
|
### Operational Benefits
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
116
|
+
|
|
117
|
+
- ✅ **Predictable startup**: Clear logging of schema initialization
|
|
118
|
+
- ✅ **Zero runtime overhead**: Collections created once, not on every operation
|
|
119
|
+
- ✅ **Production-ready**: Handles race conditions, errors, and edge cases gracefully
|
|
113
120
|
|
|
114
121
|
## Design Decisions
|
|
115
122
|
|
|
@@ -118,16 +125,19 @@ await prisma.credential.create({ data: {...} }); // Works without namespace erro
|
|
|
118
125
|
We considered two approaches:
|
|
119
126
|
|
|
120
127
|
**❌ Per-Operation Checks (Initial approach)**
|
|
128
|
+
|
|
121
129
|
```javascript
|
|
122
130
|
async createCredential(data) {
|
|
123
131
|
await ensureCollectionExists('Credential'); // Check every time
|
|
124
132
|
return await prisma.credential.create({ data });
|
|
125
133
|
}
|
|
126
134
|
```
|
|
127
|
-
|
|
128
|
-
-
|
|
135
|
+
|
|
136
|
+
- Pros: Guarantees collection exists before each operation
|
|
137
|
+
- Cons: Runtime overhead, repeated checks, scattered logic
|
|
129
138
|
|
|
130
139
|
**✅ Startup Initialization (Final approach)**
|
|
140
|
+
|
|
131
141
|
```javascript
|
|
132
142
|
// Once at startup
|
|
133
143
|
await connectPrisma(); // Initializes all collections
|
|
@@ -137,8 +147,9 @@ async createCredential(data) {
|
|
|
137
147
|
return await prisma.credential.create({ data }); // No checks needed
|
|
138
148
|
}
|
|
139
149
|
```
|
|
140
|
-
|
|
141
|
-
-
|
|
150
|
+
|
|
151
|
+
- Pros: Zero runtime overhead, centralized logic, fail fast, consistent
|
|
152
|
+
- Cons: Requires database connection at startup (already required)
|
|
142
153
|
|
|
143
154
|
### Benefits of Startup Approach
|
|
144
155
|
|
|
@@ -159,6 +170,7 @@ MongoDB schema initialization complete - 13 collections verified (45ms)
|
|
|
159
170
|
```
|
|
160
171
|
|
|
161
172
|
On subsequent startups (collections already exist):
|
|
173
|
+
|
|
162
174
|
```
|
|
163
175
|
Initializing MongoDB schema - ensuring all collections exist...
|
|
164
176
|
MongoDB schema initialization complete - 13 collections verified (12ms)
|
|
@@ -166,33 +178,41 @@ MongoDB schema initialization complete - 13 collections verified (12ms)
|
|
|
166
178
|
|
|
167
179
|
## References
|
|
168
180
|
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
181
|
+
- [Prisma Issue #8305](https://github.com/prisma/prisma/issues/8305) - MongoDB "Cannot create namespace" error
|
|
182
|
+
- [Mongoose Issue #6699](https://github.com/Automattic/mongoose/issues/6699) - Similar issue in Mongoose
|
|
183
|
+
- [MongoDB Transactions Documentation](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations) - Operations allowed in transactions
|
|
184
|
+
- [Prisma MongoDB Guide](https://www.prisma.io/docs/guides/database/mongodb) - Using Prisma with MongoDB
|
|
173
185
|
|
|
174
186
|
## Future Considerations
|
|
175
187
|
|
|
176
188
|
### Automatic Schema Sync
|
|
189
|
+
|
|
177
190
|
Consider enhancing the system to:
|
|
178
|
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
191
|
+
|
|
192
|
+
- Parse Prisma schema file dynamically to extract collection names
|
|
193
|
+
- Auto-detect schema changes and create new collections
|
|
194
|
+
- Provide CLI command for manual schema initialization
|
|
181
195
|
|
|
182
196
|
### Migration Support
|
|
197
|
+
|
|
183
198
|
For production deployments with existing data:
|
|
184
|
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
199
|
+
|
|
200
|
+
- Document migration procedures for new collections
|
|
201
|
+
- Consider pre-migration scripts for blue-green deployments
|
|
202
|
+
- Add health check for schema initialization status
|
|
187
203
|
|
|
188
204
|
### Multi-Database Support
|
|
205
|
+
|
|
189
206
|
The system already handles:
|
|
190
|
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
207
|
+
|
|
208
|
+
- ✅ MongoDB - Full schema initialization
|
|
209
|
+
- ✅ PostgreSQL - Skips initialization (uses Prisma migrations)
|
|
210
|
+
- Consider adding explicit migration support for DocumentDB-specific features
|
|
193
211
|
|
|
194
212
|
### Index Creation
|
|
213
|
+
|
|
195
214
|
Future enhancement could also create indexes at startup:
|
|
196
|
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
215
|
+
|
|
216
|
+
- Parse Prisma schema for `@@index` directives
|
|
217
|
+
- Create indexes if they don't exist
|
|
218
|
+
- Provide index health checks
|
package/database/config.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Determines database type from environment or app definition
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* Detection order:
|
|
10
10
|
* 1. DB_TYPE environment variable (set for migration handlers)
|
|
11
11
|
* 2. App definition (backend/index.js Definition.database configuration)
|
|
@@ -20,67 +20,21 @@ function getDatabaseType() {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Fallback: Load app definition
|
|
23
|
+
// Uses loadAppDefinition() which respects setAppDefinition() cache.
|
|
24
|
+
// This is critical for platforms like Netlify where process.cwd()-based
|
|
25
|
+
// discovery fails at runtime (process.cwd() is /var/task, not the project root).
|
|
23
26
|
try {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let backendIndexPath;
|
|
29
|
-
let database;
|
|
30
|
-
const backendPackagePath = findNearestBackendPackageJson();
|
|
31
|
-
|
|
32
|
-
if (!backendPackagePath) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
'[Frigg] Cannot find backend package.json. ' +
|
|
35
|
-
'Ensure backend/package.json exists in your project.'
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const backendDir = path.dirname(backendPackagePath);
|
|
40
|
-
backendIndexPath = path.join(backendDir, 'index.js');
|
|
41
|
-
|
|
42
|
-
if (!fs.existsSync(backendIndexPath)) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`[Frigg] Backend index.js not found at ${backendIndexPath}. ` +
|
|
45
|
-
'Ensure backend/index.js exists with a Definition export.'
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let backendModule;
|
|
50
|
-
try {
|
|
51
|
-
backendModule = require(backendIndexPath);
|
|
52
|
-
} catch (requireError) {
|
|
53
|
-
// Extract the actual file with the error from the stack trace
|
|
54
|
-
// Skip internal Node.js files (node:internal/*) and find first user file
|
|
55
|
-
let errorFile = 'unknown file';
|
|
56
|
-
const stackLines = requireError.stack?.split('\n') || [];
|
|
57
|
-
|
|
58
|
-
for (const line of stackLines) {
|
|
59
|
-
// Match file paths in stack trace, excluding node:internal
|
|
60
|
-
const match = line.match(/\(([^)]+\.js):\d+:\d+\)/) || line.match(/at ([^(]+\.js):\d+:\d+/);
|
|
61
|
-
if (match && match[1] && !match[1].includes('node:internal')) {
|
|
62
|
-
errorFile = match[1];
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Provide better error context for syntax/runtime errors
|
|
68
|
-
throw new Error(
|
|
69
|
-
`[Frigg] Failed to load app definition from ${backendIndexPath}\n` +
|
|
70
|
-
`Error: ${requireError.message}\n` +
|
|
71
|
-
`File with error: ${errorFile}\n` +
|
|
72
|
-
`\nFull stack trace:\n${requireError.stack}\n\n` +
|
|
73
|
-
'This error occurred while loading your app definition or its dependencies. ' +
|
|
74
|
-
'Check the file listed above for syntax errors (trailing commas, missing brackets, etc.)'
|
|
75
|
-
);
|
|
76
|
-
}
|
|
27
|
+
const {
|
|
28
|
+
loadAppDefinition,
|
|
29
|
+
} = require('../handlers/app-definition-loader');
|
|
77
30
|
|
|
78
|
-
|
|
31
|
+
const { appDefinition } = loadAppDefinition();
|
|
32
|
+
const database = appDefinition?.database;
|
|
79
33
|
|
|
80
34
|
if (!database) {
|
|
81
35
|
throw new Error(
|
|
82
36
|
'[Frigg] App definition missing database configuration. ' +
|
|
83
|
-
|
|
37
|
+
'Add database: { postgres: { enable: true } } (or mongoDB/documentDB) to your backend/index.js'
|
|
84
38
|
);
|
|
85
39
|
}
|
|
86
40
|
|
|
@@ -98,7 +52,7 @@ function getDatabaseType() {
|
|
|
98
52
|
|
|
99
53
|
throw new Error(
|
|
100
54
|
'[Frigg] No database enabled in app definition. ' +
|
|
101
|
-
|
|
55
|
+
'Set one of: database.postgres.enable, database.mongoDB.enable, or database.documentDB.enable to true'
|
|
102
56
|
);
|
|
103
57
|
} catch (error) {
|
|
104
58
|
// Re-throw with context if it's our error
|
|
@@ -150,5 +104,5 @@ Object.defineProperty(module.exports, 'DB_TYPE', {
|
|
|
150
104
|
return cachedDbType;
|
|
151
105
|
},
|
|
152
106
|
enumerable: true,
|
|
153
|
-
configurable: true
|
|
154
|
-
});
|
|
107
|
+
configurable: true,
|
|
108
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const { Cryptor } = require('../encrypt/Cryptor');
|
|
2
|
-
const {
|
|
2
|
+
const {
|
|
3
|
+
getEncryptedFields,
|
|
4
|
+
loadCustomEncryptionSchema,
|
|
5
|
+
} = require('./encryption/encryption-schema-registry');
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Encryption service specifically for DocumentDB repositories
|
|
@@ -51,8 +54,11 @@ class DocumentDBEncryptionService {
|
|
|
51
54
|
loadCustomEncryptionSchema();
|
|
52
55
|
|
|
53
56
|
// Match logic from packages/core/database/prisma.js
|
|
54
|
-
const stage =
|
|
55
|
-
|
|
57
|
+
const stage =
|
|
58
|
+
process.env.STAGE || process.env.NODE_ENV || 'development';
|
|
59
|
+
const bypassEncryption = ['dev', 'test', 'local'].includes(
|
|
60
|
+
stage.toLowerCase()
|
|
61
|
+
);
|
|
56
62
|
|
|
57
63
|
if (bypassEncryption) {
|
|
58
64
|
this.cryptor = null;
|
|
@@ -61,11 +67,17 @@ class DocumentDBEncryptionService {
|
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
// Determine encryption method (ensure boolean values)
|
|
64
|
-
const hasKMS = !!(
|
|
65
|
-
|
|
70
|
+
const hasKMS = !!(
|
|
71
|
+
process.env.KMS_KEY_ARN && process.env.KMS_KEY_ARN.trim() !== ''
|
|
72
|
+
);
|
|
73
|
+
const hasAES = !!(
|
|
74
|
+
process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== ''
|
|
75
|
+
);
|
|
66
76
|
|
|
67
77
|
if (!hasKMS && !hasAES) {
|
|
68
|
-
console.warn(
|
|
78
|
+
console.warn(
|
|
79
|
+
'[DocumentDBEncryptionService] No encryption keys configured. Encryption disabled.'
|
|
80
|
+
);
|
|
69
81
|
this.cryptor = null;
|
|
70
82
|
this.enabled = false;
|
|
71
83
|
return;
|
|
@@ -202,14 +214,16 @@ class DocumentDBEncryptionService {
|
|
|
202
214
|
|
|
203
215
|
try {
|
|
204
216
|
// Convert to string if needed
|
|
205
|
-
const stringValue =
|
|
206
|
-
? value
|
|
207
|
-
: JSON.stringify(value);
|
|
217
|
+
const stringValue =
|
|
218
|
+
typeof value === 'string' ? value : JSON.stringify(value);
|
|
208
219
|
|
|
209
220
|
// Encrypt using Cryptor
|
|
210
221
|
current[fieldName] = await this.cryptor.encrypt(stringValue);
|
|
211
222
|
} catch (error) {
|
|
212
|
-
console.error(
|
|
223
|
+
console.error(
|
|
224
|
+
`[DocumentDBEncryptionService] Failed to encrypt ${modelName}.${fieldPath}:`,
|
|
225
|
+
error.message
|
|
226
|
+
);
|
|
213
227
|
throw error;
|
|
214
228
|
}
|
|
215
229
|
}
|
|
@@ -261,7 +275,7 @@ class DocumentDBEncryptionService {
|
|
|
261
275
|
modelName,
|
|
262
276
|
fieldPath,
|
|
263
277
|
encryptedValuePrefix: encryptedValue.substring(0, 20),
|
|
264
|
-
errorMessage: error.message
|
|
278
|
+
errorMessage: error.message,
|
|
265
279
|
};
|
|
266
280
|
|
|
267
281
|
console.error(
|
|
@@ -270,7 +284,9 @@ class DocumentDBEncryptionService {
|
|
|
270
284
|
);
|
|
271
285
|
|
|
272
286
|
// Throw error to fail fast - don't silently corrupt data
|
|
273
|
-
throw new Error(
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Decryption failed for ${modelName}.${fieldPath}: ${error.message}`
|
|
289
|
+
);
|
|
274
290
|
}
|
|
275
291
|
}
|
|
276
292
|
|
|
@@ -307,7 +323,7 @@ class DocumentDBEncryptionService {
|
|
|
307
323
|
const base64Pattern = /^[A-Za-z0-9+/=]+$/;
|
|
308
324
|
|
|
309
325
|
// All parts should be base64-encoded
|
|
310
|
-
if (!parts.every(part => base64Pattern.test(part))) {
|
|
326
|
+
if (!parts.every((part) => base64Pattern.test(part))) {
|
|
311
327
|
return false;
|
|
312
328
|
}
|
|
313
329
|
|