@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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
The current BaseCRMIntegration implementation has a **race condition** in process record updates:
|
|
6
6
|
|
|
7
7
|
1. Multiple queue workers process batches concurrently
|
|
8
|
-
2. Each worker calls `processManager.updateMetrics()`
|
|
8
|
+
2. Each worker calls `processManager.updateMetrics()`
|
|
9
9
|
3. Multiple workers read-modify-write the same process record simultaneously
|
|
10
10
|
4. **Result**: Lost updates, inconsistent metrics, potential data corruption
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ The current BaseCRMIntegration implementation has a **race condition** in proces
|
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
Time 1: Worker A reads process.results.aggregateData.totalSynced = 100
|
|
16
|
-
Time 2: Worker B reads process.results.aggregateData.totalSynced = 100
|
|
16
|
+
Time 2: Worker B reads process.results.aggregateData.totalSynced = 100
|
|
17
17
|
Time 3: Worker A adds 50 → writes totalSynced = 150
|
|
18
18
|
Time 4: Worker B adds 30 → writes totalSynced = 130 (overwrites Worker A's update!)
|
|
19
19
|
```
|
|
@@ -24,10 +24,10 @@ Time 4: Worker B adds 30 → writes totalSynced = 130 (overwrites Worker A's upd
|
|
|
24
24
|
|
|
25
25
|
Create a dedicated FIFO SQS queue in **Frigg Core** for all process management operations:
|
|
26
26
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
27
|
+
- **Queue Type**: FIFO (First-In-First-Out)
|
|
28
|
+
- **Message Group ID**: `process-{processId}` (ensures ordered processing per process)
|
|
29
|
+
- **Message Deduplication**: Enabled (prevents duplicate updates)
|
|
30
|
+
- **Dead Letter Queue**: Enabled (captures failed updates)
|
|
31
31
|
|
|
32
32
|
### Architecture
|
|
33
33
|
|
|
@@ -80,7 +80,7 @@ class ProcessManagementQueueFactory {
|
|
|
80
80
|
*/
|
|
81
81
|
async createProcessManagementQueue(integrationName) {
|
|
82
82
|
const queueName = `${integrationName}-process-management.fifo`;
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
const params = {
|
|
85
85
|
QueueName: queueName,
|
|
86
86
|
Attributes: {
|
|
@@ -92,7 +92,7 @@ class ProcessManagementQueueFactory {
|
|
|
92
92
|
ReceiveMessageWaitTimeSeconds: '20', // Long polling
|
|
93
93
|
DeadLetterTargetArn: `${queueName}-dlq.fifo`, // DLQ
|
|
94
94
|
MaxReceiveCount: '3', // Retry failed messages 3 times
|
|
95
|
-
}
|
|
95
|
+
},
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
const result = await this.sqs.createQueue(params).promise();
|
|
@@ -114,7 +114,7 @@ class ProcessManagementQueueFactory {
|
|
|
114
114
|
processId,
|
|
115
115
|
operation,
|
|
116
116
|
data,
|
|
117
|
-
timestamp: new Date().toISOString()
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
118
|
}),
|
|
119
119
|
MessageGroupId: `process-${processId}`,
|
|
120
120
|
MessageDeduplicationId: `${processId}-${operation}-${Date.now()}`,
|
|
@@ -137,7 +137,9 @@ const {
|
|
|
137
137
|
UpdateProcessMetrics,
|
|
138
138
|
GetProcess,
|
|
139
139
|
} = require('../use-cases');
|
|
140
|
-
const {
|
|
140
|
+
const {
|
|
141
|
+
createProcessRepository,
|
|
142
|
+
} = require('../repositories/process-repository-factory');
|
|
141
143
|
|
|
142
144
|
/**
|
|
143
145
|
* Handler for process management FIFO queue messages
|
|
@@ -146,8 +148,12 @@ const { createProcessRepository } = require('../repositories/process-repository-
|
|
|
146
148
|
class ProcessUpdateHandler {
|
|
147
149
|
constructor() {
|
|
148
150
|
const processRepository = createProcessRepository();
|
|
149
|
-
this.updateProcessStateUseCase = new UpdateProcessState({
|
|
150
|
-
|
|
151
|
+
this.updateProcessStateUseCase = new UpdateProcessState({
|
|
152
|
+
processRepository,
|
|
153
|
+
});
|
|
154
|
+
this.updateProcessMetricsUseCase = new UpdateProcessMetrics({
|
|
155
|
+
processRepository,
|
|
156
|
+
});
|
|
151
157
|
this.getProcessUseCase = new GetProcess({ processRepository });
|
|
152
158
|
}
|
|
153
159
|
|
|
@@ -164,35 +170,35 @@ class ProcessUpdateHandler {
|
|
|
164
170
|
switch (operation) {
|
|
165
171
|
case 'UPDATE_STATE':
|
|
166
172
|
await this.updateProcessStateUseCase.execute(
|
|
167
|
-
processId,
|
|
168
|
-
data.state,
|
|
173
|
+
processId,
|
|
174
|
+
data.state,
|
|
169
175
|
data.contextUpdates
|
|
170
176
|
);
|
|
171
177
|
break;
|
|
172
178
|
|
|
173
179
|
case 'UPDATE_METRICS':
|
|
174
180
|
await this.updateProcessMetricsUseCase.execute(
|
|
175
|
-
processId,
|
|
181
|
+
processId,
|
|
176
182
|
data.metricsUpdate
|
|
177
183
|
);
|
|
178
184
|
break;
|
|
179
185
|
|
|
180
186
|
case 'COMPLETE_PROCESS':
|
|
181
187
|
await this.updateProcessStateUseCase.execute(
|
|
182
|
-
processId,
|
|
183
|
-
'COMPLETED',
|
|
188
|
+
processId,
|
|
189
|
+
'COMPLETED',
|
|
184
190
|
{ endTime: new Date().toISOString() }
|
|
185
191
|
);
|
|
186
192
|
break;
|
|
187
193
|
|
|
188
194
|
case 'HANDLE_ERROR':
|
|
189
195
|
await this.updateProcessStateUseCase.execute(
|
|
190
|
-
processId,
|
|
191
|
-
'ERROR',
|
|
196
|
+
processId,
|
|
197
|
+
'ERROR',
|
|
192
198
|
{
|
|
193
199
|
error: data.error.message,
|
|
194
200
|
errorStack: data.error.stack,
|
|
195
|
-
errorTimestamp: new Date().toISOString()
|
|
201
|
+
errorTimestamp: new Date().toISOString(),
|
|
196
202
|
}
|
|
197
203
|
);
|
|
198
204
|
break;
|
|
@@ -201,7 +207,9 @@ class ProcessUpdateHandler {
|
|
|
201
207
|
throw new Error(`Unknown process operation: ${operation}`);
|
|
202
208
|
}
|
|
203
209
|
|
|
204
|
-
console.log(
|
|
210
|
+
console.log(
|
|
211
|
+
`Process update completed: ${operation} for process ${processId}`
|
|
212
|
+
);
|
|
205
213
|
} catch (error) {
|
|
206
214
|
console.error('Process update failed:', error);
|
|
207
215
|
throw error; // Will trigger SQS retry/DLQ
|
|
@@ -217,7 +225,9 @@ module.exports = { ProcessUpdateHandler };
|
|
|
217
225
|
**File**: `/packages/core/integrations/queues/process-queue-manager.js`
|
|
218
226
|
|
|
219
227
|
```javascript
|
|
220
|
-
const {
|
|
228
|
+
const {
|
|
229
|
+
ProcessManagementQueueFactory,
|
|
230
|
+
} = require('./process-management-queue-factory');
|
|
221
231
|
|
|
222
232
|
/**
|
|
223
233
|
* Manages process update operations via FIFO queue
|
|
@@ -236,7 +246,9 @@ class ProcessQueueManager {
|
|
|
236
246
|
*/
|
|
237
247
|
async getProcessQueueUrl(integrationName) {
|
|
238
248
|
if (!this.queueUrls.has(integrationName)) {
|
|
239
|
-
const queueUrl = await this.factory.createProcessManagementQueue(
|
|
249
|
+
const queueUrl = await this.factory.createProcessManagementQueue(
|
|
250
|
+
integrationName
|
|
251
|
+
);
|
|
240
252
|
this.queueUrls.set(integrationName, queueUrl);
|
|
241
253
|
}
|
|
242
254
|
return this.queueUrls.get(integrationName);
|
|
@@ -250,12 +262,22 @@ class ProcessQueueManager {
|
|
|
250
262
|
* @param {Object} contextUpdates - Context updates
|
|
251
263
|
* @returns {Promise<void>}
|
|
252
264
|
*/
|
|
253
|
-
async queueStateUpdate(
|
|
265
|
+
async queueStateUpdate(
|
|
266
|
+
integrationName,
|
|
267
|
+
processId,
|
|
268
|
+
state,
|
|
269
|
+
contextUpdates = {}
|
|
270
|
+
) {
|
|
254
271
|
const queueUrl = await this.getProcessQueueUrl(integrationName);
|
|
255
|
-
await this.factory.sendProcessUpdate(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
272
|
+
await this.factory.sendProcessUpdate(
|
|
273
|
+
queueUrl,
|
|
274
|
+
processId,
|
|
275
|
+
'UPDATE_STATE',
|
|
276
|
+
{
|
|
277
|
+
state,
|
|
278
|
+
contextUpdates,
|
|
279
|
+
}
|
|
280
|
+
);
|
|
259
281
|
}
|
|
260
282
|
|
|
261
283
|
/**
|
|
@@ -267,9 +289,14 @@ class ProcessQueueManager {
|
|
|
267
289
|
*/
|
|
268
290
|
async queueMetricsUpdate(integrationName, processId, metricsUpdate) {
|
|
269
291
|
const queueUrl = await this.getProcessQueueUrl(integrationName);
|
|
270
|
-
await this.factory.sendProcessUpdate(
|
|
271
|
-
|
|
272
|
-
|
|
292
|
+
await this.factory.sendProcessUpdate(
|
|
293
|
+
queueUrl,
|
|
294
|
+
processId,
|
|
295
|
+
'UPDATE_METRICS',
|
|
296
|
+
{
|
|
297
|
+
metricsUpdate,
|
|
298
|
+
}
|
|
299
|
+
);
|
|
273
300
|
}
|
|
274
301
|
|
|
275
302
|
/**
|
|
@@ -280,7 +307,12 @@ class ProcessQueueManager {
|
|
|
280
307
|
*/
|
|
281
308
|
async queueProcessCompletion(integrationName, processId) {
|
|
282
309
|
const queueUrl = await this.getProcessQueueUrl(integrationName);
|
|
283
|
-
await this.factory.sendProcessUpdate(
|
|
310
|
+
await this.factory.sendProcessUpdate(
|
|
311
|
+
queueUrl,
|
|
312
|
+
processId,
|
|
313
|
+
'COMPLETE_PROCESS',
|
|
314
|
+
{}
|
|
315
|
+
);
|
|
284
316
|
}
|
|
285
317
|
|
|
286
318
|
/**
|
|
@@ -292,12 +324,17 @@ class ProcessQueueManager {
|
|
|
292
324
|
*/
|
|
293
325
|
async queueErrorHandling(integrationName, processId, error) {
|
|
294
326
|
const queueUrl = await this.getProcessQueueUrl(integrationName);
|
|
295
|
-
await this.factory.sendProcessUpdate(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
327
|
+
await this.factory.sendProcessUpdate(
|
|
328
|
+
queueUrl,
|
|
329
|
+
processId,
|
|
330
|
+
'HANDLE_ERROR',
|
|
331
|
+
{
|
|
332
|
+
error: {
|
|
333
|
+
message: error.message,
|
|
334
|
+
stack: error.stack,
|
|
335
|
+
},
|
|
299
336
|
}
|
|
300
|
-
|
|
337
|
+
);
|
|
301
338
|
}
|
|
302
339
|
}
|
|
303
340
|
|
|
@@ -311,7 +348,9 @@ module.exports = { ProcessQueueManager };
|
|
|
311
348
|
**File**: `/Users/sean/Documents/GitHub/quo--frigg/backend/src/base/services/ProcessManager.js`
|
|
312
349
|
|
|
313
350
|
```javascript
|
|
314
|
-
const {
|
|
351
|
+
const {
|
|
352
|
+
ProcessQueueManager,
|
|
353
|
+
} = require('@friggframework/core/integrations/queues/process-queue-manager');
|
|
315
354
|
|
|
316
355
|
class ProcessManager {
|
|
317
356
|
constructor({
|
|
@@ -394,11 +433,11 @@ class ProcessManager {
|
|
|
394
433
|
const attachProcessManagementQueues = (definition, AppDefinition) => {
|
|
395
434
|
for (const integration of AppDefinition.integrations) {
|
|
396
435
|
const integrationName = integration.Definition.name;
|
|
397
|
-
|
|
436
|
+
|
|
398
437
|
// Create FIFO queue for process management
|
|
399
438
|
const processQueueName = `${integrationName}ProcessManagementQueue`;
|
|
400
439
|
const processDLQName = `${integrationName}ProcessManagementDLQ`;
|
|
401
|
-
|
|
440
|
+
|
|
402
441
|
// FIFO Queue
|
|
403
442
|
definition.resources.Resources[processQueueName] = {
|
|
404
443
|
Type: 'AWS::SQS::Queue',
|
|
@@ -411,7 +450,9 @@ const attachProcessManagementQueues = (definition, AppDefinition) => {
|
|
|
411
450
|
DelaySeconds: 0,
|
|
412
451
|
ReceiveMessageWaitTimeSeconds: 20, // Long polling
|
|
413
452
|
RedrivePolicy: {
|
|
414
|
-
deadLetterTargetArn: {
|
|
453
|
+
deadLetterTargetArn: {
|
|
454
|
+
'Fn::GetAtt': [processDLQName, 'Arn'],
|
|
455
|
+
},
|
|
415
456
|
maxReceiveCount: 3,
|
|
416
457
|
},
|
|
417
458
|
},
|
|
@@ -430,15 +471,18 @@ const attachProcessManagementQueues = (definition, AppDefinition) => {
|
|
|
430
471
|
// Process Update Handler Function
|
|
431
472
|
const processHandlerName = `${integrationName}ProcessUpdateHandler`;
|
|
432
473
|
definition.functions[processHandlerName] = {
|
|
433
|
-
handler:
|
|
474
|
+
handler:
|
|
475
|
+
'node_modules/@friggframework/core/handlers/process-update-handler.handler',
|
|
434
476
|
reservedConcurrency: 1, // Process updates sequentially per integration
|
|
435
|
-
events: [
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
477
|
+
events: [
|
|
478
|
+
{
|
|
479
|
+
sqs: {
|
|
480
|
+
arn: { 'Fn::GetAtt': [processQueueName, 'Arn'] },
|
|
481
|
+
batchSize: 1, // Process one update at a time
|
|
482
|
+
maximumBatchingWindowInSeconds: 5,
|
|
483
|
+
},
|
|
440
484
|
},
|
|
441
|
-
|
|
485
|
+
],
|
|
442
486
|
timeout: 30,
|
|
443
487
|
environment: {
|
|
444
488
|
INTEGRATION_NAME: integrationName,
|
|
@@ -451,64 +495,75 @@ const attachProcessManagementQueues = (definition, AppDefinition) => {
|
|
|
451
495
|
## Benefits
|
|
452
496
|
|
|
453
497
|
### ✅ Race Condition Prevention
|
|
454
|
-
|
|
455
|
-
-
|
|
456
|
-
-
|
|
498
|
+
|
|
499
|
+
- FIFO queue ensures ordered processing per process ID
|
|
500
|
+
- MessageGroupId = `process-{processId}` guarantees sequential updates
|
|
501
|
+
- No more lost updates or inconsistent metrics
|
|
457
502
|
|
|
458
503
|
### ✅ Cost Optimization
|
|
459
|
-
|
|
460
|
-
-
|
|
461
|
-
-
|
|
504
|
+
|
|
505
|
+
- Only one FIFO queue per integration (not per process)
|
|
506
|
+
- MessageGroupId provides ordering without expensive per-process queues
|
|
507
|
+
- Long polling reduces API calls
|
|
462
508
|
|
|
463
509
|
### ✅ Reliability
|
|
464
|
-
|
|
465
|
-
-
|
|
466
|
-
-
|
|
510
|
+
|
|
511
|
+
- Dead Letter Queue captures failed updates
|
|
512
|
+
- Retry mechanism with exponential backoff
|
|
513
|
+
- Content-based deduplication prevents duplicate processing
|
|
467
514
|
|
|
468
515
|
### ✅ Scalability
|
|
469
|
-
|
|
470
|
-
-
|
|
471
|
-
-
|
|
516
|
+
|
|
517
|
+
- Each integration has its own process management queue
|
|
518
|
+
- Process updates don't block data processing
|
|
519
|
+
- Can scale process update handlers independently
|
|
472
520
|
|
|
473
521
|
## Migration Strategy
|
|
474
522
|
|
|
475
523
|
### Phase 1: Current Implementation (Native Queue)
|
|
476
|
-
|
|
477
|
-
-
|
|
478
|
-
-
|
|
524
|
+
|
|
525
|
+
- Use existing integration queue for process updates
|
|
526
|
+
- Accept potential race conditions for now
|
|
527
|
+
- Focus on core functionality
|
|
479
528
|
|
|
480
529
|
### Phase 2: FIFO Queue Implementation
|
|
481
|
-
|
|
482
|
-
-
|
|
483
|
-
-
|
|
530
|
+
|
|
531
|
+
- Implement FIFO queue infrastructure in Frigg Core
|
|
532
|
+
- Update ProcessManager to use FIFO queue
|
|
533
|
+
- Deploy with feature flag
|
|
484
534
|
|
|
485
535
|
### Phase 3: Full Migration
|
|
486
|
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
536
|
+
|
|
537
|
+
- Switch all integrations to FIFO queue
|
|
538
|
+
- Remove native queue process update code
|
|
539
|
+
- Monitor for race condition elimination
|
|
489
540
|
|
|
490
541
|
## Cost Analysis
|
|
491
542
|
|
|
492
543
|
### FIFO Queue Costs (per integration)
|
|
493
|
-
|
|
494
|
-
-
|
|
495
|
-
-
|
|
496
|
-
-
|
|
544
|
+
|
|
545
|
+
- **Queue Creation**: Free
|
|
546
|
+
- **Message Storage**: $0.40 per million messages
|
|
547
|
+
- **Message Processing**: $0.40 per million requests
|
|
548
|
+
- **Example**: 10 integrations, 1000 process updates/day = ~$2.40/month
|
|
497
549
|
|
|
498
550
|
### Benefits vs Costs
|
|
499
|
-
|
|
500
|
-
-
|
|
501
|
-
-
|
|
551
|
+
|
|
552
|
+
- **Cost**: ~$2.40/month for 10 integrations
|
|
553
|
+
- **Benefit**: Eliminates race conditions, ensures data consistency
|
|
554
|
+
- **ROI**: High - prevents data corruption and debugging time
|
|
502
555
|
|
|
503
556
|
## Implementation Priority
|
|
504
557
|
|
|
505
558
|
**High Priority** - Race conditions in process updates can cause:
|
|
506
|
-
|
|
507
|
-
-
|
|
508
|
-
-
|
|
509
|
-
-
|
|
559
|
+
|
|
560
|
+
- Lost sync progress
|
|
561
|
+
- Inconsistent metrics
|
|
562
|
+
- Difficult debugging
|
|
563
|
+
- Data integrity issues
|
|
510
564
|
|
|
511
565
|
**Recommended Timeline**:
|
|
566
|
+
|
|
512
567
|
1. **Week 1**: Implement FIFO queue infrastructure in Frigg Core
|
|
513
568
|
2. **Week 2**: Update ProcessManager to use FIFO queue
|
|
514
569
|
3. **Week 3**: Deploy and test with one integration
|
package/encrypt/Cryptor.js
CHANGED
|
@@ -1,58 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cryptor - Encryption Service Adapter
|
|
3
3
|
*
|
|
4
|
-
* Infrastructure Layer adapter for
|
|
5
|
-
*
|
|
4
|
+
* Infrastructure Layer adapter for envelope encryption.
|
|
5
|
+
* Key management is delegated to an EncryptionKeyProviderInterface adapter:
|
|
6
|
+
* - KmsEncryptionKeyProvider (in @friggframework/provider-aws) for AWS KMS
|
|
7
|
+
* - AesEncryptionKeyProvider (in core) for local AES keys
|
|
6
8
|
*
|
|
7
9
|
* Envelope Encryption Pattern:
|
|
8
|
-
* 1. Generate Data Encryption Key (DEK) via
|
|
10
|
+
* 1. Generate Data Encryption Key (DEK) via key provider
|
|
9
11
|
* 2. Encrypt field value with DEK using AES-256-CTR
|
|
10
|
-
* 3.
|
|
12
|
+
* 3. Store encrypted DEK alongside ciphertext
|
|
11
13
|
* 4. Return format: "keyId:encryptedText:encryptedKey"
|
|
12
14
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
15
|
+
* BREAKING CHANGE (v3): A keyProvider must be explicitly provided,
|
|
16
|
+
* or pass { shouldUseAws: false } to auto-create AesEncryptionKeyProvider.
|
|
17
|
+
* For AWS KMS, pass `new KmsEncryptionKeyProvider()` from @friggframework/provider-aws.
|
|
18
|
+
* See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
|
-
const crypto = require('crypto');
|
|
20
|
-
const { KMSClient, GenerateDataKeyCommand, DecryptCommand } = require('@aws-sdk/client-kms');
|
|
21
21
|
const aes = require('./aes');
|
|
22
22
|
|
|
23
23
|
class Cryptor {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @param {boolean} [options.shouldUseAws] - true requires explicit keyProvider; false auto-creates AesEncryptionKeyProvider
|
|
27
|
+
* @param {EncryptionKeyProviderInterface} [options.keyProvider] - Explicit key provider (takes precedence)
|
|
28
|
+
*/
|
|
29
|
+
constructor({ shouldUseAws, keyProvider } = {}) {
|
|
30
|
+
if (keyProvider) {
|
|
31
|
+
this._keyProvider = keyProvider;
|
|
32
|
+
} else if (shouldUseAws) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
'Cryptor with shouldUseAws=true requires an explicit keyProvider. Pass one via constructor options, e.g.:\n' +
|
|
35
|
+
' const { KmsEncryptionKeyProvider } = require("@friggframework/provider-aws");\n' +
|
|
36
|
+
' new Cryptor({ shouldUseAws: true, keyProvider: new KmsEncryptionKeyProvider() })\n' +
|
|
37
|
+
'See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.'
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
// AES mode — no AWS dependency needed
|
|
41
|
+
const { AesEncryptionKeyProvider } = require('./aes-encryption-key-provider');
|
|
42
|
+
this._keyProvider = new AesEncryptionKeyProvider();
|
|
41
43
|
}
|
|
44
|
+
this._shouldUseAws = shouldUseAws;
|
|
45
|
+
}
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Get the key provider.
|
|
49
|
+
* @returns {EncryptionKeyProviderInterface}
|
|
50
|
+
*/
|
|
51
|
+
_getKeyProvider() {
|
|
52
|
+
return this._keyProvider;
|
|
53
|
+
}
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
encryptedKey: Buffer.from(aes.encrypt(randomKey, AES_KEY)).toString(
|
|
49
|
-
'base64'
|
|
50
|
-
),
|
|
51
|
-
plaintext: randomKey,
|
|
52
|
-
};
|
|
55
|
+
async generateDataKey() {
|
|
56
|
+
return this._getKeyProvider().generateDataKey();
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Look up an AES key by identifier from environment variables.
|
|
61
|
+
* Kept for backward compatibility.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} keyId - Key identifier
|
|
64
|
+
* @returns {string} The master key
|
|
65
|
+
*/
|
|
55
66
|
getKeyFromEnvironment(keyId) {
|
|
67
|
+
const provider = this._getKeyProvider();
|
|
68
|
+
if (typeof provider.getKeyFromEnvironment === 'function') {
|
|
69
|
+
return provider.getKeyFromEnvironment(keyId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fallback for providers that don't implement getKeyFromEnvironment
|
|
56
73
|
const availableKeys = {
|
|
57
74
|
[process.env.AES_KEY_ID]: process.env.AES_KEY,
|
|
58
75
|
[process.env.DEPRECATED_AES_KEY_ID]: process.env.DEPRECATED_AES_KEY,
|
|
@@ -68,19 +85,7 @@ class Cryptor {
|
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
async decryptDataKey(keyId, encryptedKey) {
|
|
71
|
-
|
|
72
|
-
const kmsClient = new KMSClient({});
|
|
73
|
-
const command = new DecryptCommand({
|
|
74
|
-
KeyId: keyId,
|
|
75
|
-
CiphertextBlob: encryptedKey,
|
|
76
|
-
});
|
|
77
|
-
const dataKey = await kmsClient.send(command);
|
|
78
|
-
|
|
79
|
-
return dataKey.Plaintext;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const key = this.getKeyFromEnvironment(keyId);
|
|
83
|
-
return aes.decrypt(encryptedKey, key);
|
|
88
|
+
return this._getKeyProvider().decryptDataKey(keyId, encryptedKey);
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
async encrypt(text) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES Encryption Key Provider (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* Local AES-based implementation of EncryptionKeyProviderInterface.
|
|
5
|
+
* Uses environment-variable-based master keys for envelope encryption.
|
|
6
|
+
*
|
|
7
|
+
* No external dependencies — works on any platform.
|
|
8
|
+
*
|
|
9
|
+
* Environment Variables:
|
|
10
|
+
* - AES_KEY_ID: Identifier for the current AES master key
|
|
11
|
+
* - AES_KEY: The current AES master key (32 chars)
|
|
12
|
+
* - DEPRECATED_AES_KEY_ID: (optional) Previous key ID for key rotation
|
|
13
|
+
* - DEPRECATED_AES_KEY: (optional) Previous key for decrypting old data
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
const aes = require('./aes');
|
|
18
|
+
const {
|
|
19
|
+
EncryptionKeyProviderInterface,
|
|
20
|
+
} = require('./encryption-key-provider-interface');
|
|
21
|
+
|
|
22
|
+
class AesEncryptionKeyProvider extends EncryptionKeyProviderInterface {
|
|
23
|
+
/**
|
|
24
|
+
* Generate a data encryption key using local AES
|
|
25
|
+
*
|
|
26
|
+
* Creates a random DEK and encrypts it with the AES master key
|
|
27
|
+
* from environment variables.
|
|
28
|
+
*
|
|
29
|
+
* @returns {Promise<{keyId: string, encryptedKey: string, plaintext: string}>}
|
|
30
|
+
*/
|
|
31
|
+
async generateDataKey() {
|
|
32
|
+
const { AES_KEY, AES_KEY_ID } = process.env;
|
|
33
|
+
const randomKey = crypto.randomBytes(32).toString('hex').slice(0, 32);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
keyId: Buffer.from(AES_KEY_ID).toString('base64'),
|
|
37
|
+
encryptedKey: Buffer.from(aes.encrypt(randomKey, AES_KEY)).toString(
|
|
38
|
+
'base64'
|
|
39
|
+
),
|
|
40
|
+
plaintext: randomKey,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Decrypt a data encryption key using the AES master key
|
|
46
|
+
*
|
|
47
|
+
* Looks up the master key by keyId from environment variables,
|
|
48
|
+
* supporting key rotation via DEPRECATED_AES_KEY.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} keyId - Key identifier to look up in environment
|
|
51
|
+
* @param {string|Buffer} encryptedKey - Encrypted DEK to decrypt
|
|
52
|
+
* @returns {Promise<string>} Decrypted plaintext DEK
|
|
53
|
+
*/
|
|
54
|
+
async decryptDataKey(keyId, encryptedKey) {
|
|
55
|
+
const key = this.getKeyFromEnvironment(keyId);
|
|
56
|
+
return aes.decrypt(encryptedKey, key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Look up an AES master key by its identifier
|
|
61
|
+
*
|
|
62
|
+
* @param {string} keyId - Key identifier
|
|
63
|
+
* @returns {string} The master key
|
|
64
|
+
* @throws {Error} If key not found in environment
|
|
65
|
+
*/
|
|
66
|
+
getKeyFromEnvironment(keyId) {
|
|
67
|
+
const availableKeys = {
|
|
68
|
+
[process.env.AES_KEY_ID]: process.env.AES_KEY,
|
|
69
|
+
[process.env.DEPRECATED_AES_KEY_ID]: process.env.DEPRECATED_AES_KEY,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const key = availableKeys[keyId];
|
|
73
|
+
|
|
74
|
+
if (!key) {
|
|
75
|
+
throw new Error('Encryption key not found');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return key;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { AesEncryptionKeyProvider };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption Key Provider Interface (Port)
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for envelope encryption key operations.
|
|
5
|
+
* Used by Cryptor for generating and decrypting data encryption keys.
|
|
6
|
+
*
|
|
7
|
+
* Following Frigg's hexagonal architecture pattern:
|
|
8
|
+
* - Port defines WHAT the service does (contract)
|
|
9
|
+
* - Adapters implement HOW (AWS KMS, local AES, Vault, etc.)
|
|
10
|
+
*
|
|
11
|
+
* Envelope Encryption Pattern:
|
|
12
|
+
* 1. generateDataKey() creates a fresh DEK (plaintext + encrypted form)
|
|
13
|
+
* 2. Caller encrypts data with the plaintext DEK
|
|
14
|
+
* 3. Caller stores the encrypted DEK alongside the ciphertext
|
|
15
|
+
* 4. decryptDataKey() recovers the plaintext DEK from the encrypted form
|
|
16
|
+
* 5. Caller decrypts data with the recovered DEK
|
|
17
|
+
*/
|
|
18
|
+
class EncryptionKeyProviderInterface {
|
|
19
|
+
/**
|
|
20
|
+
* Generate a new data encryption key
|
|
21
|
+
*
|
|
22
|
+
* Returns both the plaintext key (for immediate encryption) and an
|
|
23
|
+
* encrypted copy (for storage alongside the ciphertext).
|
|
24
|
+
*
|
|
25
|
+
* @returns {Promise<{keyId: string, encryptedKey: string, plaintext: string|Buffer}>}
|
|
26
|
+
* - keyId: Base64-encoded identifier for the master key used
|
|
27
|
+
* - encryptedKey: Base64-encoded encrypted copy of the DEK
|
|
28
|
+
* - plaintext: The raw DEK (string or Buffer) for immediate use
|
|
29
|
+
*/
|
|
30
|
+
async generateDataKey() {
|
|
31
|
+
throw new Error(
|
|
32
|
+
'Method generateDataKey must be implemented by subclass'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Decrypt a previously encrypted data encryption key
|
|
38
|
+
*
|
|
39
|
+
* @param {string} keyId - Identifier of the master key used for encryption
|
|
40
|
+
* @param {string|Buffer} encryptedKey - The encrypted DEK to decrypt
|
|
41
|
+
* @returns {Promise<string|Buffer>} The decrypted plaintext DEK
|
|
42
|
+
*/
|
|
43
|
+
async decryptDataKey(keyId, encryptedKey) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'Method decryptDataKey must be implemented by subclass'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { EncryptionKeyProviderInterface };
|