@friggframework/core 2.0.0-next.44 → 2.0.0-next.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/application/commands/integration-commands.js +19 -0
- package/core/Worker.js +8 -21
- package/credential/repositories/credential-repository-mongo.js +14 -8
- package/credential/repositories/credential-repository-postgres.js +14 -8
- package/credential/repositories/credential-repository.js +3 -8
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +11 -2
- package/database/models/WebsocketConnection.js +11 -10
- package/database/prisma.js +63 -3
- package/database/repositories/health-check-repository-mongodb.js +3 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +3 -2
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +137 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +91 -0
- package/database/utils/mongodb-schema-init.js +106 -0
- package/database/utils/prisma-runner.js +400 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/encrypt/Cryptor.js +14 -16
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22897 -0
- package/generated/prisma-mongodb/index.js +359 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
- package/generated/prisma-mongodb/runtime/binary.js +289 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +362 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +341 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +356 -0
- package/generated/prisma-postgresql/index-browser.js +338 -0
- package/generated/prisma-postgresql/index.d.ts +25071 -0
- package/generated/prisma-postgresql/index.js +381 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
- package/generated/prisma-postgresql/runtime/binary.js +289 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +345 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +363 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/backend-utils.js +118 -3
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/routers/auth.js +1 -1
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +256 -0
- package/handlers/routers/health.js +41 -6
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
- package/handlers/workers/db-migration.js +352 -0
- package/index.js +28 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/integration-base.js +74 -3
- package/integrations/integration-router.js +60 -70
- package/integrations/repositories/integration-repository-interface.js +12 -0
- package/integrations/repositories/integration-repository-mongo.js +32 -0
- package/integrations/repositories/integration-repository-postgres.js +33 -0
- package/integrations/repositories/process-repository-postgres.js +43 -20
- package/integrations/tests/doubles/dummy-integration-class.js +1 -8
- package/integrations/tests/doubles/test-integration-repository.js +2 -2
- package/logs/logger.js +0 -4
- package/modules/entity.js +0 -1
- package/modules/repositories/module-repository-mongo.js +3 -12
- package/modules/repositories/module-repository-postgres.js +0 -11
- package/modules/repositories/module-repository.js +1 -12
- package/modules/use-cases/get-entity-options-by-id.js +1 -1
- package/modules/use-cases/get-module.js +1 -2
- package/modules/use-cases/refresh-entity-options.js +1 -1
- package/modules/use-cases/test-module-auth.js +1 -1
- package/package.json +82 -66
- package/prisma-mongodb/schema.prisma +21 -21
- package/prisma-postgresql/schema.prisma +15 -15
- package/queues/queuer-util.js +28 -15
- package/types/core/index.d.ts +2 -2
- package/types/module-plugin/index.d.ts +0 -2
- package/user/repositories/user-repository-mongo.js +53 -12
- package/user/repositories/user-repository-postgres.js +53 -14
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
- package/user/use-cases/login-user.js +1 -1
- package/user/user.js +18 -2
- package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
- package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
- package/websocket/repositories/websocket-connection-repository.js +11 -10
- package/application/commands/integration-commands.test.js +0 -123
- package/database/encryption/encryption-integration.test.js +0 -553
- package/database/encryption/encryption-schema-registry.test.js +0 -392
- package/database/encryption/field-encryption-service.test.js +0 -525
- package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
- package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
- package/database/encryption/postgres-relation-decryption.test.js +0 -245
- package/database/encryption/prisma-encryption-extension.test.js +0 -439
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/handlers/auth-flow.integration.test.js +0 -147
- package/handlers/integration-event-dispatcher.test.js +0 -141
- package/handlers/routers/health.test.js +0 -210
- package/integrations/tests/use-cases/create-integration.test.js +0 -131
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
- package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
- package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
- package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
- package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
- package/integrations/tests/use-cases/update-integration.test.js +0 -141
- package/integrations/use-cases/create-process.test.js +0 -178
- package/integrations/use-cases/get-process.test.js +0 -190
- package/integrations/use-cases/load-integration-context-full.test.js +0 -329
- package/integrations/use-cases/load-integration-context.test.js +0 -114
- package/integrations/use-cases/update-process-metrics.test.js +0 -308
- package/integrations/use-cases/update-process-state.test.js +0 -256
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/modules/module-hydration.test.js +0 -205
- package/modules/requester/requester.test.js +0 -28
- package/user/tests/use-cases/create-individual-user.test.js +0 -24
- package/user/tests/use-cases/create-organization-user.test.js +0 -28
- package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
- package/user/tests/use-cases/login-user.test.js +0 -140
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Webhook Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get webhooks working in your Frigg integration in 3 simple steps.
|
|
4
|
+
|
|
5
|
+
## Step 1: Enable Webhooks
|
|
6
|
+
|
|
7
|
+
Add `webhooks: true` to your Integration Definition:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
class MyIntegration extends IntegrationBase {
|
|
11
|
+
static Definition = {
|
|
12
|
+
name: 'my-integration',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
modules: {
|
|
15
|
+
myapi: { definition: MyApiDefinition },
|
|
16
|
+
},
|
|
17
|
+
webhooks: true, // ← Add this line
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Step 2: Handle Webhook Processing
|
|
23
|
+
|
|
24
|
+
Override the `onWebhook` handler to process webhooks:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
class MyIntegration extends IntegrationBase {
|
|
28
|
+
// ... Definition ...
|
|
29
|
+
|
|
30
|
+
async onWebhook({ data }) {
|
|
31
|
+
const { body } = data;
|
|
32
|
+
|
|
33
|
+
// You have full access to:
|
|
34
|
+
// - this.myapi (your API modules)
|
|
35
|
+
// - this.config (integration config)
|
|
36
|
+
// - Database operations
|
|
37
|
+
|
|
38
|
+
if (body.event === 'item.created') {
|
|
39
|
+
await this.myapi.api.createItem(body.data);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { processed: true };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Step 3: Deploy
|
|
48
|
+
|
|
49
|
+
Deploy your Frigg app - webhook routes are automatically created:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
POST /api/my-integration-integration/webhooks/:integrationId
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## That's It!
|
|
56
|
+
|
|
57
|
+
The default behavior handles:
|
|
58
|
+
- ✅ Receiving webhooks (instant 200 OK response)
|
|
59
|
+
- ✅ Queuing to SQS
|
|
60
|
+
- ✅ Loading your integration with DB and API modules
|
|
61
|
+
- ✅ Calling your `onWebhook` handler
|
|
62
|
+
|
|
63
|
+
## Optional: Custom Signature Verification
|
|
64
|
+
|
|
65
|
+
Override `onWebhookReceived` for custom signature checks:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
async onWebhookReceived({ req, res }) {
|
|
69
|
+
// Verify signature
|
|
70
|
+
const signature = req.headers['x-webhook-signature'];
|
|
71
|
+
if (!this.verifySignature(req.body, signature)) {
|
|
72
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Queue for processing (default behavior)
|
|
76
|
+
await this.queueWebhook({
|
|
77
|
+
integrationId: req.params.integrationId,
|
|
78
|
+
body: req.body,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
res.status(200).json({ received: true });
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Two Webhook Routes
|
|
86
|
+
|
|
87
|
+
### With Integration ID (Recommended)
|
|
88
|
+
```
|
|
89
|
+
POST /api/{name}-integration/webhooks/:integrationId
|
|
90
|
+
```
|
|
91
|
+
- Full integration loaded in worker
|
|
92
|
+
- Access to DB, config, and API modules
|
|
93
|
+
- Use `this.myapi`, `this.config`, etc.
|
|
94
|
+
|
|
95
|
+
### Without Integration ID
|
|
96
|
+
```
|
|
97
|
+
POST /api/{name}-integration/webhooks
|
|
98
|
+
```
|
|
99
|
+
- Unhydrated integration
|
|
100
|
+
- Useful for system-wide events
|
|
101
|
+
- Limited context
|
|
102
|
+
|
|
103
|
+
## Need Help?
|
|
104
|
+
|
|
105
|
+
See full documentation: `packages/core/handlers/WEBHOOKS.md`
|
|
106
|
+
|
|
107
|
+
## Common Patterns
|
|
108
|
+
|
|
109
|
+
### Slack
|
|
110
|
+
```javascript
|
|
111
|
+
async onWebhookReceived({ req, res }) {
|
|
112
|
+
if (req.body.type === 'url_verification') {
|
|
113
|
+
return res.json({ challenge: req.body.challenge });
|
|
114
|
+
}
|
|
115
|
+
// ... verify signature, queue ...
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Stripe
|
|
120
|
+
```javascript
|
|
121
|
+
async onWebhookReceived({ req, res }) {
|
|
122
|
+
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
|
123
|
+
const event = stripe.webhooks.constructEvent(
|
|
124
|
+
JSON.stringify(req.body),
|
|
125
|
+
req.headers['stripe-signature'],
|
|
126
|
+
process.env.STRIPE_WEBHOOK_SECRET
|
|
127
|
+
);
|
|
128
|
+
await this.queueWebhook({ body: event });
|
|
129
|
+
res.status(200).json({ received: true });
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### GitHub
|
|
134
|
+
```javascript
|
|
135
|
+
async onWebhookReceived({ req, res }) {
|
|
136
|
+
const crypto = require('crypto');
|
|
137
|
+
const signature = req.headers['x-hub-signature-256'];
|
|
138
|
+
const hash = crypto
|
|
139
|
+
.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET)
|
|
140
|
+
.update(JSON.stringify(req.body))
|
|
141
|
+
.digest('hex');
|
|
142
|
+
|
|
143
|
+
if (`sha256=${hash}` !== signature) {
|
|
144
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await this.queueWebhook({ integrationId: req.params.integrationId, body: req.body });
|
|
148
|
+
res.status(200).json({ received: true });
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
@@ -22,6 +22,8 @@ const constantsToBeMigrated = {
|
|
|
22
22
|
GET_USER_ACTIONS: 'GET_USER_ACTIONS',
|
|
23
23
|
GET_USER_ACTION_OPTIONS: 'GET_USER_ACTION_OPTIONS',
|
|
24
24
|
REFRESH_USER_ACTION_OPTIONS: 'REFRESH_USER_ACTION_OPTIONS',
|
|
25
|
+
WEBHOOK_RECEIVED: 'WEBHOOK_RECEIVED', // HTTP handler, no DB
|
|
26
|
+
ON_WEBHOOK: 'ON_WEBHOOK', // Queue worker, DB-connected
|
|
25
27
|
// etc...
|
|
26
28
|
},
|
|
27
29
|
types: {
|
|
@@ -130,6 +132,14 @@ class IntegrationBase {
|
|
|
130
132
|
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
131
133
|
handler: this.refreshActionOptions,
|
|
132
134
|
},
|
|
135
|
+
[constantsToBeMigrated.defaultEvents.WEBHOOK_RECEIVED]: {
|
|
136
|
+
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
137
|
+
handler: this.onWebhookReceived,
|
|
138
|
+
},
|
|
139
|
+
[constantsToBeMigrated.defaultEvents.ON_WEBHOOK]: {
|
|
140
|
+
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
141
|
+
handler: this.onWebhook,
|
|
142
|
+
},
|
|
133
143
|
};
|
|
134
144
|
}
|
|
135
145
|
|
|
@@ -294,7 +304,9 @@ class IntegrationBase {
|
|
|
294
304
|
await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
|
|
295
305
|
}
|
|
296
306
|
|
|
297
|
-
async onUpdate(params) {
|
|
307
|
+
async onUpdate(params) {
|
|
308
|
+
return this.validateConfig();
|
|
309
|
+
}
|
|
298
310
|
|
|
299
311
|
async onDelete(params) {}
|
|
300
312
|
|
|
@@ -357,6 +369,50 @@ class IntegrationBase {
|
|
|
357
369
|
return options;
|
|
358
370
|
}
|
|
359
371
|
|
|
372
|
+
/**
|
|
373
|
+
* WEBHOOK EVENT HANDLERS
|
|
374
|
+
*/
|
|
375
|
+
async onWebhookReceived({ req, res }) {
|
|
376
|
+
// Default: queue webhook for processing
|
|
377
|
+
const body = req.body;
|
|
378
|
+
const integrationId = req.params.integrationId || null;
|
|
379
|
+
|
|
380
|
+
await this.queueWebhook({
|
|
381
|
+
integrationId,
|
|
382
|
+
body,
|
|
383
|
+
headers: req.headers,
|
|
384
|
+
query: req.query,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
res.status(200).json({ received: true });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async onWebhook({ data }) {
|
|
391
|
+
// Default: no-op, integrations override this
|
|
392
|
+
console.log('Webhook received:', data);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async queueWebhook(data) {
|
|
396
|
+
const { QueuerUtil } = require('../queues');
|
|
397
|
+
|
|
398
|
+
const queueName = `${this.constructor.Definition.name
|
|
399
|
+
.toUpperCase()
|
|
400
|
+
.replace(/-/g, '_')}_QUEUE_URL`;
|
|
401
|
+
const queueUrl = process.env[queueName];
|
|
402
|
+
|
|
403
|
+
if (!queueUrl) {
|
|
404
|
+
throw new Error(`Queue URL not found for ${queueName}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return QueuerUtil.send(
|
|
408
|
+
{
|
|
409
|
+
event: 'ON_WEBHOOK',
|
|
410
|
+
data,
|
|
411
|
+
},
|
|
412
|
+
queueUrl
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
360
416
|
// === Domain Methods (moved from Integration.js) ===
|
|
361
417
|
|
|
362
418
|
getConfig() {
|
|
@@ -403,8 +459,14 @@ class IntegrationBase {
|
|
|
403
459
|
return this.userId.toString() === userId.toString();
|
|
404
460
|
}
|
|
405
461
|
|
|
462
|
+
registerEventHandlers() {
|
|
463
|
+
this.on = {
|
|
464
|
+
...this.defaultEvents,
|
|
465
|
+
...this.events,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
406
469
|
async initialize() {
|
|
407
|
-
// Load dynamic user actions
|
|
408
470
|
try {
|
|
409
471
|
const additionalUserActions = await this.loadDynamicUserActions();
|
|
410
472
|
this.events = { ...this.events, ...additionalUserActions };
|
|
@@ -412,7 +474,16 @@ class IntegrationBase {
|
|
|
412
474
|
this.addError(e);
|
|
413
475
|
}
|
|
414
476
|
|
|
415
|
-
|
|
477
|
+
this.registerEventHandlers();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async send(event, object) {
|
|
481
|
+
if (!this.on[event]) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Event ${event} is not defined in the Integration event object`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return this.on[event].handler.call(this, object);
|
|
416
487
|
}
|
|
417
488
|
|
|
418
489
|
getOptionDetails() {
|
|
@@ -56,6 +56,18 @@ const {
|
|
|
56
56
|
const {
|
|
57
57
|
GetUserFromBearerToken,
|
|
58
58
|
} = require('../user/use-cases/get-user-from-bearer-token');
|
|
59
|
+
const {
|
|
60
|
+
GetUserFromXFriggHeaders,
|
|
61
|
+
} = require('../user/use-cases/get-user-from-x-frigg-headers');
|
|
62
|
+
const {
|
|
63
|
+
GetUserFromAdopterJwt,
|
|
64
|
+
} = require('../user/use-cases/get-user-from-adopter-jwt');
|
|
65
|
+
const {
|
|
66
|
+
AuthenticateWithSharedSecret,
|
|
67
|
+
} = require('../user/use-cases/authenticate-with-shared-secret');
|
|
68
|
+
const {
|
|
69
|
+
AuthenticateUser,
|
|
70
|
+
} = require('../user/use-cases/authenticate-user');
|
|
59
71
|
const {
|
|
60
72
|
ProcessAuthorizationCallback,
|
|
61
73
|
} = require('../modules/use-cases/process-authorization-callback');
|
|
@@ -73,6 +85,26 @@ function createIntegrationRouter() {
|
|
|
73
85
|
userConfig,
|
|
74
86
|
});
|
|
75
87
|
|
|
88
|
+
const getUserFromXFriggHeaders = new GetUserFromXFriggHeaders({
|
|
89
|
+
userRepository,
|
|
90
|
+
userConfig,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const getUserFromAdopterJwt = new GetUserFromAdopterJwt({
|
|
94
|
+
userRepository,
|
|
95
|
+
userConfig,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const authenticateWithSharedSecret = new AuthenticateWithSharedSecret();
|
|
99
|
+
|
|
100
|
+
const authenticateUser = new AuthenticateUser({
|
|
101
|
+
getUserFromBearerToken,
|
|
102
|
+
getUserFromXFriggHeaders,
|
|
103
|
+
getUserFromAdopterJwt,
|
|
104
|
+
authenticateWithSharedSecret,
|
|
105
|
+
userConfig,
|
|
106
|
+
});
|
|
107
|
+
|
|
76
108
|
const moduleFactory = new ModuleFactory({
|
|
77
109
|
moduleRepository,
|
|
78
110
|
moduleDefinitions:
|
|
@@ -165,7 +197,7 @@ function createIntegrationRouter() {
|
|
|
165
197
|
|
|
166
198
|
const router = express();
|
|
167
199
|
|
|
168
|
-
setIntegrationRoutes(router,
|
|
200
|
+
setIntegrationRoutes(router, authenticateUser, {
|
|
169
201
|
createIntegration,
|
|
170
202
|
deleteIntegrationForUser,
|
|
171
203
|
getIntegrationsForUser,
|
|
@@ -174,7 +206,7 @@ function createIntegrationRouter() {
|
|
|
174
206
|
updateIntegration,
|
|
175
207
|
getPossibleIntegrations,
|
|
176
208
|
});
|
|
177
|
-
setEntityRoutes(router,
|
|
209
|
+
setEntityRoutes(router, authenticateUser, {
|
|
178
210
|
getCredentialForUser,
|
|
179
211
|
getModuleInstanceFromType,
|
|
180
212
|
getEntityOptionsByType,
|
|
@@ -201,10 +233,8 @@ function checkRequiredParams(params, requiredKeys) {
|
|
|
201
233
|
|
|
202
234
|
if (missingKeys.length > 0) {
|
|
203
235
|
throw Boom.badRequest(
|
|
204
|
-
`Missing Parameter${
|
|
205
|
-
|
|
206
|
-
}: ${missingKeys.join(', ')} ${
|
|
207
|
-
missingKeys.length === 1 ? 'is' : 'are'
|
|
236
|
+
`Missing Parameter${missingKeys.length === 1 ? '' : 's'
|
|
237
|
+
}: ${missingKeys.join(', ')} ${missingKeys.length === 1 ? 'is' : 'are'
|
|
208
238
|
} required.`
|
|
209
239
|
);
|
|
210
240
|
}
|
|
@@ -214,10 +244,10 @@ function checkRequiredParams(params, requiredKeys) {
|
|
|
214
244
|
/**
|
|
215
245
|
* Sets up integration-related routes on the provided Express router
|
|
216
246
|
* @param {express.Router} router - Express router instance to add routes to
|
|
217
|
-
* @param {import('../user/use-cases/
|
|
247
|
+
* @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
|
|
218
248
|
* @param {Object} useCases - use cases for integration management
|
|
219
249
|
*/
|
|
220
|
-
function setIntegrationRoutes(router,
|
|
250
|
+
function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
221
251
|
const {
|
|
222
252
|
createIntegration,
|
|
223
253
|
deleteIntegrationForUser,
|
|
@@ -229,9 +259,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
229
259
|
} = useCases;
|
|
230
260
|
router.route('/api/integrations').get(
|
|
231
261
|
catchAsyncError(async (req, res) => {
|
|
232
|
-
const user = await
|
|
233
|
-
req.headers.authorization
|
|
234
|
-
);
|
|
262
|
+
const user = await authenticateUser.execute(req);
|
|
235
263
|
const userId = user.getId();
|
|
236
264
|
const integrations = await getIntegrationsForUser.execute(userId);
|
|
237
265
|
const results = {
|
|
@@ -248,9 +276,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
248
276
|
|
|
249
277
|
router.route('/api/integrations').post(
|
|
250
278
|
catchAsyncError(async (req, res) => {
|
|
251
|
-
const user = await
|
|
252
|
-
req.headers.authorization
|
|
253
|
-
);
|
|
279
|
+
const user = await authenticateUser.execute(req);
|
|
254
280
|
const userId = user.getId();
|
|
255
281
|
const params = checkRequiredParams(req.body, [
|
|
256
282
|
'entities',
|
|
@@ -271,9 +297,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
271
297
|
|
|
272
298
|
router.route('/api/integrations/:integrationId').patch(
|
|
273
299
|
catchAsyncError(async (req, res) => {
|
|
274
|
-
const user = await
|
|
275
|
-
req.headers.authorization
|
|
276
|
-
);
|
|
300
|
+
const user = await authenticateUser.execute(req);
|
|
277
301
|
const userId = user.getId();
|
|
278
302
|
const params = checkRequiredParams(req.body, ['config']);
|
|
279
303
|
|
|
@@ -288,9 +312,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
288
312
|
|
|
289
313
|
router.route('/api/integrations/:integrationId').delete(
|
|
290
314
|
catchAsyncError(async (req, res) => {
|
|
291
|
-
const user = await
|
|
292
|
-
req.headers.authorization
|
|
293
|
-
);
|
|
315
|
+
const user = await authenticateUser.execute(req);
|
|
294
316
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
295
317
|
await deleteIntegrationForUser.execute(
|
|
296
318
|
params.integrationId,
|
|
@@ -302,9 +324,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
302
324
|
|
|
303
325
|
router.route('/api/integrations/:integrationId/config/options').get(
|
|
304
326
|
catchAsyncError(async (req, res) => {
|
|
305
|
-
const user = await
|
|
306
|
-
req.headers.authorization
|
|
307
|
-
);
|
|
327
|
+
const user = await authenticateUser.execute(req);
|
|
308
328
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
309
329
|
const integration = await getIntegrationInstance.execute(
|
|
310
330
|
params.integrationId,
|
|
@@ -318,9 +338,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
318
338
|
.route('/api/integrations/:integrationId/config/options/refresh')
|
|
319
339
|
.post(
|
|
320
340
|
catchAsyncError(async (req, res) => {
|
|
321
|
-
const user = await
|
|
322
|
-
req.headers.authorization
|
|
323
|
-
);
|
|
341
|
+
const user = await authenticateUser.execute(req);
|
|
324
342
|
const params = checkRequiredParams(req.params, [
|
|
325
343
|
'integrationId',
|
|
326
344
|
]);
|
|
@@ -336,9 +354,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
336
354
|
);
|
|
337
355
|
router.route('/api/integrations/:integrationId/actions').all(
|
|
338
356
|
catchAsyncError(async (req, res) => {
|
|
339
|
-
const user = await
|
|
340
|
-
req.headers.authorization
|
|
341
|
-
);
|
|
357
|
+
const user = await authenticateUser.execute(req);
|
|
342
358
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
343
359
|
const integration = await getIntegrationInstance.execute(
|
|
344
360
|
params.integrationId,
|
|
@@ -352,9 +368,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
352
368
|
.route('/api/integrations/:integrationId/actions/:actionId/options')
|
|
353
369
|
.all(
|
|
354
370
|
catchAsyncError(async (req, res) => {
|
|
355
|
-
const user = await
|
|
356
|
-
req.headers.authorization
|
|
357
|
-
);
|
|
371
|
+
const user = await authenticateUser.execute(req);
|
|
358
372
|
const params = checkRequiredParams(req.params, [
|
|
359
373
|
'integrationId',
|
|
360
374
|
'actionId',
|
|
@@ -379,9 +393,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
379
393
|
)
|
|
380
394
|
.post(
|
|
381
395
|
catchAsyncError(async (req, res) => {
|
|
382
|
-
const user = await
|
|
383
|
-
req.headers.authorization
|
|
384
|
-
);
|
|
396
|
+
const user = await authenticateUser.execute(req);
|
|
385
397
|
const params = checkRequiredParams(req.params, [
|
|
386
398
|
'integrationId',
|
|
387
399
|
'actionId',
|
|
@@ -402,9 +414,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
402
414
|
|
|
403
415
|
router.route('/api/integrations/:integrationId/actions/:actionId').post(
|
|
404
416
|
catchAsyncError(async (req, res) => {
|
|
405
|
-
const user = await
|
|
406
|
-
req.headers.authorization
|
|
407
|
-
);
|
|
417
|
+
const user = await authenticateUser.execute(req);
|
|
408
418
|
const params = checkRequiredParams(req.params, [
|
|
409
419
|
'integrationId',
|
|
410
420
|
'actionId',
|
|
@@ -419,9 +429,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
419
429
|
|
|
420
430
|
router.route('/api/integrations/:integrationId').get(
|
|
421
431
|
catchAsyncError(async (req, res) => {
|
|
422
|
-
const user = await
|
|
423
|
-
req.headers.authorization
|
|
424
|
-
);
|
|
432
|
+
const user = await authenticateUser.execute(req);
|
|
425
433
|
|
|
426
434
|
if (!user) {
|
|
427
435
|
throw Boom.forbidden('User not found');
|
|
@@ -446,9 +454,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
446
454
|
|
|
447
455
|
router.route('/api/integrations/:integrationId/test-auth').get(
|
|
448
456
|
catchAsyncError(async (req, res) => {
|
|
449
|
-
const user = await
|
|
450
|
-
req.headers.authorization
|
|
451
|
-
);
|
|
457
|
+
const user = await authenticateUser.execute(req);
|
|
452
458
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
453
459
|
const instance = await getIntegrationInstance.execute(
|
|
454
460
|
params.integrationId,
|
|
@@ -478,9 +484,9 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
478
484
|
/**
|
|
479
485
|
* Sets up entity-related routes for the integration router
|
|
480
486
|
* @param {Object} router - Express router instance
|
|
481
|
-
* @param {import('../user/use-cases/
|
|
487
|
+
* @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
|
|
482
488
|
*/
|
|
483
|
-
function setEntityRoutes(router,
|
|
489
|
+
function setEntityRoutes(router, authenticateUser, useCases) {
|
|
484
490
|
const {
|
|
485
491
|
getCredentialForUser,
|
|
486
492
|
getModuleInstanceFromType,
|
|
@@ -494,9 +500,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
494
500
|
|
|
495
501
|
router.route('/api/authorize').get(
|
|
496
502
|
catchAsyncError(async (req, res) => {
|
|
497
|
-
const user = await
|
|
498
|
-
req.headers.authorization
|
|
499
|
-
);
|
|
503
|
+
const user = await authenticateUser.execute(req);
|
|
500
504
|
const userId = user.getId();
|
|
501
505
|
const params = checkRequiredParams(req.query, ['entityType']);
|
|
502
506
|
const module = await getModuleInstanceFromType.execute(
|
|
@@ -517,9 +521,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
517
521
|
|
|
518
522
|
router.route('/api/authorize').post(
|
|
519
523
|
catchAsyncError(async (req, res) => {
|
|
520
|
-
const user = await
|
|
521
|
-
req.headers.authorization
|
|
522
|
-
);
|
|
524
|
+
const user = await authenticateUser.execute(req);
|
|
523
525
|
const userId = user.getId();
|
|
524
526
|
const params = checkRequiredParams(req.body, [
|
|
525
527
|
'entityType',
|
|
@@ -538,9 +540,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
538
540
|
|
|
539
541
|
router.route('/api/entity').post(
|
|
540
542
|
catchAsyncError(async (req, res) => {
|
|
541
|
-
const user = await
|
|
542
|
-
req.headers.authorization
|
|
543
|
-
);
|
|
543
|
+
const user = await authenticateUser.execute(req);
|
|
544
544
|
const userId = user.getId();
|
|
545
545
|
const params = checkRequiredParams(req.body, [
|
|
546
546
|
'entityType',
|
|
@@ -575,9 +575,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
575
575
|
|
|
576
576
|
router.route('/api/entity/options/:credentialId').get(
|
|
577
577
|
catchAsyncError(async (req, res) => {
|
|
578
|
-
const user = await
|
|
579
|
-
req.headers.authorization
|
|
580
|
-
);
|
|
578
|
+
const user = await authenticateUser.execute(req);
|
|
581
579
|
const userId = user.getId();
|
|
582
580
|
// TODO May want to pass along the user ID as well so credential ID's can't be fished???
|
|
583
581
|
// TODO **flagging this for review** -MW
|
|
@@ -601,9 +599,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
601
599
|
|
|
602
600
|
router.route('/api/entities/:entityId/test-auth').get(
|
|
603
601
|
catchAsyncError(async (req, res) => {
|
|
604
|
-
const user = await
|
|
605
|
-
req.headers.authorization
|
|
606
|
-
);
|
|
602
|
+
const user = await authenticateUser.execute(req);
|
|
607
603
|
const userId = user.getId();
|
|
608
604
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
609
605
|
const testAuthResponse = await testModuleAuth.execute(
|
|
@@ -630,9 +626,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
630
626
|
|
|
631
627
|
router.route('/api/entities/:entityId').get(
|
|
632
628
|
catchAsyncError(async (req, res) => {
|
|
633
|
-
const user = await
|
|
634
|
-
req.headers.authorization
|
|
635
|
-
);
|
|
629
|
+
const user = await authenticateUser.execute(req);
|
|
636
630
|
const userId = user.getId();
|
|
637
631
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
638
632
|
const module = await getModule.execute(params.entityId, userId);
|
|
@@ -643,9 +637,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
643
637
|
|
|
644
638
|
router.route('/api/entities/:entityId/options').post(
|
|
645
639
|
catchAsyncError(async (req, res) => {
|
|
646
|
-
const user = await
|
|
647
|
-
req.headers.authorization
|
|
648
|
-
);
|
|
640
|
+
const user = await authenticateUser.execute(req);
|
|
649
641
|
const userId = user.getId();
|
|
650
642
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
651
643
|
|
|
@@ -660,9 +652,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
|
|
|
660
652
|
|
|
661
653
|
router.route('/api/entities/:entityId/options/refresh').post(
|
|
662
654
|
catchAsyncError(async (req, res) => {
|
|
663
|
-
const user = await
|
|
664
|
-
req.headers.authorization
|
|
665
|
-
);
|
|
655
|
+
const user = await authenticateUser.execute(req);
|
|
666
656
|
const userId = user.getId();
|
|
667
657
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
668
658
|
const updatedOptions = await refreshEntityOptions.execute(
|
|
@@ -110,6 +110,18 @@ class IntegrationRepositoryInterface {
|
|
|
110
110
|
async findIntegrationByUserId(userId) {
|
|
111
111
|
throw new Error('Method findIntegrationByUserId must be implemented by subclass');
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Update integration configuration
|
|
116
|
+
*
|
|
117
|
+
* @param {string|number} integrationId - Integration ID
|
|
118
|
+
* @param {Object} config - Updated configuration object
|
|
119
|
+
* @returns {Promise<Object>} Updated integration object
|
|
120
|
+
* @abstract
|
|
121
|
+
*/
|
|
122
|
+
async updateIntegrationConfig(integrationId, config) {
|
|
123
|
+
throw new Error('Method updateIntegrationConfig must be implemented by subclass');
|
|
124
|
+
}
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
module.exports = { IntegrationRepositoryInterface };
|
|
@@ -266,6 +266,38 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
266
266
|
messages: integration.messages,
|
|
267
267
|
};
|
|
268
268
|
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Update integration configuration
|
|
272
|
+
* Replaces: IntegrationModel.updateOne({ _id: integrationId }, { config })
|
|
273
|
+
*
|
|
274
|
+
* @param {string} integrationId - Integration ID (MongoDB ObjectId as string)
|
|
275
|
+
* @param {Object} config - Updated configuration object
|
|
276
|
+
* @returns {Promise<Object>} Updated integration object
|
|
277
|
+
*/
|
|
278
|
+
async updateIntegrationConfig(integrationId, config) {
|
|
279
|
+
if (config === null || config === undefined) {
|
|
280
|
+
throw new Error('Config parameter is required');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const integration = await this.prisma.integration.update({
|
|
284
|
+
where: { id: integrationId },
|
|
285
|
+
data: { config },
|
|
286
|
+
include: {
|
|
287
|
+
entities: true,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
id: integration.id,
|
|
293
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
294
|
+
userId: integration.userId,
|
|
295
|
+
config: integration.config,
|
|
296
|
+
version: integration.version,
|
|
297
|
+
status: integration.status,
|
|
298
|
+
messages: integration.messages,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
269
301
|
}
|
|
270
302
|
|
|
271
303
|
module.exports = { IntegrationRepositoryMongo };
|
|
@@ -314,6 +314,39 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
314
314
|
messages: converted.messages,
|
|
315
315
|
};
|
|
316
316
|
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Update integration configuration
|
|
320
|
+
*
|
|
321
|
+
* @param {string} integrationId - Integration ID (string from application layer)
|
|
322
|
+
* @param {Object} config - Updated configuration object
|
|
323
|
+
* @returns {Promise<Object>} Updated integration object with string IDs
|
|
324
|
+
*/
|
|
325
|
+
async updateIntegrationConfig(integrationId, config) {
|
|
326
|
+
if (config === null || config === undefined) {
|
|
327
|
+
throw new Error('Config parameter is required');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const intId = this._convertId(integrationId);
|
|
331
|
+
const integration = await this.prisma.integration.update({
|
|
332
|
+
where: { id: intId },
|
|
333
|
+
data: { config },
|
|
334
|
+
include: {
|
|
335
|
+
entities: true,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const converted = this._convertIntegrationIds(integration);
|
|
340
|
+
return {
|
|
341
|
+
id: converted.id,
|
|
342
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
343
|
+
userId: converted.userId,
|
|
344
|
+
config: converted.config,
|
|
345
|
+
version: converted.version,
|
|
346
|
+
status: converted.status,
|
|
347
|
+
messages: converted.messages,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
317
350
|
}
|
|
318
351
|
|
|
319
352
|
module.exports = { IntegrationRepositoryPostgres };
|