@friggframework/core 2.0.0--canary.704.79d9a63.0 → 2.0.0--canary.686.e94de4d.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/CLAUDE.md +28 -40
- package/README.md +105 -0
- package/application/commands/entity-commands.js +60 -18
- package/application/commands/integration-commands.js +20 -73
- package/application/commands/scheduler-commands.js +11 -7
- package/application/commands/user-commands.js +17 -5
- package/application/index.js +6 -2
- package/core/Worker.js +31 -7
- package/core/create-handler.js +9 -0
- package/credential/repositories/credential-repository-documentdb.js +34 -0
- package/credential/repositories/credential-repository-interface.js +15 -0
- package/credential/repositories/credential-repository-mongo.js +23 -0
- package/credential/repositories/credential-repository-postgres.js +28 -0
- package/credential/use-cases/credential-operations-for-user.js +202 -0
- package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
- package/database/config.js +48 -24
- package/database/documentdb-encryption-service.js +9 -22
- package/database/documentdb-utils.js +2 -0
- package/database/encryption/README.md +76 -14
- package/database/encryption/documentdb-encryption-service.md +78 -1
- package/database/encryption/encryption-config.js +219 -0
- package/database/encryption/encryption-schema-registry.js +93 -12
- package/database/encryption/integration-mapping-encryption.js +1 -1
- package/database/organization-where.js +18 -0
- package/database/prisma.js +15 -38
- package/database/unique-violation.js +25 -0
- package/database/use-cases/check-encryption-health-use-case.js +43 -52
- package/errors/index.js +0 -4
- package/extensions/bootstrap-runner.js +43 -0
- package/extensions/extension-loader.js +187 -0
- package/extensions/index.js +41 -0
- package/extensions/initialize-app.js +123 -0
- package/extensions/route-mounter.js +103 -0
- package/extensions/schema-composer.js +188 -0
- package/generated/prisma-mongodb/edge.js +43 -4
- package/generated/prisma-mongodb/index-browser.js +40 -1
- package/generated/prisma-mongodb/index.d.ts +4761 -133
- package/generated/prisma-mongodb/index.js +43 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +92 -0
- package/generated/prisma-mongodb/wasm.js +43 -4
- package/generated/prisma-postgresql/edge.js +43 -4
- package/generated/prisma-postgresql/index-browser.js +40 -1
- package/generated/prisma-postgresql/index.d.ts +4961 -28
- package/generated/prisma-postgresql/index.js +43 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +87 -0
- package/generated/prisma-postgresql/wasm.js +43 -4
- package/handlers/WEBHOOKS.md +0 -29
- package/handlers/app-definition-loader.js +121 -19
- package/handlers/app-handler-helpers.js +25 -3
- package/handlers/backend-utils.js +31 -25
- package/handlers/middleware/admin-auth.js +14 -1
- package/handlers/routers/auth.js +46 -10
- package/handlers/routers/health.js +13 -4
- package/handlers/routers/integration-defined-routers.js +144 -92
- package/handlers/routers/integration-webhook-routers.js +102 -64
- package/handlers/workers/integration-defined-workers.js +5 -2
- package/index.js +65 -3
- package/infrastructure/scheduler/index.js +19 -6
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
- package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
- package/integrations/index.js +1 -0
- package/integrations/integration-base.js +0 -29
- package/integrations/integration-router.js +131 -96
- package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
- package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
- package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
- package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
- package/integrations/repositories/integration-mapping-repository.js +0 -29
- package/integrations/repositories/integration-not-found.js +14 -0
- package/integrations/repositories/integration-repository-documentdb.js +35 -35
- package/integrations/repositories/integration-repository-interface.js +17 -31
- package/integrations/repositories/integration-repository-mongo.js +60 -129
- package/integrations/repositories/integration-repository-postgres.js +62 -134
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/schedule-config.js +80 -0
- package/integrations/tests/doubles/test-integration-repository.js +12 -1
- package/integrations/use-cases/create-integration.js +72 -75
- package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
- package/integrations/use-cases/get-integrations-for-user.js +80 -40
- package/integrations/use-cases/index.js +7 -1
- package/integrations/use-cases/integration-operations-for-user.js +256 -0
- package/integrations/use-cases/resolve-integration-ownership.js +177 -0
- package/integrations/utils/map-integration-dto.js +43 -14
- package/management-api/create-management-api-router.js +49 -0
- package/management-api/error-response.js +116 -0
- package/management-api/management-api-config.js +151 -0
- package/management-api/meta-router.js +76 -0
- package/management-api/openapi/build-openapi.js +144 -0
- package/management-api/openapi/components.js +408 -0
- package/management-api/openapi/v1-operations.js +83 -0
- package/management-api/openapi/v2-operations.js +264 -0
- package/management-api/route-registry.js +411 -0
- package/management-api/test-support/http-client.js +39 -0
- package/management-api/use-cases/get-management-api-meta.js +57 -0
- package/management-api/v1-deprecation.js +120 -0
- package/management-api/v2/create-v2-router.js +85 -0
- package/management-api/v2/dependencies.js +281 -0
- package/management-api/v2/dto.js +143 -0
- package/management-api/v2/handlers/authorization.js +94 -0
- package/management-api/v2/handlers/credentials.js +33 -0
- package/management-api/v2/handlers/entities.js +77 -0
- package/management-api/v2/handlers/integrations.js +128 -0
- package/management-api/v2/handlers/proxy.js +34 -0
- package/management-api/v2/request.js +85 -0
- package/management-api/v2/test-support/fake-dependencies.js +103 -0
- package/modules/domain/authorization-session.js +144 -0
- package/modules/entity-scope.js +15 -0
- package/modules/entity-type-registry.js +311 -0
- package/modules/index.js +14 -0
- package/modules/module-factory.js +12 -0
- package/modules/module.js +53 -2
- package/modules/proxy/proxy-errors.js +20 -0
- package/modules/proxy/proxy-path.js +179 -0
- package/modules/proxy/proxy-policy.js +248 -0
- package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
- package/modules/repositories/authorization-session-repository-factory.js +31 -0
- package/modules/repositories/authorization-session-repository-interface.js +47 -0
- package/modules/repositories/authorization-session-repository-prisma.js +87 -0
- package/modules/repositories/entity-not-found.js +18 -0
- package/modules/repositories/module-repository-documentdb.js +51 -6
- package/modules/repositories/module-repository-interface.js +19 -2
- package/modules/repositories/module-repository-mongo.js +67 -74
- package/modules/repositories/module-repository-postgres.js +66 -74
- package/modules/repositories/module-repository.js +2 -1
- package/modules/requester/proxy-request.js +244 -0
- package/modules/requester/requester.js +27 -2
- package/modules/scope-config.js +192 -0
- package/modules/scope-policy.js +112 -0
- package/modules/scope-resolution.js +288 -0
- package/modules/scope-visibility.js +247 -0
- package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
- package/modules/tests/doubles/ownership-matching.js +18 -0
- package/modules/use-cases/authorize-entity-for-user.js +61 -0
- package/modules/use-cases/create-entity-from-credential.js +113 -0
- package/modules/use-cases/entity-operations-for-user.js +162 -0
- package/modules/use-cases/entity-types.js +139 -0
- package/modules/use-cases/execute-entity-proxy-request.js +286 -0
- package/modules/use-cases/get-authorization-requirements.js +100 -0
- package/modules/use-cases/get-entities-for-user.js +26 -13
- package/modules/use-cases/get-entity-module-for-user.js +113 -0
- package/modules/use-cases/get-entity-options-by-id.js +10 -4
- package/modules/use-cases/get-entity-options-by-type.js +1 -1
- package/modules/use-cases/get-module-instance-from-type.js +1 -1
- package/modules/use-cases/get-module.js +10 -4
- package/modules/use-cases/manage-access.js +21 -0
- package/modules/use-cases/multi-step-authorization.js +329 -0
- package/modules/use-cases/process-authorization-callback.js +53 -19
- package/modules/use-cases/refresh-entity-options.js +10 -4
- package/modules/use-cases/test-module-auth.js +10 -4
- package/modules/utils/map-module-dto.js +3 -5
- package/package.json +6 -5
- package/prisma-mongodb/schema.prisma +92 -0
- package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
- package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
- package/prisma-postgresql/schema.prisma +87 -0
- package/providers/index.js +23 -0
- package/providers/resolve-provider.js +93 -0
- package/providers/runtime-detection.js +124 -0
- package/queues/index.js +25 -0
- package/queues/providers/index.js +11 -0
- package/queues/providers/netlify-background-provider.js +150 -0
- package/queues/providers/qstash-queue-provider.js +219 -0
- package/queues/providers/sqs-queue-provider.js +70 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-message-auth.js +193 -0
- package/queues/queue-provider-factory.js +101 -0
- package/queues/queue-provider.js +65 -0
- package/queues/sqs-queue-client.js +81 -0
- package/types/index.d.ts +2 -0
- package/types/modules/index.d.ts +197 -0
- package/types/user/index.d.ts +134 -0
- package/user/adopter-jwt-config.js +177 -0
- package/user/adopter-jwt-errors.js +59 -0
- package/user/adopter-token-verifier.js +122 -0
- package/user/app-user-helpers.js +97 -0
- package/user/create-user-authentication.js +64 -0
- package/user/membership.js +51 -0
- package/user/repositories/organization-membership-record.js +19 -0
- package/user/repositories/organization-membership-repository-documentdb.js +106 -0
- package/user/repositories/organization-membership-repository-factory.js +42 -0
- package/user/repositories/organization-membership-repository-interface.js +57 -0
- package/user/repositories/organization-membership-repository-mongo.js +13 -0
- package/user/repositories/organization-membership-repository-postgres.js +29 -0
- package/user/repositories/organization-membership-repository-prisma.js +81 -0
- package/user/repositories/user-repository-documentdb.js +27 -2
- package/user/repositories/user-repository-interface.js +18 -0
- package/user/repositories/user-repository-mongo.js +24 -0
- package/user/repositories/user-repository-postgres.js +18 -0
- package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
- package/user/tests/doubles/test-user-repository.js +1 -1
- package/user/use-cases/authenticate-user.js +7 -1
- package/user/use-cases/ensure-organization-membership.js +92 -0
- package/user/use-cases/find-individual-users-by-organization.js +54 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
- package/user/use-cases/get-user-from-bearer-token.js +2 -7
- package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
- package/user/user.js +64 -1
- package/errors/mapping-already-exists-error.js +0 -14
- package/integrations/use-cases/add-entities-to-integration.js +0 -92
- package/integrations/use-cases/remove-entities-from-integration.js +0 -70
- package/integrations/utils/assert-entity-ids-input.js +0 -42
- package/modules/utils/to-credential-id.js +0 -14
|
@@ -31,9 +31,6 @@ const {
|
|
|
31
31
|
GetIntegrationInstance,
|
|
32
32
|
} = require('./use-cases/get-integration-instance');
|
|
33
33
|
const { UpdateIntegration } = require('./use-cases/update-integration');
|
|
34
|
-
const {
|
|
35
|
-
getModulesDefinitionFromIntegrationClasses,
|
|
36
|
-
} = require('./utils/map-integration-dto');
|
|
37
34
|
const {
|
|
38
35
|
GetModuleInstanceFromType,
|
|
39
36
|
} = require('../modules/use-cases/get-module-instance-from-type');
|
|
@@ -54,47 +51,51 @@ const {
|
|
|
54
51
|
const {
|
|
55
52
|
createUserRepository,
|
|
56
53
|
} = require('../user/repositories/user-repository-factory');
|
|
57
|
-
const {
|
|
58
|
-
GetUserFromBearerToken,
|
|
59
|
-
} = require('../user/use-cases/get-user-from-bearer-token');
|
|
60
|
-
const {
|
|
61
|
-
GetUserFromXFriggHeaders,
|
|
62
|
-
} = require('../user/use-cases/get-user-from-x-frigg-headers');
|
|
63
|
-
const {
|
|
64
|
-
GetUserFromAdopterJwt,
|
|
65
|
-
} = require('../user/use-cases/get-user-from-adopter-jwt');
|
|
66
54
|
const {
|
|
67
55
|
AuthenticateWithSharedSecret,
|
|
68
56
|
} = require('../user/use-cases/authenticate-with-shared-secret');
|
|
69
57
|
const { AuthenticateUser } = require('../user/use-cases/authenticate-user');
|
|
58
|
+
const {
|
|
59
|
+
buildUserAuthentication,
|
|
60
|
+
} = require('../user/create-user-authentication');
|
|
70
61
|
const {
|
|
71
62
|
ProcessAuthorizationCallback,
|
|
72
63
|
} = require('../modules/use-cases/process-authorization-callback');
|
|
64
|
+
const { createScopePolicy } = require('../modules/scope-policy');
|
|
65
|
+
const {
|
|
66
|
+
AuthorizeEntityForUser,
|
|
67
|
+
} = require('../modules/use-cases/authorize-entity-for-user');
|
|
68
|
+
const {
|
|
69
|
+
CreateEntityFromCredential,
|
|
70
|
+
} = require('../modules/use-cases/create-entity-from-credential');
|
|
71
|
+
const {
|
|
72
|
+
ResolveIntegrationOwnership,
|
|
73
|
+
} = require('./use-cases/resolve-integration-ownership');
|
|
74
|
+
const { GetOwnedIntegrationRecord } = require('./use-cases/integration-operations-for-user');
|
|
73
75
|
|
|
74
76
|
const log = getLogger('frigg.integrations');
|
|
75
77
|
|
|
76
78
|
function createIntegrationRouter() {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
// moduleDefinitions is the app registry's definitions (ADR-059), so
|
|
80
|
+
// /api/authorize and friends accept a type no integration binds.
|
|
81
|
+
const {
|
|
82
|
+
integrations: integrationClasses,
|
|
83
|
+
userConfig,
|
|
84
|
+
moduleDefinitions,
|
|
85
|
+
ownership,
|
|
86
|
+
entityTypeRegistry,
|
|
87
|
+
} = loadAppDefinition();
|
|
88
|
+
// Ownership by scope (ADR-056 §3): one object every use case asks.
|
|
89
|
+
const scopePolicy = createScopePolicy({ ownership, entityTypeRegistry });
|
|
79
90
|
const moduleRepository = createModuleRepository();
|
|
80
91
|
const integrationRepository = createIntegrationRepository();
|
|
81
92
|
const credentialRepository = createCredentialRepository();
|
|
82
93
|
const userRepository = createUserRepository();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const getUserFromXFriggHeaders = new GetUserFromXFriggHeaders({
|
|
90
|
-
userRepository,
|
|
91
|
-
userConfig,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const getUserFromAdopterJwt = new GetUserFromAdopterJwt({
|
|
95
|
-
userRepository,
|
|
96
|
-
userConfig,
|
|
97
|
-
});
|
|
94
|
+
const {
|
|
95
|
+
getUserFromBearerToken,
|
|
96
|
+
getUserFromXFriggHeaders,
|
|
97
|
+
getUserFromAdopterJwt,
|
|
98
|
+
} = buildUserAuthentication({ userConfig, userRepository });
|
|
98
99
|
|
|
99
100
|
const authenticateWithSharedSecret = new AuthenticateWithSharedSecret();
|
|
100
101
|
|
|
@@ -108,8 +109,7 @@ function createIntegrationRouter() {
|
|
|
108
109
|
|
|
109
110
|
const moduleFactory = new ModuleFactory({
|
|
110
111
|
moduleRepository,
|
|
111
|
-
moduleDefinitions
|
|
112
|
-
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
112
|
+
moduleDefinitions,
|
|
113
113
|
});
|
|
114
114
|
const deleteIntegrationForUser = new DeleteIntegrationForUser({
|
|
115
115
|
integrationRepository,
|
|
@@ -122,6 +122,19 @@ function createIntegrationRouter() {
|
|
|
122
122
|
integrationClasses,
|
|
123
123
|
moduleFactory,
|
|
124
124
|
moduleRepository,
|
|
125
|
+
scopePolicy,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Decides who owns a new integration and which entities the caller may bind
|
|
129
|
+
// (the v1 create used to check neither), and who may reach an existing one.
|
|
130
|
+
const resolveIntegrationOwnership = new ResolveIntegrationOwnership({
|
|
131
|
+
moduleRepository,
|
|
132
|
+
integrationClasses,
|
|
133
|
+
scopePolicy,
|
|
134
|
+
});
|
|
135
|
+
const getOwnedIntegrationRecord = new GetOwnedIntegrationRecord({
|
|
136
|
+
integrationRepository,
|
|
137
|
+
scopePolicy,
|
|
125
138
|
});
|
|
126
139
|
|
|
127
140
|
const getCredentialForUser = new GetCredentialForUser({
|
|
@@ -136,8 +149,8 @@ function createIntegrationRouter() {
|
|
|
136
149
|
|
|
137
150
|
const getEntitiesForUser = new GetEntitiesForUser({
|
|
138
151
|
moduleRepository,
|
|
139
|
-
moduleDefinitions
|
|
140
|
-
|
|
152
|
+
moduleDefinitions,
|
|
153
|
+
scopePolicy,
|
|
141
154
|
});
|
|
142
155
|
|
|
143
156
|
const getIntegrationInstance = new GetIntegrationInstance({
|
|
@@ -153,54 +166,65 @@ function createIntegrationRouter() {
|
|
|
153
166
|
});
|
|
154
167
|
|
|
155
168
|
const getModuleInstanceFromType = new GetModuleInstanceFromType({
|
|
156
|
-
moduleDefinitions
|
|
157
|
-
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
169
|
+
moduleDefinitions,
|
|
158
170
|
});
|
|
159
171
|
|
|
160
172
|
const getEntityOptionsByType = new GetEntityOptionsByType({
|
|
161
|
-
moduleDefinitions
|
|
162
|
-
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
173
|
+
moduleDefinitions,
|
|
163
174
|
});
|
|
164
175
|
|
|
165
176
|
const testModuleAuth = new TestModuleAuth({
|
|
166
177
|
moduleRepository,
|
|
167
|
-
moduleDefinitions
|
|
168
|
-
|
|
178
|
+
moduleDefinitions,
|
|
179
|
+
scopePolicy,
|
|
169
180
|
});
|
|
170
181
|
|
|
171
182
|
const getModule = new GetModule({
|
|
172
183
|
moduleRepository,
|
|
173
|
-
moduleDefinitions
|
|
174
|
-
|
|
184
|
+
moduleDefinitions,
|
|
185
|
+
scopePolicy,
|
|
175
186
|
});
|
|
176
187
|
|
|
177
188
|
const getEntityOptionsById = new GetEntityOptionsById({
|
|
178
189
|
moduleRepository,
|
|
179
|
-
moduleDefinitions
|
|
180
|
-
|
|
190
|
+
moduleDefinitions,
|
|
191
|
+
scopePolicy,
|
|
181
192
|
});
|
|
182
193
|
|
|
183
194
|
const refreshEntityOptions = new RefreshEntityOptions({
|
|
184
195
|
moduleRepository,
|
|
185
|
-
moduleDefinitions
|
|
186
|
-
|
|
196
|
+
moduleDefinitions,
|
|
197
|
+
scopePolicy,
|
|
187
198
|
});
|
|
188
199
|
|
|
189
200
|
const getPossibleIntegrations = new GetPossibleIntegrations({
|
|
190
201
|
integrationClasses,
|
|
191
202
|
});
|
|
192
203
|
|
|
204
|
+
// The owner of what an authorization creates follows its scope (ADR-056 §3.2).
|
|
193
205
|
const processAuthorizationCallback = new ProcessAuthorizationCallback({
|
|
194
206
|
moduleRepository,
|
|
195
207
|
credentialRepository,
|
|
196
208
|
integrationRepository,
|
|
197
|
-
moduleDefinitions
|
|
198
|
-
|
|
209
|
+
moduleDefinitions,
|
|
210
|
+
});
|
|
211
|
+
const authorizeEntityForUser = new AuthorizeEntityForUser({
|
|
212
|
+
scopePolicy,
|
|
213
|
+
processAuthorizationCallback,
|
|
214
|
+
});
|
|
215
|
+
// v1 POST /api/entity: same owner resolution as /api/authorize.
|
|
216
|
+
const createEntityFromCredential = new CreateEntityFromCredential({
|
|
217
|
+
authorizeEntityForUser,
|
|
218
|
+
processAuthorizationCallback,
|
|
219
|
+
credentialRepository,
|
|
220
|
+
moduleDefinitions,
|
|
199
221
|
});
|
|
200
222
|
|
|
201
223
|
const router = express();
|
|
202
224
|
|
|
203
225
|
setIntegrationRoutes(router, authenticateUser, {
|
|
226
|
+
resolveIntegrationOwnership,
|
|
227
|
+
getOwnedIntegrationRecord,
|
|
204
228
|
createIntegration,
|
|
205
229
|
deleteIntegrationForUser,
|
|
206
230
|
getIntegrationsForUser,
|
|
@@ -217,7 +241,8 @@ function createIntegrationRouter() {
|
|
|
217
241
|
getModule,
|
|
218
242
|
getEntityOptionsById,
|
|
219
243
|
refreshEntityOptions,
|
|
220
|
-
|
|
244
|
+
authorizeEntityForUser,
|
|
245
|
+
createEntityFromCredential,
|
|
221
246
|
});
|
|
222
247
|
return router;
|
|
223
248
|
}
|
|
@@ -254,6 +279,8 @@ function checkRequiredParams(params, requiredKeys) {
|
|
|
254
279
|
*/
|
|
255
280
|
function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
256
281
|
const {
|
|
282
|
+
resolveIntegrationOwnership,
|
|
283
|
+
getOwnedIntegrationRecord,
|
|
257
284
|
createIntegration,
|
|
258
285
|
deleteIntegrationForUser,
|
|
259
286
|
getIntegrationsForUser,
|
|
@@ -262,15 +289,22 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
262
289
|
updateIntegration,
|
|
263
290
|
getPossibleIntegrations,
|
|
264
291
|
} = useCases;
|
|
292
|
+
|
|
293
|
+
// The owner of an integration the caller may reach (404 otherwise), which is
|
|
294
|
+
// what the id-and-owner use cases below expect. `manage: true` is for
|
|
295
|
+
// changing or deleting: an organization's integration needs an admin (403).
|
|
296
|
+
const ownerOf = async (integrationId, user, options) =>
|
|
297
|
+
(await getOwnedIntegrationRecord.execute(integrationId, user, options))
|
|
298
|
+
.userId;
|
|
299
|
+
|
|
265
300
|
router.route('/api/integrations').get(
|
|
266
301
|
catchAsyncError(async (req, res) => {
|
|
267
302
|
const user = await authenticateUser.execute(req);
|
|
268
|
-
const
|
|
269
|
-
const integrations = await getIntegrationsForUser.execute(userId);
|
|
303
|
+
const integrations = await getIntegrationsForUser.execute(user);
|
|
270
304
|
const results = {
|
|
271
305
|
entities: {
|
|
272
306
|
options: await getPossibleIntegrations.execute(),
|
|
273
|
-
authorized: await getEntitiesForUser.execute(
|
|
307
|
+
authorized: await getEntitiesForUser.execute(user),
|
|
274
308
|
},
|
|
275
309
|
integrations: integrations,
|
|
276
310
|
};
|
|
@@ -282,7 +316,6 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
282
316
|
router.route('/api/integrations').post(
|
|
283
317
|
catchAsyncError(async (req, res) => {
|
|
284
318
|
const user = await authenticateUser.execute(req);
|
|
285
|
-
const userId = user.getId();
|
|
286
319
|
const params = checkRequiredParams(req.body, [
|
|
287
320
|
'entities',
|
|
288
321
|
'config',
|
|
@@ -290,10 +323,17 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
290
323
|
|
|
291
324
|
get(params.config, 'type');
|
|
292
325
|
|
|
326
|
+
const { ownerId, organizationId, adoptUnscoped } =
|
|
327
|
+
await resolveIntegrationOwnership.execute({
|
|
328
|
+
entityIds: params.entities,
|
|
329
|
+
config: params.config,
|
|
330
|
+
user,
|
|
331
|
+
});
|
|
293
332
|
const integration = await createIntegration.execute(
|
|
294
333
|
params.entities,
|
|
295
|
-
|
|
296
|
-
params.config
|
|
334
|
+
ownerId,
|
|
335
|
+
params.config,
|
|
336
|
+
{ organizationId, adoptUnscoped }
|
|
297
337
|
);
|
|
298
338
|
|
|
299
339
|
res.status(201).json(integration);
|
|
@@ -303,12 +343,11 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
303
343
|
router.route('/api/integrations/:integrationId').patch(
|
|
304
344
|
catchAsyncError(async (req, res) => {
|
|
305
345
|
const user = await authenticateUser.execute(req);
|
|
306
|
-
const userId = user.getId();
|
|
307
346
|
const params = checkRequiredParams(req.body, ['config']);
|
|
308
347
|
|
|
309
348
|
const integration = await updateIntegration.execute(
|
|
310
349
|
req.params.integrationId,
|
|
311
|
-
|
|
350
|
+
await ownerOf(req.params.integrationId, user, { manage: true }),
|
|
312
351
|
params.config
|
|
313
352
|
);
|
|
314
353
|
res.json(integration);
|
|
@@ -321,7 +360,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
321
360
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
322
361
|
await deleteIntegrationForUser.execute(
|
|
323
362
|
params.integrationId,
|
|
324
|
-
user
|
|
363
|
+
await ownerOf(params.integrationId, user, { manage: true })
|
|
325
364
|
);
|
|
326
365
|
res.status(204).json({});
|
|
327
366
|
})
|
|
@@ -333,7 +372,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
333
372
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
334
373
|
const integration = await getIntegrationInstance.execute(
|
|
335
374
|
params.integrationId,
|
|
336
|
-
user
|
|
375
|
+
await ownerOf(params.integrationId, user)
|
|
337
376
|
);
|
|
338
377
|
res.json(await integration.send('GET_CONFIG_OPTIONS'));
|
|
339
378
|
})
|
|
@@ -349,7 +388,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
349
388
|
]);
|
|
350
389
|
const integration = await getIntegrationInstance.execute(
|
|
351
390
|
params.integrationId,
|
|
352
|
-
user
|
|
391
|
+
await ownerOf(params.integrationId, user)
|
|
353
392
|
);
|
|
354
393
|
|
|
355
394
|
res.json(
|
|
@@ -363,7 +402,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
363
402
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
364
403
|
const integration = await getIntegrationInstance.execute(
|
|
365
404
|
params.integrationId,
|
|
366
|
-
user
|
|
405
|
+
await ownerOf(params.integrationId, user)
|
|
367
406
|
);
|
|
368
407
|
res.json(await integration.send('GET_USER_ACTIONS', req.body));
|
|
369
408
|
})
|
|
@@ -380,7 +419,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
380
419
|
]);
|
|
381
420
|
const integration = await getIntegrationInstance.execute(
|
|
382
421
|
params.integrationId,
|
|
383
|
-
user
|
|
422
|
+
await ownerOf(params.integrationId, user)
|
|
384
423
|
);
|
|
385
424
|
|
|
386
425
|
res.json(
|
|
@@ -405,7 +444,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
405
444
|
]);
|
|
406
445
|
const integration = await getIntegrationInstance.execute(
|
|
407
446
|
params.integrationId,
|
|
408
|
-
user
|
|
447
|
+
await ownerOf(params.integrationId, user)
|
|
409
448
|
);
|
|
410
449
|
|
|
411
450
|
res.json(
|
|
@@ -426,7 +465,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
426
465
|
]);
|
|
427
466
|
const integration = await getIntegrationInstance.execute(
|
|
428
467
|
params.integrationId,
|
|
429
|
-
user
|
|
468
|
+
await ownerOf(params.integrationId, user)
|
|
430
469
|
);
|
|
431
470
|
res.json(await integration.send(params.actionId, req.body));
|
|
432
471
|
})
|
|
@@ -443,7 +482,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
443
482
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
444
483
|
const integration = await getIntegrationInstance.execute(
|
|
445
484
|
params.integrationId,
|
|
446
|
-
user
|
|
485
|
+
await ownerOf(params.integrationId, user)
|
|
447
486
|
);
|
|
448
487
|
|
|
449
488
|
// We could perhaps augment router with dynamic options? Haven't decided yet, but here may be the place
|
|
@@ -463,7 +502,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
463
502
|
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
464
503
|
const instance = await getIntegrationInstance.execute(
|
|
465
504
|
params.integrationId,
|
|
466
|
-
user
|
|
505
|
+
await ownerOf(params.integrationId, user)
|
|
467
506
|
);
|
|
468
507
|
|
|
469
508
|
if (!instance) {
|
|
@@ -501,16 +540,21 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
501
540
|
getModule,
|
|
502
541
|
getEntityOptionsById,
|
|
503
542
|
refreshEntityOptions,
|
|
504
|
-
|
|
543
|
+
authorizeEntityForUser,
|
|
544
|
+
createEntityFromCredential,
|
|
505
545
|
} = useCases;
|
|
506
546
|
|
|
507
547
|
router.route('/api/authorize').get(
|
|
508
548
|
catchAsyncError(async (req, res) => {
|
|
509
549
|
const user = await authenticateUser.execute(req);
|
|
510
|
-
const userId = user.getId();
|
|
511
550
|
const params = checkRequiredParams(req.query, ['entityType']);
|
|
551
|
+
// The owner POST stores under, so GET and POST describe one connection.
|
|
552
|
+
const { ownerId } = authorizeEntityForUser.resolveOwnership({
|
|
553
|
+
user,
|
|
554
|
+
entityType: params.entityType,
|
|
555
|
+
});
|
|
512
556
|
const module = await getModuleInstanceFromType.execute(
|
|
513
|
-
|
|
557
|
+
ownerId,
|
|
514
558
|
params.entityType,
|
|
515
559
|
{ state: req.query.state }
|
|
516
560
|
);
|
|
@@ -529,11 +573,15 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
529
573
|
router.route('/api/authorize').post(
|
|
530
574
|
catchAsyncError(async (req, res) => {
|
|
531
575
|
const user = await authenticateUser.execute(req);
|
|
532
|
-
const userId = user.getId();
|
|
533
576
|
const params = checkRequiredParams(req.body, [
|
|
534
577
|
'entityType',
|
|
535
578
|
'data',
|
|
536
579
|
]);
|
|
580
|
+
const ownership = authorizeEntityForUser.resolveOwnership({
|
|
581
|
+
user,
|
|
582
|
+
entityType: params.entityType,
|
|
583
|
+
});
|
|
584
|
+
const userId = ownership.ownerId;
|
|
537
585
|
|
|
538
586
|
const dataKeys =
|
|
539
587
|
params.data && typeof params.data === 'object'
|
|
@@ -545,11 +593,13 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
545
593
|
dataKeys,
|
|
546
594
|
});
|
|
547
595
|
|
|
548
|
-
const entityDetails =
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
596
|
+
const { result: entityDetails } =
|
|
597
|
+
await authorizeEntityForUser.execute({
|
|
598
|
+
user,
|
|
599
|
+
entityType: params.entityType,
|
|
600
|
+
data: params.data,
|
|
601
|
+
ownership,
|
|
602
|
+
});
|
|
553
603
|
|
|
554
604
|
log.info('Entity authorized', {
|
|
555
605
|
eventName: 'frigg.integrations.authorized',
|
|
@@ -566,35 +616,20 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
566
616
|
router.route('/api/entity').post(
|
|
567
617
|
catchAsyncError(async (req, res) => {
|
|
568
618
|
const user = await authenticateUser.execute(req);
|
|
569
|
-
const userId = user.getId();
|
|
570
619
|
const params = checkRequiredParams(req.body, [
|
|
571
620
|
'entityType',
|
|
572
621
|
'data',
|
|
573
622
|
]);
|
|
574
623
|
checkRequiredParams(req.body.data, ['credential_id']);
|
|
575
624
|
|
|
576
|
-
//
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
625
|
+
// The owner follows the entity type's scope, as /api/authorize.
|
|
626
|
+
res.json(
|
|
627
|
+
await createEntityFromCredential.execute({
|
|
628
|
+
user,
|
|
629
|
+
entityType: params.entityType,
|
|
630
|
+
credentialId: params.data.credential_id,
|
|
631
|
+
})
|
|
580
632
|
);
|
|
581
|
-
|
|
582
|
-
if (!credential) {
|
|
583
|
-
throw Boom.badRequest('Invalid credential ID');
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const module = await getModuleInstanceFromType.execute(
|
|
587
|
-
userId,
|
|
588
|
-
params.entityType
|
|
589
|
-
);
|
|
590
|
-
const entityDetails = await module.getEntityDetails(
|
|
591
|
-
module.api,
|
|
592
|
-
null,
|
|
593
|
-
null,
|
|
594
|
-
userId
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
res.json(await module.findOrCreateEntity(entityDetails));
|
|
598
633
|
})
|
|
599
634
|
);
|
|
600
635
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
|
-
const { MappingAlreadyExistsError } = require('../../errors');
|
|
3
2
|
const {
|
|
4
3
|
toObjectId,
|
|
5
4
|
fromObjectId,
|
|
@@ -181,55 +180,6 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
|
|
|
181
180
|
return this._mapMapping(decryptedMapping);
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
async createMapping(integrationId, sourceId, mapping) {
|
|
185
|
-
const now = new Date();
|
|
186
|
-
const encryptedDocument = await this.encryptionService.encryptFields(
|
|
187
|
-
'IntegrationMapping',
|
|
188
|
-
{
|
|
189
|
-
integrationId,
|
|
190
|
-
sourceId:
|
|
191
|
-
sourceId === null || sourceId === undefined
|
|
192
|
-
? null
|
|
193
|
-
: String(sourceId),
|
|
194
|
-
mapping,
|
|
195
|
-
createdAt: now,
|
|
196
|
-
updatedAt: now,
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
let insertedId;
|
|
201
|
-
try {
|
|
202
|
-
insertedId = await insertOne(
|
|
203
|
-
this.prisma,
|
|
204
|
-
'IntegrationMapping',
|
|
205
|
-
encryptedDocument
|
|
206
|
-
);
|
|
207
|
-
} catch (error) {
|
|
208
|
-
if (error.code === 11000) {
|
|
209
|
-
throw new MappingAlreadyExistsError(integrationId, sourceId, {
|
|
210
|
-
cause: error,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const created = await findOne(this.prisma, 'IntegrationMapping', {
|
|
217
|
-
_id: insertedId,
|
|
218
|
-
});
|
|
219
|
-
if (!created) {
|
|
220
|
-
throw new Error(
|
|
221
|
-
'Failed to create mapping: Document not found after insert. ' +
|
|
222
|
-
'This indicates a database consistency issue.'
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
return this._mapMapping(
|
|
226
|
-
await this.encryptionService.decryptFields(
|
|
227
|
-
'IntegrationMapping',
|
|
228
|
-
created
|
|
229
|
-
)
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
183
|
async findMappingsByIntegration(integrationId) {
|
|
234
184
|
const filter = {};
|
|
235
185
|
if (integrationId) filter.integrationId = integrationId;
|
|
@@ -39,21 +39,6 @@ class IntegrationMappingRepositoryInterface {
|
|
|
39
39
|
throw new Error('Method upsertMapping must be implemented by subclass');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* Insert a mapping, failing if one already exists for the
|
|
44
|
-
* (integrationId, sourceId) pair. Atomic: usable as a claim/lock row.
|
|
45
|
-
*
|
|
46
|
-
* @param {string|number} integrationId - The integration ID
|
|
47
|
-
* @param {string} sourceId - The source ID for lookup
|
|
48
|
-
* @param {Object} mapping - The mapping data
|
|
49
|
-
* @returns {Promise<Object>} The created mapping document
|
|
50
|
-
* @throws {MappingAlreadyExistsError} If the pair already exists
|
|
51
|
-
* @abstract
|
|
52
|
-
*/
|
|
53
|
-
async createMapping(integrationId, sourceId, mapping) {
|
|
54
|
-
throw new Error('createMapping must be implemented by subclass');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
42
|
/**
|
|
58
43
|
* Find all mappings for an integration
|
|
59
44
|
*
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
|
-
const { MappingAlreadyExistsError } = require('../../errors');
|
|
3
2
|
const {
|
|
4
3
|
assertMappingWrittenUnencrypted,
|
|
5
4
|
decryptQueriedMappings,
|
|
@@ -94,34 +93,6 @@ class IntegrationMappingRepositoryMongo extends IntegrationMappingRepositoryInte
|
|
|
94
93
|
});
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
/**
|
|
98
|
-
* Insert a mapping; fails if (integrationId, sourceId) already exists
|
|
99
|
-
*
|
|
100
|
-
* @param {string} integrationId - The integration ID
|
|
101
|
-
* @param {string} sourceId - The source ID
|
|
102
|
-
* @param {Object} mapping - The mapping data
|
|
103
|
-
* @returns {Promise<Object>} The created mapping document
|
|
104
|
-
* @throws {MappingAlreadyExistsError} If the pair already exists
|
|
105
|
-
*/
|
|
106
|
-
async createMapping(integrationId, sourceId, mapping) {
|
|
107
|
-
try {
|
|
108
|
-
return await this.prisma.integrationMapping.create({
|
|
109
|
-
data: {
|
|
110
|
-
integrationId,
|
|
111
|
-
sourceId: this._toString(sourceId),
|
|
112
|
-
mapping,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
} catch (error) {
|
|
116
|
-
if (error.code === 'P2002') {
|
|
117
|
-
throw new MappingAlreadyExistsError(integrationId, sourceId, {
|
|
118
|
-
cause: error,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
96
|
/**
|
|
126
97
|
* Find all mappings for an integration
|
|
127
98
|
* Replaces: IntegrationMapping.find({ integration: integrationId })
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
|
-
const { MappingAlreadyExistsError } = require('../../errors');
|
|
3
2
|
const {
|
|
4
3
|
assertMappingWrittenUnencrypted,
|
|
5
4
|
decryptQueriedMappings,
|
|
@@ -151,35 +150,6 @@ class IntegrationMappingRepositoryPostgres extends IntegrationMappingRepositoryI
|
|
|
151
150
|
return this._convertMappingIds(result);
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
/**
|
|
155
|
-
* Insert a mapping; fails if (integrationId, sourceId) already exists
|
|
156
|
-
*
|
|
157
|
-
* @param {string} integrationId - The integration ID
|
|
158
|
-
* @param {string} sourceId - The source ID
|
|
159
|
-
* @param {Object} mapping - The mapping data
|
|
160
|
-
* @returns {Promise<Object>} The created mapping document
|
|
161
|
-
* @throws {MappingAlreadyExistsError} If the pair already exists
|
|
162
|
-
*/
|
|
163
|
-
async createMapping(integrationId, sourceId, mapping) {
|
|
164
|
-
try {
|
|
165
|
-
const created = await this.prisma.integrationMapping.create({
|
|
166
|
-
data: {
|
|
167
|
-
integrationId: this._stringToInt(integrationId),
|
|
168
|
-
sourceId: this._toString(sourceId),
|
|
169
|
-
mapping,
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
return this._convertMappingIds(created);
|
|
173
|
-
} catch (error) {
|
|
174
|
-
if (error.code === 'P2002') {
|
|
175
|
-
throw new MappingAlreadyExistsError(integrationId, sourceId, {
|
|
176
|
-
cause: error,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
153
|
/**
|
|
184
154
|
* Find all mappings for an integration
|
|
185
155
|
* Replaces: IntegrationMapping.find({ integration: integrationId })
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
|
-
const { MappingAlreadyExistsError } = require('../../errors');
|
|
3
2
|
const {
|
|
4
3
|
IntegrationMappingRepositoryInterface,
|
|
5
4
|
} = require('./integration-mapping-repository-interface');
|
|
@@ -70,34 +69,6 @@ class IntegrationMappingRepository extends IntegrationMappingRepositoryInterface
|
|
|
70
69
|
});
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
/**
|
|
74
|
-
* Insert a mapping; fails if (integrationId, sourceId) already exists
|
|
75
|
-
*
|
|
76
|
-
* @param {string} integrationId - The integration ID
|
|
77
|
-
* @param {string} sourceId - The source ID
|
|
78
|
-
* @param {Object} mapping - The mapping data
|
|
79
|
-
* @returns {Promise<Object>} The created mapping document
|
|
80
|
-
* @throws {MappingAlreadyExistsError} If the pair already exists
|
|
81
|
-
*/
|
|
82
|
-
async createMapping(integrationId, sourceId, mapping) {
|
|
83
|
-
try {
|
|
84
|
-
return await this.prisma.integrationMapping.create({
|
|
85
|
-
data: {
|
|
86
|
-
integrationId,
|
|
87
|
-
sourceId,
|
|
88
|
-
mapping,
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
} catch (error) {
|
|
92
|
-
if (error.code === 'P2002') {
|
|
93
|
-
throw new MappingAlreadyExistsError(integrationId, sourceId, {
|
|
94
|
-
cause: error,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
72
|
/**
|
|
102
73
|
* Find all mappings for an integration
|
|
103
74
|
* Replaces: IntegrationMapping.find({ integration: integrationId })
|