@friggframework/core 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.35ee143.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 +67 -66
- package/CLAUDE.md +124 -148
- package/README.md +130 -165
- package/admin-scripts/index.js +16 -30
- package/admin-scripts/repositories/{admin-api-key-repository-documentdb.js → admin-process-repository-documentdb.js} +5 -5
- package/admin-scripts/repositories/{admin-api-key-repository-factory.js → admin-process-repository-factory.js} +15 -19
- 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-schedule-repository-factory.js +2 -6
- package/admin-scripts/repositories/script-schedule-repository-interface.js +14 -36
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +13 -25
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +13 -25
- package/application/commands/admin-script-commands.js +102 -266
- package/application/commands/integration-commands.js +6 -12
- package/application/index.js +3 -1
- package/assertions/get.js +4 -1
- package/associations/association.js +57 -59
- package/associations/model.js +41 -41
- package/core/CLAUDE.md +156 -222
- package/core/Worker.js +1 -5
- package/credential/repositories/credential-repository-documentdb.js +1 -71
- package/credential/repositories/credential-repository-interface.js +2 -8
- package/credential/repositories/credential-repository-mongo.js +4 -45
- package/credential/repositories/credential-repository-postgres.js +3 -44
- package/credential/repositories/credential-repository.js +4 -6
- package/credential/use-cases/get-credential-for-user.js +3 -21
- package/credential/use-cases/update-authentication-status.js +2 -5
- package/database/MONGODB_TRANSACTION_FIX.md +52 -72
- package/database/adapters/lambda-invoker.js +7 -8
- package/database/config.js +13 -15
- package/database/documentdb-encryption-service.js +13 -29
- package/database/documentdb-utils.js +12 -21
- package/database/encryption/README.md +36 -53
- package/database/encryption/encryption-schema-registry.js +9 -21
- package/database/encryption/field-encryption-service.js +4 -15
- package/database/encryption/logger.js +2 -3
- package/database/encryption/prisma-encryption-extension.js +8 -5
- package/database/index.js +2 -4
- package/database/models/IndividualUser.js +15 -15
- package/database/models/OrganizationUser.js +4 -6
- package/database/models/UserModel.js +2 -2
- package/database/models/WebsocketConnection.js +1 -4
- package/database/mongoose.js +2 -2
- package/database/prisma.js +14 -22
- package/database/repositories/health-check-repository-documentdb.js +7 -22
- package/database/repositories/health-check-repository-factory.js +3 -9
- package/database/repositories/health-check-repository-interface.js +5 -15
- package/database/repositories/health-check-repository-mongodb.js +6 -9
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/repositories/migration-status-repository-s3.js +5 -10
- package/database/use-cases/check-database-health-use-case.js +2 -3
- package/database/use-cases/check-database-state-use-case.js +7 -9
- package/database/use-cases/get-database-state-via-worker-use-case.js +12 -15
- package/database/use-cases/get-migration-status-use-case.js +2 -4
- package/database/use-cases/run-database-migration-use-case.js +9 -41
- package/database/use-cases/test-encryption-use-case.js +5 -4
- package/database/use-cases/trigger-database-migration-use-case.js +8 -9
- package/database/utils/mongodb-collection-utils.js +3 -11
- package/database/utils/mongodb-schema-init.js +4 -10
- package/database/utils/prisma-runner.js +74 -144
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +80 -135
- package/encrypt/Cryptor.js +2 -8
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +16 -59
- package/generated/prisma-mongodb/index-browser.js +12 -62
- package/generated/prisma-mongodb/index.d.ts +4850 -8136
- package/generated/prisma-mongodb/index.js +16 -59
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +33 -75
- package/generated/prisma-mongodb/wasm.js +16 -59
- package/generated/prisma-postgresql/edge.js +15 -59
- package/generated/prisma-postgresql/index-browser.js +11 -62
- package/generated/prisma-postgresql/index.d.ts +5963 -8930
- package/generated/prisma-postgresql/index.js +15 -59
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +33 -73
- package/generated/prisma-postgresql/wasm.js +15 -59
- package/handlers/WEBHOOKS.md +81 -104
- package/handlers/app-definition-loader.js +1 -1
- package/handlers/app-handler-helpers.js +2 -14
- package/handlers/backend-utils.js +1 -0
- package/handlers/database-migration-handler.js +45 -64
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +135 -170
- package/handlers/routers/auth.js +2 -23
- package/handlers/routers/db-migration.handler.js +3 -2
- package/handlers/routers/db-migration.js +33 -69
- package/handlers/routers/health.js +8 -23
- package/handlers/routers/integration-defined-routers.js +4 -4
- package/handlers/routers/integration-webhook-routers.js +7 -17
- package/handlers/routers/websocket.js +3 -9
- package/handlers/use-cases/check-integrations-health-use-case.js +6 -13
- package/handlers/workers/db-migration.js +26 -54
- package/index.js +9 -5
- package/integrations/WEBHOOK-QUICKSTART.md +14 -21
- package/integrations/index.js +0 -4
- package/integrations/integration-base.js +6 -9
- package/integrations/integration-router.js +146 -1817
- package/integrations/options.js +20 -47
- package/integrations/repositories/integration-repository-documentdb.js +24 -53
- package/integrations/repositories/integration-repository-factory.js +2 -6
- package/integrations/repositories/integration-repository-interface.js +9 -27
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +1 -0
- package/integrations/repositories/process-repository-interface.js +5 -9
- package/integrations/repositories/process-repository-mongo.js +3 -7
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +8 -9
- package/integrations/tests/doubles/dummy-integration-class.js +8 -81
- package/integrations/tests/doubles/test-integration-repository.js +11 -51
- package/integrations/use-cases/create-integration.js +32 -36
- package/integrations/use-cases/create-process.js +9 -20
- package/integrations/use-cases/delete-integration-for-user.js +4 -5
- package/integrations/use-cases/get-integration-for-user.js +10 -25
- package/integrations/use-cases/get-integration-instance-by-definition.js +7 -13
- package/integrations/use-cases/get-integrations-for-user.js +1 -4
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +6 -5
- package/integrations/use-cases/index.js +1 -3
- package/integrations/use-cases/load-integration-context.js +5 -1
- package/integrations/use-cases/update-process-state.js +8 -10
- package/integrations/utils/map-integration-dto.js +3 -9
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +30 -32
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/module.js +1 -3
- package/modules/repositories/module-repository-documentdb.js +48 -114
- package/modules/repositories/module-repository-interface.js +0 -13
- package/modules/repositories/module-repository-mongo.js +6 -46
- package/modules/repositories/module-repository-postgres.js +6 -48
- package/modules/repositories/module-repository.js +1 -34
- package/modules/requester/api-key.js +2 -0
- package/modules/requester/basic.js +1 -0
- package/modules/requester/oauth-2.js +2 -3
- package/modules/test/mock-api/definition.js +10 -15
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +4 -10
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +6 -8
- package/modules/use-cases/get-module.js +9 -12
- package/modules/use-cases/process-authorization-callback.js +6 -12
- package/modules/use-cases/refresh-entity-options.js +6 -8
- package/modules/use-cases/test-module-auth.js +6 -8
- package/modules/utils/map-module-dto.js +2 -2
- package/package.json +5 -7
- package/prisma-mongodb/schema.prisma +33 -75
- package/prisma-postgresql/schema.prisma +33 -73
- package/queues/queuer-util.js +1 -5
- package/syncs/model.js +51 -51
- package/syncs/repositories/sync-repository-documentdb.js +19 -39
- package/syncs/repositories/sync-repository-interface.js +2 -6
- package/syncs/repositories/sync-repository-postgres.js +13 -19
- package/syncs/sync.js +100 -104
- package/token/repositories/token-repository-factory.js +3 -1
- package/types/assertions/index.d.ts +72 -72
- package/types/associations/index.d.ts +57 -57
- package/types/core/index.d.ts +53 -59
- package/types/database/index.d.ts +2 -2
- package/types/encrypt/index.d.ts +3 -3
- package/types/errors/index.d.ts +54 -61
- package/types/eslint-config/index.d.ts +40 -40
- package/types/integrations/index.d.ts +183 -185
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/module-plugin/index.d.ts +237 -239
- package/types/prettier-config/index.d.ts +5 -5
- package/types/syncs/index.d.ts +108 -102
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +93 -95
- package/user/tests/doubles/test-user-repository.js +2 -5
- package/user/use-cases/authenticate-user.js +4 -2
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +7 -8
- 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/get-user-from-adopter-jwt.js +2 -0
- package/user/use-cases/get-user-from-bearer-token.js +3 -7
- package/user/use-cases/get-user-from-x-frigg-headers.js +11 -14
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +4 -18
- package/utils/backend-path.js +2 -2
- package/utils/index.js +1 -4
- package/websocket/repositories/websocket-connection-repository-documentdb.js +8 -21
- package/websocket/repositories/websocket-connection-repository-mongo.js +4 -8
- package/websocket/repositories/websocket-connection-repository-postgres.js +4 -8
- package/websocket/repositories/websocket-connection-repository.js +4 -8
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +0 -121
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +0 -155
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +0 -189
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +0 -21
- package/admin-scripts/repositories/script-execution-repository-factory.js +0 -55
- package/admin-scripts/repositories/script-execution-repository-interface.js +0 -193
- package/admin-scripts/repositories/script-execution-repository-mongo.js +0 -278
- package/admin-scripts/repositories/script-execution-repository-postgres.js +0 -320
- package/credential/credential-router.js +0 -262
- package/credential/use-cases/delete-credential-for-user.js +0 -50
- package/credential/use-cases/list-credentials-for-user.js +0 -31
- package/credential/use-cases/reauthorize-credential.js +0 -103
- package/handlers/routers/admin.js +0 -450
- package/handlers/routers/docs.js +0 -180
- package/handlers/routers/middleware/loadUser.js +0 -39
- package/handlers/routers/middleware/requireAdmin.js +0 -41
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -19
- package/integrations/use-cases/execute-proxy-request.js +0 -564
- package/modules/domain/entities/AuthorizationSession.js +0 -131
- package/modules/domain/entities/index.js +0 -10
- package/modules/repositories/authorization-session-repository-factory.js +0 -55
- package/modules/repositories/authorization-session-repository-interface.js +0 -75
- package/modules/repositories/authorization-session-repository-mongo.js +0 -169
- package/modules/repositories/authorization-session-repository-postgres.js +0 -170
- package/modules/use-cases/delete-module-entity.js +0 -23
- package/modules/use-cases/get-authorization-requirements.js +0 -110
- package/modules/use-cases/get-module-entity-by-id.js +0 -16
- package/modules/use-cases/process-authorization-step.js +0 -160
- package/modules/use-cases/start-authorization-session.js +0 -86
- package/modules/use-cases/update-module-entity.js +0 -27
- package/openapi/openapi-spec-generator.js +0 -370
- package/openapi/openapi-v1.yaml +0 -1360
- package/openapi/openapi-v2.yaml +0 -1535
- package/user/use-cases/delete-user.js +0 -47
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)
|
|
@@ -32,7 +32,7 @@ function getDatabaseType() {
|
|
|
32
32
|
if (!backendPackagePath) {
|
|
33
33
|
throw new Error(
|
|
34
34
|
'[Frigg] Cannot find backend package.json. ' +
|
|
35
|
-
|
|
35
|
+
'Ensure backend/package.json exists in your project.'
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ function getDatabaseType() {
|
|
|
42
42
|
if (!fs.existsSync(backendIndexPath)) {
|
|
43
43
|
throw new Error(
|
|
44
44
|
`[Frigg] Backend index.js not found at ${backendIndexPath}. ` +
|
|
45
|
-
|
|
45
|
+
'Ensure backend/index.js exists with a Definition export.'
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -57,9 +57,7 @@ function getDatabaseType() {
|
|
|
57
57
|
|
|
58
58
|
for (const line of stackLines) {
|
|
59
59
|
// Match file paths in stack trace, excluding node:internal
|
|
60
|
-
const match =
|
|
61
|
-
line.match(/\(([^)]+\.js):\d+:\d+\)/) ||
|
|
62
|
-
line.match(/at ([^(]+\.js):\d+:\d+/);
|
|
60
|
+
const match = line.match(/\(([^)]+\.js):\d+:\d+\)/) || line.match(/at ([^(]+\.js):\d+:\d+/);
|
|
63
61
|
if (match && match[1] && !match[1].includes('node:internal')) {
|
|
64
62
|
errorFile = match[1];
|
|
65
63
|
break;
|
|
@@ -69,11 +67,11 @@ function getDatabaseType() {
|
|
|
69
67
|
// Provide better error context for syntax/runtime errors
|
|
70
68
|
throw new Error(
|
|
71
69
|
`[Frigg] Failed to load app definition from ${backendIndexPath}\n` +
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.)'
|
|
77
75
|
);
|
|
78
76
|
}
|
|
79
77
|
|
|
@@ -82,7 +80,7 @@ function getDatabaseType() {
|
|
|
82
80
|
if (!database) {
|
|
83
81
|
throw new Error(
|
|
84
82
|
'[Frigg] App definition missing database configuration. ' +
|
|
85
|
-
|
|
83
|
+
`Add database: { postgres: { enable: true } } (or mongoDB/documentDB) to ${backendIndexPath}`
|
|
86
84
|
);
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -100,7 +98,7 @@ function getDatabaseType() {
|
|
|
100
98
|
|
|
101
99
|
throw new Error(
|
|
102
100
|
'[Frigg] No database enabled in app definition. ' +
|
|
103
|
-
|
|
101
|
+
'Set one of: database.postgres.enable, database.mongoDB.enable, or database.documentDB.enable to true'
|
|
104
102
|
);
|
|
105
103
|
} catch (error) {
|
|
106
104
|
// Re-throw with context if it's our error
|
|
@@ -152,5 +150,5 @@ Object.defineProperty(module.exports, 'DB_TYPE', {
|
|
|
152
150
|
return cachedDbType;
|
|
153
151
|
},
|
|
154
152
|
enumerable: true,
|
|
155
|
-
configurable: true
|
|
156
|
-
});
|
|
153
|
+
configurable: true
|
|
154
|
+
});
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
const { Cryptor } = require('../encrypt/Cryptor');
|
|
2
|
-
const {
|
|
3
|
-
getEncryptedFields,
|
|
4
|
-
loadCustomEncryptionSchema,
|
|
5
|
-
} = require('./encryption/encryption-schema-registry');
|
|
2
|
+
const { getEncryptedFields, loadCustomEncryptionSchema } = require('./encryption/encryption-schema-registry');
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
5
|
* Encryption service specifically for DocumentDB repositories
|
|
@@ -54,11 +51,8 @@ class DocumentDBEncryptionService {
|
|
|
54
51
|
loadCustomEncryptionSchema();
|
|
55
52
|
|
|
56
53
|
// Match logic from packages/core/database/prisma.js
|
|
57
|
-
const stage =
|
|
58
|
-
|
|
59
|
-
const bypassEncryption = ['dev', 'test', 'local'].includes(
|
|
60
|
-
stage.toLowerCase()
|
|
61
|
-
);
|
|
54
|
+
const stage = process.env.STAGE || process.env.NODE_ENV || 'development';
|
|
55
|
+
const bypassEncryption = ['dev', 'test', 'local'].includes(stage.toLowerCase());
|
|
62
56
|
|
|
63
57
|
if (bypassEncryption) {
|
|
64
58
|
this.cryptor = null;
|
|
@@ -67,17 +61,11 @@ class DocumentDBEncryptionService {
|
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
// Determine encryption method (ensure boolean values)
|
|
70
|
-
const hasKMS = !!(
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
const hasAES = !!(
|
|
74
|
-
process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== ''
|
|
75
|
-
);
|
|
64
|
+
const hasKMS = !!(process.env.KMS_KEY_ARN && process.env.KMS_KEY_ARN.trim() !== '');
|
|
65
|
+
const hasAES = !!(process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== '');
|
|
76
66
|
|
|
77
67
|
if (!hasKMS && !hasAES) {
|
|
78
|
-
console.warn(
|
|
79
|
-
'[DocumentDBEncryptionService] No encryption keys configured. Encryption disabled.'
|
|
80
|
-
);
|
|
68
|
+
console.warn('[DocumentDBEncryptionService] No encryption keys configured. Encryption disabled.');
|
|
81
69
|
this.cryptor = null;
|
|
82
70
|
this.enabled = false;
|
|
83
71
|
return;
|
|
@@ -214,16 +202,14 @@ class DocumentDBEncryptionService {
|
|
|
214
202
|
|
|
215
203
|
try {
|
|
216
204
|
// Convert to string if needed
|
|
217
|
-
const stringValue =
|
|
218
|
-
|
|
205
|
+
const stringValue = typeof value === 'string'
|
|
206
|
+
? value
|
|
207
|
+
: JSON.stringify(value);
|
|
219
208
|
|
|
220
209
|
// Encrypt using Cryptor
|
|
221
210
|
current[fieldName] = await this.cryptor.encrypt(stringValue);
|
|
222
211
|
} catch (error) {
|
|
223
|
-
console.error(
|
|
224
|
-
`[DocumentDBEncryptionService] Failed to encrypt ${modelName}.${fieldPath}:`,
|
|
225
|
-
error.message
|
|
226
|
-
);
|
|
212
|
+
console.error(`[DocumentDBEncryptionService] Failed to encrypt ${modelName}.${fieldPath}:`, error.message);
|
|
227
213
|
throw error;
|
|
228
214
|
}
|
|
229
215
|
}
|
|
@@ -275,7 +261,7 @@ class DocumentDBEncryptionService {
|
|
|
275
261
|
modelName,
|
|
276
262
|
fieldPath,
|
|
277
263
|
encryptedValuePrefix: encryptedValue.substring(0, 20),
|
|
278
|
-
errorMessage: error.message
|
|
264
|
+
errorMessage: error.message
|
|
279
265
|
};
|
|
280
266
|
|
|
281
267
|
console.error(
|
|
@@ -284,9 +270,7 @@ class DocumentDBEncryptionService {
|
|
|
284
270
|
);
|
|
285
271
|
|
|
286
272
|
// Throw error to fail fast - don't silently corrupt data
|
|
287
|
-
throw new Error(
|
|
288
|
-
`Decryption failed for ${modelName}.${fieldPath}: ${error.message}`
|
|
289
|
-
);
|
|
273
|
+
throw new Error(`Decryption failed for ${modelName}.${fieldPath}: ${error.message}`);
|
|
290
274
|
}
|
|
291
275
|
}
|
|
292
276
|
|
|
@@ -323,7 +307,7 @@ class DocumentDBEncryptionService {
|
|
|
323
307
|
const base64Pattern = /^[A-Za-z0-9+/=]+$/;
|
|
324
308
|
|
|
325
309
|
// All parts should be base64-encoded
|
|
326
|
-
if (!parts.every(
|
|
310
|
+
if (!parts.every(part => base64Pattern.test(part))) {
|
|
327
311
|
return false;
|
|
328
312
|
}
|
|
329
313
|
|
|
@@ -3,10 +3,8 @@ const { ObjectId } = require('mongodb');
|
|
|
3
3
|
function toObjectId(value) {
|
|
4
4
|
if (value === null || value === undefined || value === '') return undefined;
|
|
5
5
|
if (value instanceof ObjectId) return value;
|
|
6
|
-
if (typeof value === 'object' && value.$oid)
|
|
7
|
-
|
|
8
|
-
if (typeof value === 'string')
|
|
9
|
-
return ObjectId.isValid(value) ? new ObjectId(value) : undefined;
|
|
6
|
+
if (typeof value === 'object' && value.$oid) return new ObjectId(value.$oid);
|
|
7
|
+
if (typeof value === 'string') return ObjectId.isValid(value) ? new ObjectId(value) : undefined;
|
|
10
8
|
return undefined;
|
|
11
9
|
}
|
|
12
10
|
|
|
@@ -17,8 +15,7 @@ function toObjectIdArray(values) {
|
|
|
17
15
|
|
|
18
16
|
function fromObjectId(value) {
|
|
19
17
|
if (value instanceof ObjectId) return value.toHexString();
|
|
20
|
-
if (typeof value === 'object' && value !== null && value.$oid)
|
|
21
|
-
return value.$oid;
|
|
18
|
+
if (typeof value === 'object' && value !== null && value.$oid) return value.$oid;
|
|
22
19
|
if (typeof value === 'string') return value;
|
|
23
20
|
return value === undefined || value === null ? value : String(value);
|
|
24
21
|
}
|
|
@@ -33,10 +30,7 @@ async function findMany(client, collection, filter = {}, options = {}) {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
async function findOne(client, collection, filter = {}, options = {}) {
|
|
36
|
-
const docs = await findMany(client, collection, filter, {
|
|
37
|
-
...options,
|
|
38
|
-
limit: 1,
|
|
39
|
-
});
|
|
33
|
+
const docs = await findMany(client, collection, filter, { ...options, limit: 1 });
|
|
40
34
|
return docs[0] || null;
|
|
41
35
|
}
|
|
42
36
|
|
|
@@ -53,9 +47,7 @@ async function insertOne(client, collection, document) {
|
|
|
53
47
|
// Validate insert succeeded
|
|
54
48
|
if (result.ok !== 1) {
|
|
55
49
|
throw new Error(
|
|
56
|
-
`Insert command failed for collection '${collection}': ${JSON.stringify(
|
|
57
|
-
result
|
|
58
|
-
)}`
|
|
50
|
+
`Insert command failed for collection '${collection}': ${JSON.stringify(result)}`
|
|
59
51
|
);
|
|
60
52
|
}
|
|
61
53
|
|
|
@@ -75,7 +67,7 @@ async function insertOne(client, collection, document) {
|
|
|
75
67
|
if (result.n !== 1) {
|
|
76
68
|
throw new Error(
|
|
77
69
|
`Expected to insert 1 document into '${collection}', but inserted ${result.n}. ` +
|
|
78
|
-
|
|
70
|
+
`Result: ${JSON.stringify(result)}`
|
|
79
71
|
);
|
|
80
72
|
}
|
|
81
73
|
|
|
@@ -83,13 +75,11 @@ async function insertOne(client, collection, document) {
|
|
|
83
75
|
}
|
|
84
76
|
|
|
85
77
|
async function updateOne(client, collection, filter, update, options = {}) {
|
|
86
|
-
const updates = [
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
},
|
|
92
|
-
];
|
|
78
|
+
const updates = [{
|
|
79
|
+
q: filter,
|
|
80
|
+
u: update,
|
|
81
|
+
upsert: Boolean(options.upsert),
|
|
82
|
+
}];
|
|
93
83
|
if (options.arrayFilters) updates[0].arrayFilters = options.arrayFilters;
|
|
94
84
|
const result = await client.$runCommandRaw({
|
|
95
85
|
update: collection,
|
|
@@ -143,3 +133,4 @@ module.exports = {
|
|
|
143
133
|
deleteMany,
|
|
144
134
|
aggregate,
|
|
145
135
|
};
|
|
136
|
+
|
|
@@ -125,11 +125,10 @@ Or simply don't configure any encryption keys. In Production field level encrypt
|
|
|
125
125
|
Core and custom encrypted fields are defined in `encryption-schema-registry.js`. See that file for the current list of encrypted fields.
|
|
126
126
|
|
|
127
127
|
**Core fields include**:
|
|
128
|
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
- OAuth client credentials: `client_secret`
|
|
128
|
+
- OAuth tokens: `access_token`, `refresh_token`, `id_token`
|
|
129
|
+
- API keys: `api_key`, `apiKey`, `API_KEY_VALUE`
|
|
130
|
+
- Basic auth: `password`
|
|
131
|
+
- OAuth client credentials: `client_secret`
|
|
133
132
|
|
|
134
133
|
**Note**: API modules should use `api_key` (snake_case) in their `apiPropertiesToPersist.credential` arrays for consistency with OAuth2Requester and BasicAuthRequester conventions.
|
|
135
134
|
|
|
@@ -138,36 +137,34 @@ Core and custom encrypted fields are defined in `encryption-schema-registry.js`.
|
|
|
138
137
|
When creating API module definitions, use **snake_case** for credential property names to ensure automatic encryption:
|
|
139
138
|
|
|
140
139
|
**✅ Recommended (automatically encrypted):**
|
|
141
|
-
|
|
142
140
|
```javascript
|
|
143
141
|
// API Module Definition
|
|
144
142
|
const Definition = {
|
|
145
143
|
requiredAuthMethods: {
|
|
146
144
|
apiPropertiesToPersist: {
|
|
147
145
|
// For API key authentication
|
|
148
|
-
credential: ['api_key'],
|
|
146
|
+
credential: ['api_key'], // ✅ Automatically encrypted
|
|
149
147
|
// or for OAuth authentication
|
|
150
|
-
credential: ['access_token', 'refresh_token'],
|
|
148
|
+
credential: ['access_token', 'refresh_token'], // ✅ OAuth - encrypted
|
|
151
149
|
// or for Basic authentication
|
|
152
|
-
credential: ['username', 'password'],
|
|
153
|
-
}
|
|
154
|
-
}
|
|
150
|
+
credential: ['username', 'password'], // ✅ Basic auth - encrypted
|
|
151
|
+
}
|
|
152
|
+
}
|
|
155
153
|
};
|
|
156
154
|
|
|
157
155
|
// API class (extends ApiKeyRequester)
|
|
158
156
|
class MyApi extends ApiKeyRequester {
|
|
159
157
|
constructor(params) {
|
|
160
158
|
super(params);
|
|
161
|
-
this.api_key = params.api_key;
|
|
159
|
+
this.api_key = params.api_key; // ✅ snake_case convention
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
162
|
```
|
|
165
163
|
|
|
166
164
|
**❌ Avoid (requires manual encryption schema):**
|
|
167
|
-
|
|
168
165
|
```javascript
|
|
169
166
|
apiPropertiesToPersist: {
|
|
170
|
-
credential: ['customToken', 'proprietaryKey']
|
|
167
|
+
credential: ['customToken', 'proprietaryKey'] // ❌ Not in core schema
|
|
171
168
|
}
|
|
172
169
|
```
|
|
173
170
|
|
|
@@ -187,61 +184,57 @@ const Definition = {
|
|
|
187
184
|
|
|
188
185
|
// Declare which credential fields need encryption
|
|
189
186
|
encryption: {
|
|
190
|
-
credentialFields: ['api_key', 'webhook_secret']
|
|
187
|
+
credentialFields: ['api_key', 'webhook_secret']
|
|
191
188
|
},
|
|
192
189
|
|
|
193
190
|
requiredAuthMethods: {
|
|
194
191
|
apiPropertiesToPersist: {
|
|
195
|
-
credential: ['api_key', 'webhook_secret'],
|
|
196
|
-
entity: []
|
|
192
|
+
credential: ['api_key', 'webhook_secret'], // These will be auto-encrypted
|
|
193
|
+
entity: []
|
|
197
194
|
},
|
|
198
195
|
// ... other methods
|
|
199
|
-
}
|
|
196
|
+
}
|
|
200
197
|
};
|
|
201
198
|
```
|
|
202
199
|
|
|
203
200
|
**How it works**:
|
|
204
|
-
|
|
205
201
|
1. Module declares `encryption.credentialFields` array
|
|
206
202
|
2. Framework automatically adds `data.` prefix: `['api_key']` → `['data.api_key']`
|
|
207
203
|
3. Fields are merged with core encryption schema on app startup
|
|
208
204
|
4. All modules across all integrations are scanned and combined
|
|
209
205
|
|
|
210
206
|
**Benefits**:
|
|
211
|
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
- ✅ Works seamlessly with `apiPropertiesToPersist`
|
|
207
|
+
- ✅ Module authors control their own security requirements
|
|
208
|
+
- ✅ No need to modify core framework or app configuration
|
|
209
|
+
- ✅ Automatic encryption for API key-based integrations
|
|
210
|
+
- ✅ Works seamlessly with `apiPropertiesToPersist`
|
|
216
211
|
|
|
217
212
|
**Example - API Key Module**:
|
|
218
|
-
|
|
219
213
|
```javascript
|
|
220
214
|
// API Module Definition
|
|
221
215
|
const Definition = {
|
|
222
216
|
moduleName: 'axiscare',
|
|
223
217
|
API: AxisCareApi,
|
|
224
218
|
encryption: {
|
|
225
|
-
credentialFields: ['api_key']
|
|
219
|
+
credentialFields: ['api_key'] // Auto-encrypted as 'data.api_key'
|
|
226
220
|
},
|
|
227
221
|
requiredAuthMethods: {
|
|
228
222
|
apiPropertiesToPersist: {
|
|
229
|
-
credential: ['api_key']
|
|
230
|
-
}
|
|
231
|
-
}
|
|
223
|
+
credential: ['api_key'] // Will be encrypted automatically
|
|
224
|
+
}
|
|
225
|
+
}
|
|
232
226
|
};
|
|
233
227
|
|
|
234
228
|
// API Class (extends ApiKeyRequester)
|
|
235
229
|
class AxisCareApi extends ApiKeyRequester {
|
|
236
230
|
constructor(params) {
|
|
237
231
|
super(params);
|
|
238
|
-
this.api_key = params.api_key;
|
|
232
|
+
this.api_key = params.api_key; // snake_case convention
|
|
239
233
|
}
|
|
240
234
|
}
|
|
241
235
|
```
|
|
242
236
|
|
|
243
237
|
**Example - Custom Authentication**:
|
|
244
|
-
|
|
245
238
|
```javascript
|
|
246
239
|
const Definition = {
|
|
247
240
|
moduleName: 'customService',
|
|
@@ -249,17 +242,16 @@ const Definition = {
|
|
|
249
242
|
credentialFields: [
|
|
250
243
|
'signing_key',
|
|
251
244
|
'webhook_secret',
|
|
252
|
-
'data.custom_nested_field'
|
|
253
|
-
]
|
|
254
|
-
}
|
|
245
|
+
'data.custom_nested_field' // Can specify data. prefix explicitly
|
|
246
|
+
]
|
|
247
|
+
}
|
|
255
248
|
};
|
|
256
249
|
```
|
|
257
250
|
|
|
258
251
|
**Limitations**:
|
|
259
|
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
- Applied globally once - module schemas loaded at app startup
|
|
252
|
+
- Only supports Credential model fields (stored in `credential.data`)
|
|
253
|
+
- Cannot encrypt entity fields or custom models (use app-level schema for those)
|
|
254
|
+
- Applied globally once - module schemas loaded at app startup
|
|
263
255
|
|
|
264
256
|
#### Option 2: App-Level Custom Schema (Integration Developers)
|
|
265
257
|
|
|
@@ -537,9 +529,7 @@ For DocumentDB repositories, use `DocumentDBEncryptionService` to manually encry
|
|
|
537
529
|
#### Usage Example
|
|
538
530
|
|
|
539
531
|
```javascript
|
|
540
|
-
const {
|
|
541
|
-
DocumentDBEncryptionService,
|
|
542
|
-
} = require('../documentdb-encryption-service');
|
|
532
|
+
const { DocumentDBEncryptionService } = require('../documentdb-encryption-service');
|
|
543
533
|
const { insertOne, findOne } = require('../documentdb-utils');
|
|
544
534
|
|
|
545
535
|
class MyRepositoryDocumentDB {
|
|
@@ -549,18 +539,12 @@ class MyRepositoryDocumentDB {
|
|
|
549
539
|
|
|
550
540
|
async create(data) {
|
|
551
541
|
// Encrypt before write
|
|
552
|
-
const encrypted = await this.encryptionService.encryptFields(
|
|
553
|
-
'ModelName',
|
|
554
|
-
data
|
|
555
|
-
);
|
|
542
|
+
const encrypted = await this.encryptionService.encryptFields('ModelName', data);
|
|
556
543
|
const id = await insertOne(this.prisma, 'CollectionName', encrypted);
|
|
557
544
|
|
|
558
545
|
// Decrypt after read
|
|
559
546
|
const doc = await findOne(this.prisma, 'CollectionName', { _id: id });
|
|
560
|
-
const decrypted = await this.encryptionService.decryptFields(
|
|
561
|
-
'ModelName',
|
|
562
|
-
doc
|
|
563
|
-
);
|
|
547
|
+
const decrypted = await this.encryptionService.decryptFields('ModelName', doc);
|
|
564
548
|
|
|
565
549
|
return decrypted;
|
|
566
550
|
}
|
|
@@ -570,10 +554,9 @@ class MyRepositoryDocumentDB {
|
|
|
570
554
|
#### Configuration
|
|
571
555
|
|
|
572
556
|
Uses the same environment variables and Cryptor as the Prisma Extension:
|
|
573
|
-
|
|
574
|
-
-
|
|
575
|
-
-
|
|
576
|
-
- `AES_KEY_ID` + `AES_KEY`: AES encryption (fallback)
|
|
557
|
+
- `STAGE`: Bypasses encryption for dev/test/local
|
|
558
|
+
- `KMS_KEY_ARN`: AWS KMS encryption (production)
|
|
559
|
+
- `AES_KEY_ID` + `AES_KEY`: AES encryption (fallback)
|
|
577
560
|
|
|
578
561
|
## Usage Examples
|
|
579
562
|
|
|
@@ -72,9 +72,7 @@ function validateCustomSchema(schema) {
|
|
|
72
72
|
|
|
73
73
|
for (const fieldPath of config.fields) {
|
|
74
74
|
if (typeof fieldPath !== 'string' || !fieldPath) {
|
|
75
|
-
errors.push(
|
|
76
|
-
`Model "${modelName}" has invalid field path: ${fieldPath}`
|
|
77
|
-
);
|
|
75
|
+
errors.push(`Model "${modelName}" has invalid field path: ${fieldPath}`);
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
// Check if trying to override core fields
|
|
@@ -106,17 +104,13 @@ function registerCustomSchema(schema) {
|
|
|
106
104
|
const validation = validateCustomSchema(schema);
|
|
107
105
|
if (!validation.valid) {
|
|
108
106
|
throw new Error(
|
|
109
|
-
`Invalid custom encryption schema:\n- ${validation.errors.join(
|
|
110
|
-
'\n- '
|
|
111
|
-
)}`
|
|
107
|
+
`Invalid custom encryption schema:\n- ${validation.errors.join('\n- ')}`
|
|
112
108
|
);
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
customSchema = { ...schema };
|
|
116
112
|
logger.info(
|
|
117
|
-
`Registered custom encryption schema for models: ${Object.keys(
|
|
118
|
-
customSchema
|
|
119
|
-
).join(', ')}`
|
|
113
|
+
`Registered custom encryption schema for models: ${Object.keys(customSchema).join(', ')}`
|
|
120
114
|
);
|
|
121
115
|
}
|
|
122
116
|
|
|
@@ -139,9 +133,7 @@ function extractCredentialFieldsFromModules(moduleDefinitions) {
|
|
|
139
133
|
}
|
|
140
134
|
|
|
141
135
|
for (const field of credentialFields) {
|
|
142
|
-
const prefixedField = field.startsWith('data.')
|
|
143
|
-
? field
|
|
144
|
-
: `data.${field}`;
|
|
136
|
+
const prefixedField = field.startsWith('data.') ? field : `data.${field}`;
|
|
145
137
|
fields.push(prefixedField);
|
|
146
138
|
}
|
|
147
139
|
}
|
|
@@ -168,14 +160,10 @@ function loadModuleEncryptionSchemas(integrations) {
|
|
|
168
160
|
return;
|
|
169
161
|
}
|
|
170
162
|
|
|
171
|
-
const {
|
|
172
|
-
getModulesDefinitionFromIntegrationClasses,
|
|
173
|
-
} = require('../integrations/utils/map-integration-dto');
|
|
163
|
+
const { getModulesDefinitionFromIntegrationClasses } = require('../integrations/utils/map-integration-dto');
|
|
174
164
|
|
|
175
|
-
const moduleDefinitions =
|
|
176
|
-
|
|
177
|
-
const credentialFields =
|
|
178
|
-
extractCredentialFieldsFromModules(moduleDefinitions);
|
|
165
|
+
const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrations);
|
|
166
|
+
const credentialFields = extractCredentialFieldsFromModules(moduleDefinitions);
|
|
179
167
|
|
|
180
168
|
if (credentialFields.length === 0) {
|
|
181
169
|
return;
|
|
@@ -183,8 +171,8 @@ function loadModuleEncryptionSchemas(integrations) {
|
|
|
183
171
|
|
|
184
172
|
const moduleSchema = {
|
|
185
173
|
Credential: {
|
|
186
|
-
fields: credentialFields
|
|
187
|
-
}
|
|
174
|
+
fields: credentialFields
|
|
175
|
+
}
|
|
188
176
|
};
|
|
189
177
|
|
|
190
178
|
logger.info(
|
|
@@ -35,9 +35,7 @@ class FieldEncryptionService {
|
|
|
35
35
|
|
|
36
36
|
if (this._shouldEncrypt(value)) {
|
|
37
37
|
const serializedValue = this._serializeForEncryption(value);
|
|
38
|
-
const encryptedValue = await this.cryptor.encrypt(
|
|
39
|
-
serializedValue
|
|
40
|
-
);
|
|
38
|
+
const encryptedValue = await this.cryptor.encrypt(serializedValue);
|
|
41
39
|
return { fieldPath, encryptedValue };
|
|
42
40
|
}
|
|
43
41
|
return null;
|
|
@@ -48,11 +46,7 @@ class FieldEncryptionService {
|
|
|
48
46
|
// Apply encrypted values
|
|
49
47
|
for (const result of results) {
|
|
50
48
|
if (result) {
|
|
51
|
-
this._setNestedValue(
|
|
52
|
-
encrypted,
|
|
53
|
-
result.fieldPath,
|
|
54
|
-
result.encryptedValue
|
|
55
|
-
);
|
|
49
|
+
this._setNestedValue(encrypted, result.fieldPath, result.encryptedValue);
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
|
|
@@ -77,8 +71,7 @@ class FieldEncryptionService {
|
|
|
77
71
|
|
|
78
72
|
if (this._isEncrypted(value)) {
|
|
79
73
|
const decryptedValue = await this.cryptor.decrypt(value);
|
|
80
|
-
const deserializedValue =
|
|
81
|
-
this._deserializeAfterDecryption(decryptedValue);
|
|
74
|
+
const deserializedValue = this._deserializeAfterDecryption(decryptedValue);
|
|
82
75
|
return { fieldPath, decryptedValue: deserializedValue };
|
|
83
76
|
}
|
|
84
77
|
return null;
|
|
@@ -89,11 +82,7 @@ class FieldEncryptionService {
|
|
|
89
82
|
// Apply decrypted values
|
|
90
83
|
for (const result of results) {
|
|
91
84
|
if (result) {
|
|
92
|
-
this._setNestedValue(
|
|
93
|
-
decrypted,
|
|
94
|
-
result.fieldPath,
|
|
95
|
-
result.decryptedValue
|
|
96
|
-
);
|
|
85
|
+
this._setNestedValue(decrypted, result.fieldPath, result.decryptedValue);
|
|
97
86
|
}
|
|
98
87
|
}
|
|
99
88
|
|
|
@@ -30,9 +30,8 @@ class EncryptionLogger {
|
|
|
30
30
|
// Remove potential key material or encrypted data from logs
|
|
31
31
|
if (typeof message === 'string') {
|
|
32
32
|
// Truncate long base64 strings that might be keys or encrypted data
|
|
33
|
-
return message.replace(
|
|
34
|
-
|
|
35
|
-
(match) => `${match.substring(0, 10)}...[${match.length} chars]`
|
|
33
|
+
return message.replace(/([A-Za-z0-9+/=]{50,})/g, (match) =>
|
|
34
|
+
`${match.substring(0, 10)}...[${match.length} chars]`
|
|
36
35
|
);
|
|
37
36
|
}
|
|
38
37
|
return message;
|
|
@@ -12,7 +12,9 @@ function createEncryptionExtension({ cryptor, enabled = true }) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
if (!cryptor) {
|
|
15
|
-
throw new Error(
|
|
15
|
+
throw new Error(
|
|
16
|
+
'Cryptor instance required for encryption extension'
|
|
17
|
+
);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const encryptionService = new FieldEncryptionService({
|
|
@@ -46,10 +48,11 @@ function createEncryptionExtension({ cryptor, enabled = true }) {
|
|
|
46
48
|
|
|
47
49
|
async createMany({ model, args, query }) {
|
|
48
50
|
if (args.data && Array.isArray(args.data)) {
|
|
49
|
-
args.data =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
args.data =
|
|
52
|
+
await encryptionService.encryptFieldsInBulk(
|
|
53
|
+
model,
|
|
54
|
+
args.data
|
|
55
|
+
);
|
|
53
56
|
} else if (args.data) {
|
|
54
57
|
args.data = await encryptionService.encryptFields(
|
|
55
58
|
model,
|
package/database/index.js
CHANGED
|
@@ -42,8 +42,7 @@ module.exports = {
|
|
|
42
42
|
},
|
|
43
43
|
get OrganizationUser() {
|
|
44
44
|
if (!_OrganizationUser) {
|
|
45
|
-
_OrganizationUser =
|
|
46
|
-
require('./models/OrganizationUser').OrganizationUser;
|
|
45
|
+
_OrganizationUser = require('./models/OrganizationUser').OrganizationUser;
|
|
47
46
|
}
|
|
48
47
|
return _OrganizationUser;
|
|
49
48
|
},
|
|
@@ -55,8 +54,7 @@ module.exports = {
|
|
|
55
54
|
},
|
|
56
55
|
get WebsocketConnection() {
|
|
57
56
|
if (!_WebsocketConnection) {
|
|
58
|
-
_WebsocketConnection =
|
|
59
|
-
require('./models/WebsocketConnection').WebsocketConnection;
|
|
57
|
+
_WebsocketConnection = require('./models/WebsocketConnection').WebsocketConnection;
|
|
60
58
|
}
|
|
61
59
|
return _WebsocketConnection;
|
|
62
60
|
},
|