@friggframework/core 2.0.0--canary.703.12b980b.0 → 2.0.0--canary.707.df80bfd.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 (308) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CLAUDE.md +28 -0
  3. package/README.md +105 -0
  4. package/application/commands/integration-commands.js +20 -10
  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 -2
  19. package/database/encryption/README.md +78 -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 +121 -24
  23. package/database/encryption/field-encryption-service.js +69 -5
  24. package/database/encryption/integration-mapping-encryption.js +1 -1
  25. package/database/encryption/test-support/fake-ciphertext.js +15 -0
  26. package/database/organization-where.js +18 -0
  27. package/database/prisma.js +15 -38
  28. package/database/unique-violation.js +25 -0
  29. package/database/use-cases/check-encryption-health-use-case.js +43 -52
  30. package/errors/index.js +0 -4
  31. package/extensions/bootstrap-runner.js +43 -0
  32. package/extensions/extension-loader.js +230 -0
  33. package/extensions/index.js +44 -0
  34. package/extensions/initialize-app.js +123 -0
  35. package/extensions/prisma-composer.js +270 -0
  36. package/extensions/route-mounter.js +103 -0
  37. package/extensions/schema-composer.js +188 -0
  38. package/generated/prisma-mongodb/edge.js +33 -42
  39. package/generated/prisma-mongodb/index-browser.js +30 -39
  40. package/generated/prisma-mongodb/index.d.ts +3187 -2005
  41. package/generated/prisma-mongodb/index.js +33 -42
  42. package/generated/prisma-mongodb/package.json +1 -1
  43. package/generated/prisma-mongodb/schema.prisma +86 -59
  44. package/generated/prisma-mongodb/wasm.js +33 -42
  45. package/generated/prisma-postgresql/edge.js +33 -42
  46. package/generated/prisma-postgresql/index-browser.js +30 -39
  47. package/generated/prisma-postgresql/index.d.ts +3380 -2177
  48. package/generated/prisma-postgresql/index.js +33 -42
  49. package/generated/prisma-postgresql/package.json +1 -1
  50. package/generated/prisma-postgresql/schema.prisma +81 -57
  51. package/generated/prisma-postgresql/wasm.js +33 -42
  52. package/handlers/WEBHOOKS.md +0 -29
  53. package/handlers/app-definition-loader.js +128 -19
  54. package/handlers/app-handler-helpers.js +27 -8
  55. package/handlers/backend-utils.js +31 -25
  56. package/handlers/integration-event-dispatcher.js +3 -7
  57. package/handlers/middleware/admin-auth.js +14 -1
  58. package/handlers/routers/auth.js +46 -10
  59. package/handlers/routers/health.js +13 -4
  60. package/handlers/routers/integration-defined-routers.js +144 -92
  61. package/handlers/routers/integration-webhook-routers.js +100 -50
  62. package/handlers/workers/integration-defined-workers.js +5 -2
  63. package/index.js +66 -16
  64. package/infrastructure/scheduler/index.js +19 -6
  65. package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
  66. package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
  67. package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
  68. package/integrations/index.js +1 -14
  69. package/integrations/integration-base.js +2 -163
  70. package/integrations/integration-router.js +132 -102
  71. package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
  72. package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
  73. package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
  74. package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
  75. package/integrations/repositories/integration-mapping-repository.js +0 -29
  76. package/integrations/repositories/integration-not-found.js +14 -0
  77. package/integrations/repositories/integration-repository-documentdb.js +37 -9
  78. package/integrations/repositories/integration-repository-interface.js +17 -4
  79. package/integrations/repositories/integration-repository-mongo.js +64 -96
  80. package/integrations/repositories/integration-repository-postgres.js +63 -81
  81. package/integrations/repositories/process-repository-mongo.js +1 -1
  82. package/integrations/schedule-config.js +80 -0
  83. package/integrations/tests/doubles/test-integration-repository.js +12 -1
  84. package/integrations/use-cases/create-integration.js +56 -31
  85. package/integrations/use-cases/delete-integration-for-user.js +4 -4
  86. package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
  87. package/integrations/use-cases/get-integration-for-user.js +2 -4
  88. package/integrations/use-cases/get-integration-instance.js +4 -4
  89. package/integrations/use-cases/get-integrations-for-user.js +79 -39
  90. package/integrations/use-cases/get-possible-integrations.js +2 -7
  91. package/integrations/use-cases/index.js +7 -1
  92. package/integrations/use-cases/integration-operations-for-user.js +256 -0
  93. package/integrations/use-cases/resolve-integration-ownership.js +177 -0
  94. package/integrations/use-cases/update-integration.js +4 -4
  95. package/integrations/utils/map-integration-dto.js +43 -14
  96. package/management-api/create-management-api-router.js +49 -0
  97. package/management-api/error-response.js +116 -0
  98. package/management-api/management-api-config.js +151 -0
  99. package/management-api/meta-router.js +76 -0
  100. package/management-api/openapi/build-openapi.js +144 -0
  101. package/management-api/openapi/components.js +415 -0
  102. package/management-api/openapi/v1-operations.js +83 -0
  103. package/management-api/openapi/v2-operations.js +264 -0
  104. package/management-api/route-registry.js +411 -0
  105. package/management-api/test-support/http-client.js +39 -0
  106. package/management-api/use-cases/get-management-api-meta.js +57 -0
  107. package/management-api/v1-deprecation.js +120 -0
  108. package/management-api/v2/create-v2-router.js +85 -0
  109. package/management-api/v2/dependencies.js +281 -0
  110. package/management-api/v2/dto.js +143 -0
  111. package/management-api/v2/handlers/authorization.js +94 -0
  112. package/management-api/v2/handlers/credentials.js +33 -0
  113. package/management-api/v2/handlers/entities.js +77 -0
  114. package/management-api/v2/handlers/integrations.js +128 -0
  115. package/management-api/v2/handlers/proxy.js +34 -0
  116. package/management-api/v2/request.js +85 -0
  117. package/management-api/v2/test-support/fake-dependencies.js +103 -0
  118. package/modules/domain/authorization-session.js +144 -0
  119. package/modules/entity-scope.js +15 -0
  120. package/modules/entity-type-registry.js +311 -0
  121. package/modules/index.js +14 -0
  122. package/modules/module-factory.js +12 -0
  123. package/modules/module.js +53 -2
  124. package/modules/proxy/proxy-bases.js +169 -0
  125. package/modules/proxy/proxy-errors.js +31 -0
  126. package/modules/proxy/proxy-path.js +183 -0
  127. package/modules/proxy/proxy-policy.js +277 -0
  128. package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
  129. package/modules/repositories/authorization-session-repository-factory.js +31 -0
  130. package/modules/repositories/authorization-session-repository-interface.js +47 -0
  131. package/modules/repositories/authorization-session-repository-prisma.js +87 -0
  132. package/modules/repositories/entity-not-found.js +18 -0
  133. package/modules/repositories/module-repository-documentdb.js +51 -6
  134. package/modules/repositories/module-repository-interface.js +19 -2
  135. package/modules/repositories/module-repository-mongo.js +67 -74
  136. package/modules/repositories/module-repository-postgres.js +66 -74
  137. package/modules/repositories/module-repository.js +2 -1
  138. package/modules/requester/proxy-request.js +245 -0
  139. package/modules/requester/requester.js +35 -5
  140. package/modules/scope-config.js +192 -0
  141. package/modules/scope-policy.js +112 -0
  142. package/modules/scope-resolution.js +288 -0
  143. package/modules/scope-visibility.js +247 -0
  144. package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
  145. package/modules/tests/doubles/ownership-matching.js +18 -0
  146. package/modules/use-cases/authorize-entity-for-user.js +61 -0
  147. package/modules/use-cases/create-entity-from-credential.js +113 -0
  148. package/modules/use-cases/entity-operations-for-user.js +162 -0
  149. package/modules/use-cases/entity-types.js +139 -0
  150. package/modules/use-cases/execute-entity-proxy-request.js +331 -0
  151. package/modules/use-cases/get-authorization-requirements.js +100 -0
  152. package/modules/use-cases/get-entities-for-user.js +26 -13
  153. package/modules/use-cases/get-entity-module-for-user.js +113 -0
  154. package/modules/use-cases/get-entity-options-by-id.js +10 -4
  155. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  156. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  157. package/modules/use-cases/get-module.js +10 -4
  158. package/modules/use-cases/manage-access.js +21 -0
  159. package/modules/use-cases/multi-step-authorization.js +329 -0
  160. package/modules/use-cases/process-authorization-callback.js +53 -19
  161. package/modules/use-cases/refresh-entity-options.js +10 -4
  162. package/modules/use-cases/test-module-auth.js +10 -4
  163. package/package.json +6 -7
  164. package/prisma-mongodb/schema.prisma +86 -59
  165. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
  166. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
  167. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
  168. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
  169. package/prisma-postgresql/schema.prisma +81 -57
  170. package/providers/index.js +23 -0
  171. package/providers/resolve-provider.js +93 -0
  172. package/providers/runtime-detection.js +124 -0
  173. package/queues/index.js +25 -0
  174. package/queues/providers/index.js +11 -0
  175. package/queues/providers/netlify-background-provider.js +150 -0
  176. package/queues/providers/qstash-queue-provider.js +219 -0
  177. package/queues/providers/sqs-queue-provider.js +70 -0
  178. package/queues/queue-client-interface.js +55 -0
  179. package/queues/queue-message-auth.js +193 -0
  180. package/queues/queue-provider-factory.js +101 -0
  181. package/queues/queue-provider.js +65 -0
  182. package/queues/queuer-util.js +2 -9
  183. package/queues/sqs-queue-client.js +81 -0
  184. package/types/index.d.ts +2 -0
  185. package/types/modules/index.d.ts +214 -0
  186. package/types/user/index.d.ts +134 -0
  187. package/user/adopter-jwt-config.js +177 -0
  188. package/user/adopter-jwt-errors.js +59 -0
  189. package/user/adopter-token-verifier.js +122 -0
  190. package/user/app-user-helpers.js +97 -0
  191. package/user/create-user-authentication.js +64 -0
  192. package/user/membership.js +51 -0
  193. package/user/repositories/organization-membership-record.js +19 -0
  194. package/user/repositories/organization-membership-repository-documentdb.js +106 -0
  195. package/user/repositories/organization-membership-repository-factory.js +42 -0
  196. package/user/repositories/organization-membership-repository-interface.js +57 -0
  197. package/user/repositories/organization-membership-repository-mongo.js +13 -0
  198. package/user/repositories/organization-membership-repository-postgres.js +29 -0
  199. package/user/repositories/organization-membership-repository-prisma.js +81 -0
  200. package/user/repositories/user-repository-documentdb.js +27 -2
  201. package/user/repositories/user-repository-interface.js +18 -0
  202. package/user/repositories/user-repository-mongo.js +24 -0
  203. package/user/repositories/user-repository-postgres.js +18 -0
  204. package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
  205. package/user/use-cases/authenticate-user.js +7 -1
  206. package/user/use-cases/ensure-organization-membership.js +92 -0
  207. package/user/use-cases/find-individual-users-by-organization.js +54 -0
  208. package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
  209. package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
  210. package/user/user.js +64 -1
  211. package/errors/mapping-already-exists-error.js +0 -14
  212. package/handlers/workers/integration-triggers.js +0 -25
  213. package/infrastructure/scheduler/recurrence.js +0 -204
  214. package/integrations/define/DESIGN.md +0 -122
  215. package/integrations/define/config.js +0 -98
  216. package/integrations/define/define-app.js +0 -66
  217. package/integrations/define/define-extension.js +0 -154
  218. package/integrations/define/define-integration.js +0 -335
  219. package/integrations/define/examples/deal-alerts.js +0 -140
  220. package/integrations/define/extension-binding.js +0 -174
  221. package/integrations/define/index.js +0 -11
  222. package/integrations/define/json-schema-lite.js +0 -68
  223. package/integrations/define/subscriptions.js +0 -186
  224. package/integrations/define/tests/fixtures/hubspot.js +0 -109
  225. package/integrations/define/tests/fixtures/slack.js +0 -26
  226. package/integrations/define/use.js +0 -39
  227. package/integrations/definitions/README.md +0 -278
  228. package/integrations/definitions/canonical.js +0 -11
  229. package/integrations/definitions/capabilities/AUDIT.md +0 -480
  230. package/integrations/definitions/capabilities/acmeerp.capabilities.json +0 -633
  231. package/integrations/definitions/capabilities/asana.capabilities.json +0 -279
  232. package/integrations/definitions/capabilities/calendly.capabilities.json +0 -385
  233. package/integrations/definitions/capabilities/docusign.capabilities.json +0 -314
  234. package/integrations/definitions/capabilities/fathom.capabilities.json +0 -238
  235. package/integrations/definitions/capabilities/github.capabilities.json +0 -585
  236. package/integrations/definitions/capabilities/hubspot.capabilities.json +0 -822
  237. package/integrations/definitions/capabilities/jira.capabilities.json +0 -391
  238. package/integrations/definitions/capabilities/klaviyo.capabilities.json +0 -503
  239. package/integrations/definitions/capabilities/linear.capabilities.json +0 -378
  240. package/integrations/definitions/capabilities/netsuite.capabilities.json +0 -535
  241. package/integrations/definitions/capabilities/notion.capabilities.json +0 -208
  242. package/integrations/definitions/capabilities/phone.capabilities.json +0 -511
  243. package/integrations/definitions/capabilities/pipedrive.capabilities.json +0 -430
  244. package/integrations/definitions/capabilities/quickbooks.capabilities.json +0 -635
  245. package/integrations/definitions/capabilities/salesforce.capabilities.json +0 -568
  246. package/integrations/definitions/capabilities/shopify.capabilities.json +0 -1128
  247. package/integrations/definitions/capabilities/slack.capabilities.json +0 -373
  248. package/integrations/definitions/capabilities/stripe.capabilities.json +0 -630
  249. package/integrations/definitions/capabilities/webflow.capabilities.json +0 -196
  250. package/integrations/definitions/capabilities/zendesk.capabilities.json +0 -422
  251. package/integrations/definitions/compile.js +0 -80
  252. package/integrations/definitions/config-form.js +0 -137
  253. package/integrations/definitions/examples/acme-erp-quickbooks.definition.json +0 -467
  254. package/integrations/definitions/examples/asana-slack.definition.json +0 -219
  255. package/integrations/definitions/examples/calendly-salesforce.definition.json +0 -459
  256. package/integrations/definitions/examples/docusign-salesforce.definition.json +0 -305
  257. package/integrations/definitions/examples/fathom-hubspot-notion.definition.json +0 -575
  258. package/integrations/definitions/examples/github-linear.definition.json +0 -322
  259. package/integrations/definitions/examples/hubspot-slack.definition.json +0 -427
  260. package/integrations/definitions/examples/klaviyo-hubspot.definition.json +0 -336
  261. package/integrations/definitions/examples/phone-hubspot.definition.json +0 -633
  262. package/integrations/definitions/examples/pipedrive-hubspot.definition.json +0 -136
  263. package/integrations/definitions/examples/pipedrive-phone.definition.json +0 -340
  264. package/integrations/definitions/examples/records/phone-hubspot.definition-version.json +0 -652
  265. package/integrations/definitions/examples/records/phone-hubspot.integration.json +0 -86
  266. package/integrations/definitions/examples/records/phone-hubspot.mappings.json +0 -39
  267. package/integrations/definitions/examples/records/phone-hubspot.workflow-run.json +0 -62
  268. package/integrations/definitions/examples/shopify-klaviyo.definition.json +0 -488
  269. package/integrations/definitions/examples/shopify-netsuite.definition.json +0 -622
  270. package/integrations/definitions/examples/stripe-quickbooks.definition.json +0 -476
  271. package/integrations/definitions/examples/webflow-hubspot-slack.definition.json +0 -262
  272. package/integrations/definitions/examples/zendesk-jira.definition.json +0 -604
  273. package/integrations/definitions/expressions.js +0 -229
  274. package/integrations/definitions/index.js +0 -48
  275. package/integrations/definitions/integration-runtime.js +0 -779
  276. package/integrations/definitions/interpreter.js +0 -104
  277. package/integrations/definitions/manifests.js +0 -177
  278. package/integrations/definitions/renderers/common.js +0 -67
  279. package/integrations/definitions/renderers/html.js +0 -129
  280. package/integrations/definitions/renderers/hubspot.js +0 -59
  281. package/integrations/definitions/renderers/preview.js +0 -154
  282. package/integrations/definitions/renderers/slack.js +0 -146
  283. package/integrations/definitions/repositories/definition-version-repository.js +0 -67
  284. package/integrations/definitions/repositories/workflow-run-repository.js +0 -80
  285. package/integrations/definitions/router.js +0 -170
  286. package/integrations/definitions/runtime.js +0 -268
  287. package/integrations/definitions/schema/capability-manifest.v0.1.schema.json +0 -549
  288. package/integrations/definitions/schema/installation.v0.1.schema.json +0 -130
  289. package/integrations/definitions/schema/integration-definition.v0.1.schema.json +0 -909
  290. package/integrations/definitions/schema/records.md +0 -105
  291. package/integrations/definitions/schema-check.js +0 -58
  292. package/integrations/definitions/scripts/check-manifests.js +0 -220
  293. package/integrations/definitions/scripts/validate.js +0 -66
  294. package/integrations/definitions/steps.js +0 -123
  295. package/integrations/definitions/sync-engine.js +0 -297
  296. package/integrations/definitions/tests/helpers-fake-crm.js +0 -49
  297. package/integrations/definitions/transforms.js +0 -91
  298. package/integrations/definitions/use-cases/definition-use-cases.js +0 -245
  299. package/integrations/definitions/validate.js +0 -436
  300. package/integrations/definitions/versioning.js +0 -176
  301. package/integrations/definitions/webhooks.js +0 -119
  302. package/integrations/integration-types.js +0 -73
  303. package/integrations/triggers.js +0 -244
  304. package/integrations/use-cases/run-trigger-tick.js +0 -155
  305. package/integrations/use-cases/run-trigger.js +0 -77
  306. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +0 -66
  307. package/prisma-postgresql/migrations/20261019000000_workflow_run_waits/migration.sql +0 -7
  308. package/prisma-postgresql/migrations/20261019120000_definition_workspace/migration.sql +0 -5
