@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.
Files changed (207) hide show
  1. package/CLAUDE.md +28 -40
  2. package/README.md +105 -0
  3. package/application/commands/entity-commands.js +60 -18
  4. package/application/commands/integration-commands.js +20 -73
  5. package/application/commands/scheduler-commands.js +11 -7
  6. package/application/commands/user-commands.js +17 -5
  7. package/application/index.js +6 -2
  8. package/core/Worker.js +31 -7
  9. package/core/create-handler.js +9 -0
  10. package/credential/repositories/credential-repository-documentdb.js +34 -0
  11. package/credential/repositories/credential-repository-interface.js +15 -0
  12. package/credential/repositories/credential-repository-mongo.js +23 -0
  13. package/credential/repositories/credential-repository-postgres.js +28 -0
  14. package/credential/use-cases/credential-operations-for-user.js +202 -0
  15. package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
  16. package/database/config.js +48 -24
  17. package/database/documentdb-encryption-service.js +9 -22
  18. package/database/documentdb-utils.js +2 -0
  19. package/database/encryption/README.md +76 -14
  20. package/database/encryption/documentdb-encryption-service.md +78 -1
  21. package/database/encryption/encryption-config.js +219 -0
  22. package/database/encryption/encryption-schema-registry.js +93 -12
  23. package/database/encryption/integration-mapping-encryption.js +1 -1
  24. package/database/organization-where.js +18 -0
  25. package/database/prisma.js +15 -38
  26. package/database/unique-violation.js +25 -0
  27. package/database/use-cases/check-encryption-health-use-case.js +43 -52
  28. package/errors/index.js +0 -4
  29. package/extensions/bootstrap-runner.js +43 -0
  30. package/extensions/extension-loader.js +187 -0
  31. package/extensions/index.js +41 -0
  32. package/extensions/initialize-app.js +123 -0
  33. package/extensions/route-mounter.js +103 -0
  34. package/extensions/schema-composer.js +188 -0
  35. package/generated/prisma-mongodb/edge.js +43 -4
  36. package/generated/prisma-mongodb/index-browser.js +40 -1
  37. package/generated/prisma-mongodb/index.d.ts +4761 -133
  38. package/generated/prisma-mongodb/index.js +43 -4
  39. package/generated/prisma-mongodb/package.json +1 -1
  40. package/generated/prisma-mongodb/schema.prisma +92 -0
  41. package/generated/prisma-mongodb/wasm.js +43 -4
  42. package/generated/prisma-postgresql/edge.js +43 -4
  43. package/generated/prisma-postgresql/index-browser.js +40 -1
  44. package/generated/prisma-postgresql/index.d.ts +4961 -28
  45. package/generated/prisma-postgresql/index.js +43 -4
  46. package/generated/prisma-postgresql/package.json +1 -1
  47. package/generated/prisma-postgresql/schema.prisma +87 -0
  48. package/generated/prisma-postgresql/wasm.js +43 -4
  49. package/handlers/WEBHOOKS.md +0 -29
  50. package/handlers/app-definition-loader.js +121 -19
  51. package/handlers/app-handler-helpers.js +25 -3
  52. package/handlers/backend-utils.js +31 -25
  53. package/handlers/middleware/admin-auth.js +14 -1
  54. package/handlers/routers/auth.js +46 -10
  55. package/handlers/routers/health.js +13 -4
  56. package/handlers/routers/integration-defined-routers.js +144 -92
  57. package/handlers/routers/integration-webhook-routers.js +102 -64
  58. package/handlers/workers/integration-defined-workers.js +5 -2
  59. package/index.js +65 -3
  60. package/infrastructure/scheduler/index.js +19 -6
  61. package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
  62. package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
  63. package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
  64. package/integrations/index.js +1 -0
  65. package/integrations/integration-base.js +0 -29
  66. package/integrations/integration-router.js +131 -96
  67. package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
  68. package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
  69. package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
  70. package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
  71. package/integrations/repositories/integration-mapping-repository.js +0 -29
  72. package/integrations/repositories/integration-not-found.js +14 -0
  73. package/integrations/repositories/integration-repository-documentdb.js +35 -35
  74. package/integrations/repositories/integration-repository-interface.js +17 -31
  75. package/integrations/repositories/integration-repository-mongo.js +60 -129
  76. package/integrations/repositories/integration-repository-postgres.js +62 -134
  77. package/integrations/repositories/process-repository-mongo.js +1 -1
  78. package/integrations/schedule-config.js +80 -0
  79. package/integrations/tests/doubles/test-integration-repository.js +12 -1
  80. package/integrations/use-cases/create-integration.js +72 -75
  81. package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
  82. package/integrations/use-cases/get-integrations-for-user.js +80 -40
  83. package/integrations/use-cases/index.js +7 -1
  84. package/integrations/use-cases/integration-operations-for-user.js +256 -0
  85. package/integrations/use-cases/resolve-integration-ownership.js +177 -0
  86. package/integrations/utils/map-integration-dto.js +43 -14
  87. package/management-api/create-management-api-router.js +49 -0
  88. package/management-api/error-response.js +116 -0
  89. package/management-api/management-api-config.js +151 -0
  90. package/management-api/meta-router.js +76 -0
  91. package/management-api/openapi/build-openapi.js +144 -0
  92. package/management-api/openapi/components.js +408 -0
  93. package/management-api/openapi/v1-operations.js +83 -0
  94. package/management-api/openapi/v2-operations.js +264 -0
  95. package/management-api/route-registry.js +411 -0
  96. package/management-api/test-support/http-client.js +39 -0
  97. package/management-api/use-cases/get-management-api-meta.js +57 -0
  98. package/management-api/v1-deprecation.js +120 -0
  99. package/management-api/v2/create-v2-router.js +85 -0
  100. package/management-api/v2/dependencies.js +281 -0
  101. package/management-api/v2/dto.js +143 -0
  102. package/management-api/v2/handlers/authorization.js +94 -0
  103. package/management-api/v2/handlers/credentials.js +33 -0
  104. package/management-api/v2/handlers/entities.js +77 -0
  105. package/management-api/v2/handlers/integrations.js +128 -0
  106. package/management-api/v2/handlers/proxy.js +34 -0
  107. package/management-api/v2/request.js +85 -0
  108. package/management-api/v2/test-support/fake-dependencies.js +103 -0
  109. package/modules/domain/authorization-session.js +144 -0
  110. package/modules/entity-scope.js +15 -0
  111. package/modules/entity-type-registry.js +311 -0
  112. package/modules/index.js +14 -0
  113. package/modules/module-factory.js +12 -0
  114. package/modules/module.js +53 -2
  115. package/modules/proxy/proxy-errors.js +20 -0
  116. package/modules/proxy/proxy-path.js +179 -0
  117. package/modules/proxy/proxy-policy.js +248 -0
  118. package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
  119. package/modules/repositories/authorization-session-repository-factory.js +31 -0
  120. package/modules/repositories/authorization-session-repository-interface.js +47 -0
  121. package/modules/repositories/authorization-session-repository-prisma.js +87 -0
  122. package/modules/repositories/entity-not-found.js +18 -0
  123. package/modules/repositories/module-repository-documentdb.js +51 -6
  124. package/modules/repositories/module-repository-interface.js +19 -2
  125. package/modules/repositories/module-repository-mongo.js +67 -74
  126. package/modules/repositories/module-repository-postgres.js +66 -74
  127. package/modules/repositories/module-repository.js +2 -1
  128. package/modules/requester/proxy-request.js +244 -0
  129. package/modules/requester/requester.js +27 -2
  130. package/modules/scope-config.js +192 -0
  131. package/modules/scope-policy.js +112 -0
  132. package/modules/scope-resolution.js +288 -0
  133. package/modules/scope-visibility.js +247 -0
  134. package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
  135. package/modules/tests/doubles/ownership-matching.js +18 -0
  136. package/modules/use-cases/authorize-entity-for-user.js +61 -0
  137. package/modules/use-cases/create-entity-from-credential.js +113 -0
  138. package/modules/use-cases/entity-operations-for-user.js +162 -0
  139. package/modules/use-cases/entity-types.js +139 -0
  140. package/modules/use-cases/execute-entity-proxy-request.js +286 -0
  141. package/modules/use-cases/get-authorization-requirements.js +100 -0
  142. package/modules/use-cases/get-entities-for-user.js +26 -13
  143. package/modules/use-cases/get-entity-module-for-user.js +113 -0
  144. package/modules/use-cases/get-entity-options-by-id.js +10 -4
  145. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  146. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  147. package/modules/use-cases/get-module.js +10 -4
  148. package/modules/use-cases/manage-access.js +21 -0
  149. package/modules/use-cases/multi-step-authorization.js +329 -0
  150. package/modules/use-cases/process-authorization-callback.js +53 -19
  151. package/modules/use-cases/refresh-entity-options.js +10 -4
  152. package/modules/use-cases/test-module-auth.js +10 -4
  153. package/modules/utils/map-module-dto.js +3 -5
  154. package/package.json +6 -5
  155. package/prisma-mongodb/schema.prisma +92 -0
  156. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
  157. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
  158. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
  159. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
  160. package/prisma-postgresql/schema.prisma +87 -0
  161. package/providers/index.js +23 -0
  162. package/providers/resolve-provider.js +93 -0
  163. package/providers/runtime-detection.js +124 -0
  164. package/queues/index.js +25 -0
  165. package/queues/providers/index.js +11 -0
  166. package/queues/providers/netlify-background-provider.js +150 -0
  167. package/queues/providers/qstash-queue-provider.js +219 -0
  168. package/queues/providers/sqs-queue-provider.js +70 -0
  169. package/queues/queue-client-interface.js +55 -0
  170. package/queues/queue-message-auth.js +193 -0
  171. package/queues/queue-provider-factory.js +101 -0
  172. package/queues/queue-provider.js +65 -0
  173. package/queues/sqs-queue-client.js +81 -0
  174. package/types/index.d.ts +2 -0
  175. package/types/modules/index.d.ts +197 -0
  176. package/types/user/index.d.ts +134 -0
  177. package/user/adopter-jwt-config.js +177 -0
  178. package/user/adopter-jwt-errors.js +59 -0
  179. package/user/adopter-token-verifier.js +122 -0
  180. package/user/app-user-helpers.js +97 -0
  181. package/user/create-user-authentication.js +64 -0
  182. package/user/membership.js +51 -0
  183. package/user/repositories/organization-membership-record.js +19 -0
  184. package/user/repositories/organization-membership-repository-documentdb.js +106 -0
  185. package/user/repositories/organization-membership-repository-factory.js +42 -0
  186. package/user/repositories/organization-membership-repository-interface.js +57 -0
  187. package/user/repositories/organization-membership-repository-mongo.js +13 -0
  188. package/user/repositories/organization-membership-repository-postgres.js +29 -0
  189. package/user/repositories/organization-membership-repository-prisma.js +81 -0
  190. package/user/repositories/user-repository-documentdb.js +27 -2
  191. package/user/repositories/user-repository-interface.js +18 -0
  192. package/user/repositories/user-repository-mongo.js +24 -0
  193. package/user/repositories/user-repository-postgres.js +18 -0
  194. package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
  195. package/user/tests/doubles/test-user-repository.js +1 -1
  196. package/user/use-cases/authenticate-user.js +7 -1
  197. package/user/use-cases/ensure-organization-membership.js +92 -0
  198. package/user/use-cases/find-individual-users-by-organization.js +54 -0
  199. package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
  200. package/user/use-cases/get-user-from-bearer-token.js +2 -7
  201. package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
  202. package/user/user.js +64 -1
  203. package/errors/mapping-already-exists-error.js +0 -14
  204. package/integrations/use-cases/add-entities-to-integration.js +0 -92
  205. package/integrations/use-cases/remove-entities-from-integration.js +0 -70
  206. package/integrations/utils/assert-entity-ids-input.js +0 -42
  207. 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