@@ -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,
@@ -209,11 +233,6 @@ function createIntegrationRouter() {
209
233
  updateIntegration,
210
234
  getPossibleIntegrations,
211
235
  });
212
- // Integration versions, run history, sync progress and approvals (ADR-063): mounted when the app registers the integration runtime.
213
- require('./definitions/router').setIntegrationRuntimeRoutes(router, authenticateUser, {
214
- getIntegrationInstance,
215
- integrationClasses,
216
- });
217
236
  setEntityRoutes(router, authenticateUser, {
218
237
  getCredentialForUser,
219
238
  getModuleInstanceFromType,
@@ -222,7 +241,8 @@ function createIntegrationRouter() {
222
241
  getModule,
223
242
  getEntityOptionsById,
224
243
  refreshEntityOptions,
225
- processAuthorizationCallback,
244
+ authorizeEntityForUser,
245
+ createEntityFromCredential,
226
246
  });
227
247
  return router;
228
248
  }
@@ -259,6 +279,8 @@ function checkRequiredParams(params, requiredKeys) {
259
279
  */
260
280
  function setIntegrationRoutes(router, authenticateUser, useCases) {
261
281
  const {
282
+ resolveIntegrationOwnership,
283
+ getOwnedIntegrationRecord,
262
284
  createIntegration,
263
285
  deleteIntegrationForUser,
264
286
  getIntegrationsForUser,
@@ -267,15 +289,22 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
267
289
  updateIntegration,
268
290
  getPossibleIntegrations,
269
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
+
270
300
  router.route('/api/integrations').get(
271
301
  catchAsyncError(async (req, res) => {
272
302
  const user = await authenticateUser.execute(req);
273
- const userId = user.getId();
274
- const integrations = await getIntegrationsForUser.execute(userId);
303
+ const integrations = await getIntegrationsForUser.execute(user);
275
304
  const results = {
276
305
  entities: {
277
- options: await getPossibleIntegrations.execute({ userId }),
278
- authorized: await getEntitiesForUser.execute(userId),
306
+ options: await getPossibleIntegrations.execute(),
307
+ authorized: await getEntitiesForUser.execute(user),
279
308
  },
280
309
  integrations: integrations,
281
310
  };
@@ -287,7 +316,6 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
287
316
  router.route('/api/integrations').post(
288
317
  catchAsyncError(async (req, res) => {
289
318
  const user = await authenticateUser.execute(req);
290
- const userId = user.getId();
291
319
  const params = checkRequiredParams(req.body, [
292
320
  'entities',
293
321
  'config',
@@ -295,10 +323,17 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
295
323
 
296
324
  get(params.config, 'type');
297
325
 
326
+ const { ownerId, organizationId, adoptUnscoped } =
327
+ await resolveIntegrationOwnership.execute({
328
+ entityIds: params.entities,
329
+ config: params.config,
330
+ user,
331
+ });
298
332
  const integration = await createIntegration.execute(
299
333
  params.entities,
300
- userId,
301
- params.config
334
+ ownerId,
335
+ params.config,
336
+ { organizationId, adoptUnscoped }
302
337
  );
303
338
 
304
339
  res.status(201).json(integration);
@@ -308,12 +343,11 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
308
343
  router.route('/api/integrations/:integrationId').patch(
309
344
  catchAsyncError(async (req, res) => {
310
345
  const user = await authenticateUser.execute(req);
311
- const userId = user.getId();
312
346
  const params = checkRequiredParams(req.body, ['config']);
313
347
 
314
348
  const integration = await updateIntegration.execute(
315
349
  req.params.integrationId,
316
- userId,
350
+ await ownerOf(req.params.integrationId, user, { manage: true }),
317
351
  params.config
318
352
  );
319
353
  res.json(integration);
@@ -326,7 +360,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
326
360
  const params = checkRequiredParams(req.params, ['integrationId']);
327
361
  await deleteIntegrationForUser.execute(
328
362
  params.integrationId,
329
- user.getId()
363
+ await ownerOf(params.integrationId, user, { manage: true })
330
364
  );
331
365
  res.status(204).json({});
332
366
  })
@@ -338,7 +372,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
338
372
  const params = checkRequiredParams(req.params, ['integrationId']);
339
373
  const integration = await getIntegrationInstance.execute(
340
374
  params.integrationId,
341
- user.getId()
375
+ await ownerOf(params.integrationId, user)
342
376
  );
343
377
  res.json(await integration.send('GET_CONFIG_OPTIONS'));
344
378
  })
@@ -354,7 +388,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
354
388
  ]);
355
389
  const integration = await getIntegrationInstance.execute(
356
390
  params.integrationId,
357
- user.getId()
391
+ await ownerOf(params.integrationId, user)
358
392
  );
359
393
 
360
394
  res.json(
@@ -368,7 +402,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
368
402
  const params = checkRequiredParams(req.params, ['integrationId']);
369
403
  const integration = await getIntegrationInstance.execute(
370
404
  params.integrationId,
371
- user.getId()
405
+ await ownerOf(params.integrationId, user)
372
406
  );
373
407
  res.json(await integration.send('GET_USER_ACTIONS', req.body));
374
408
  })
@@ -385,7 +419,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
385
419
  ]);
386
420
  const integration = await getIntegrationInstance.execute(
387
421
  params.integrationId,
388
- user.getId()
422
+ await ownerOf(params.integrationId, user)
389
423
  );
390
424
 
391
425
  res.json(
@@ -410,7 +444,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
410
444
  ]);
411
445
  const integration = await getIntegrationInstance.execute(
412
446
  params.integrationId,
413
- user.getId()
447
+ await ownerOf(params.integrationId, user)
414
448
  );
415
449
 
416
450
  res.json(
@@ -431,7 +465,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
431
465
  ]);
432
466
  const integration = await getIntegrationInstance.execute(
433
467
  params.integrationId,
434
- user.getId()
468
+ await ownerOf(params.integrationId, user)
435
469
  );
436
470
  res.json(await integration.send(params.actionId, req.body));
437
471
  })
@@ -448,7 +482,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
448
482
  const params = checkRequiredParams(req.params, ['integrationId']);
449
483
  const integration = await getIntegrationInstance.execute(
450
484
  params.integrationId,
451
- user.getId()
485
+ await ownerOf(params.integrationId, user)
452
486
  );
453
487
 
454
488
  // We could perhaps augment router with dynamic options? Haven't decided yet, but here may be the place
@@ -468,7 +502,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
468
502
  const params = checkRequiredParams(req.params, ['integrationId']);
469
503
  const instance = await getIntegrationInstance.execute(
470
504
  params.integrationId,
471
- user.getId()
505
+ await ownerOf(params.integrationId, user)
472
506
  );
473
507
 
474
508
  if (!instance) {
@@ -506,16 +540,21 @@ function setEntityRoutes(router, authenticateUser, useCases) {
506
540
  getModule,
507
541
  getEntityOptionsById,
508
542
  refreshEntityOptions,
509
- processAuthorizationCallback,
543
+ authorizeEntityForUser,
544
+ createEntityFromCredential,
510
545
  } = useCases;
511
546
 
512
547
  router.route('/api/authorize').get(
513
548
  catchAsyncError(async (req, res) => {
514
549
  const user = await authenticateUser.execute(req);
515
- const userId = user.getId();
516
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
+ });
517
556
  const module = await getModuleInstanceFromType.execute(
518
- userId,
557
+ ownerId,
519
558
  params.entityType,
520
559
  { state: req.query.state }
521
560
  );
@@ -534,11 +573,15 @@ function setEntityRoutes(router, authenticateUser, useCases) {
534
573
  router.route('/api/authorize').post(
535
574
  catchAsyncError(async (req, res) => {
536
575
  const user = await authenticateUser.execute(req);
537
- const userId = user.getId();
538
576
  const params = checkRequiredParams(req.body, [
539
577
  'entityType',
540
578
  'data',
541
579
  ]);
580
+ const ownership = authorizeEntityForUser.resolveOwnership({
581
+ user,
582
+ entityType: params.entityType,
583
+ });
584
+ const userId = ownership.ownerId;
542
585
 
543
586
  const dataKeys =
544
587
  params.data && typeof params.data === 'object'
@@ -550,11 +593,13 @@ function setEntityRoutes(router, authenticateUser, useCases) {
550
593
  dataKeys,
551
594
  });
552
595
 
553
- const entityDetails = await processAuthorizationCallback.execute(
554
- userId,
555
- params.entityType,
556
- params.data
557
- );
596
+ const { result: entityDetails } =
597
+ await authorizeEntityForUser.execute({
598
+ user,
599
+ entityType: params.entityType,
600
+ data: params.data,
601
+ ownership,
602
+ });
558
603
 
559
604
  log.info('Entity authorized', {
560
605
  eventName: 'frigg.integrations.authorized',
@@ -571,35 +616,20 @@ function setEntityRoutes(router, authenticateUser, useCases) {
571
616
  router.route('/api/entity').post(
572
617
  catchAsyncError(async (req, res) => {
573
618
  const user = await authenticateUser.execute(req);
574
- const userId = user.getId();
575
619
  const params = checkRequiredParams(req.body, [
576
620
  'entityType',
577
621
  'data',
578
622
  ]);
579
623
  checkRequiredParams(req.body.data, ['credential_id']);
580
624
 
581
- // May want to pass along the user ID as well so credential ID's can't be fished???
582
- const credential = await getCredentialForUser.execute(
583
- params.data.credential_id,
584
- userId
585
- );
586
-
587
- if (!credential) {
588
- throw Boom.badRequest('Invalid credential ID');
589
- }
590
-
591
- const module = await getModuleInstanceFromType.execute(
592
- userId,
593
- params.entityType
594
- );
595
- const entityDetails = await module.getEntityDetails(
596
- module.api,
597
- null,
598
- null,
599
- 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
+ })
600
632
  );
601
-
602
- res.json(await module.findOrCreateEntity(entityDetails));
603
633
  })
604
634
  );
605
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 })