- const { integrations: integrationClasses, userConfig } =
78
- loadAppDefinition();
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
- const getUserFromBearerToken = new GetUserFromBearerToken({
85
- userRepository,
86
- userConfig,
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
- getModulesDefinitionFromIntegrationClasses(integrationClasses),
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
- getModulesDefinitionFromIntegrationClasses(integrationClasses),
178
+ moduleDefinitions,
179
+ scopePolicy,
169
180
  });
170
181
 
171
182
  const getModule = new GetModule({
172
183
  moduleRepository,
173
- moduleDefinitions:
174
- getModulesDefinitionFromIntegrationClasses(integrationClasses),
184
+ moduleDefinitions,
185
+ scopePolicy,
175
186
  });
176
187
 
177
188
  const getEntityOptionsById = new GetEntityOptionsById({
178
189
  moduleRepository,
179
- moduleDefinitions:
180
- getModulesDefinitionFromIntegrationClasses(integrationClasses),
190
+ moduleDefinitions,
191
+ scopePolicy,
181
192
  });
182
193
 
183
194
  const refreshEntityOptions = new RefreshEntityOptions({
184
195
  moduleRepository,
185
- moduleDefinitions:
186
- getModulesDefinitionFromIntegrationClasses(integrationClasses),
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
- getModulesDefinitionFromIntegrationClasses(integrationClasses),
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
- processAuthorizationCallback,
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 userId = user.getId();
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(userId),
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
- userId,
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
- userId,
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.getId()
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.getId()
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.getId()
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.getId()
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.getId()
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.getId()
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.getId()
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.getId()
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.getId()
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
- processAuthorizationCallback,
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
- userId,
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 = await processAuthorizationCallback.execute(
549
- userId,
550
- params.entityType,
551
- params.data
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
- // May want to pass along the user ID as well so credential ID's can't be fished???
577
- const credential = await getCredentialForUser.execute(
578
- params.data.credential_id,
579
- userId
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 })