@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
@@ -0,0 +1,113 @@
1
+ const Boom = require('@hapi/boom');
2
+ const { Module } = require('../module');
3
+
4
+ const sameId = (a, b) =>
5
+ a !== undefined && a !== null && b !== undefined && b !== null &&
6
+ String(a) === String(b);
7
+
8
+ /**
9
+ * Creates (or finds) the entity for a credential the caller already holds:
10
+ * the v1 `POST /api/entity`. The owner and organization follow the scope of
11
+ * `entityType`, exactly as an authorization does (ADR-056 §3.2, §3.6), so the
12
+ * owner is decided by `AuthorizeEntityForUser.resolveOwnership` and never by
13
+ * `user.getId()`. Whatever that resolution refuses (a missing identity, and
14
+ * an organization-level connection by a non-admin) is refused here too.
15
+ *
16
+ * The credential must belong to that owner: an organization entity is built
17
+ * on the organization's credential, an individual one on the person's own.
18
+ */
19
+ class CreateEntityFromCredential {
20
+ /**
21
+ * @param {Object} params
22
+ * @param {import('./authorize-entity-for-user').AuthorizeEntityForUser} params.authorizeEntityForUser
23
+ * @param {import('./process-authorization-callback').ProcessAuthorizationCallback} params.processAuthorizationCallback
24
+ * @param {Object} params.credentialRepository
25
+ * @param {Array<Object>} params.moduleDefinitions
26
+ * @param {Function} [params.createModule] - builds a Module; injectable for tests
27
+ */
28
+ constructor({
29
+ authorizeEntityForUser,
30
+ processAuthorizationCallback,
31
+ credentialRepository,
32
+ moduleDefinitions,
33
+ createModule,
34
+ }) {
35
+ this.authorizeEntityForUser = authorizeEntityForUser;
36
+ this.processAuthorizationCallback = processAuthorizationCallback;
37
+ this.credentialRepository = credentialRepository;
38
+ this.moduleDefinitions = moduleDefinitions || [];
39
+ this.createModule = createModule || ((params) => Module.create(params));
40
+ }
41
+
42
+ /**
43
+ * @param {Object} params
44
+ * @param {Object} params.user - authenticated User
45
+ * @param {string} params.entityType
46
+ * @param {string} params.credentialId
47
+ * @returns {Promise<Object>} the persisted entity
48
+ */
49
+ async execute({ user, entityType, credentialId }) {
50
+ const definition = this.moduleDefinitions.find(
51
+ (def) =>
52
+ def.moduleName === entityType ||
53
+ (typeof def.getName === 'function' && def.getName() === entityType)
54
+ );
55
+ if (!definition) {
56
+ throw Boom.badRequest(`Unknown entity type ${entityType}`, {
57
+ code: 'ENTITY_TYPE_NOT_FOUND',
58
+ });
59
+ }
60
+
61
+ const ownership = this.authorizeEntityForUser.resolveOwnership({
62
+ user,
63
+ entityType,
64
+ });
65
+
66
+ const credential =
67
+ await this.credentialRepository.findCredentialById(credentialId);
68
+ // A missing credential and someone else's are the same answer, so ids
69
+ // cannot be probed.
70
+ if (!credential || !sameId(credential.userId, ownership.ownerId)) {
71
+ throw Boom.badRequest('Invalid credential ID', {
72
+ code: 'INVALID_CREDENTIAL',
73
+ });
74
+ }
75
+
76
+ // Built for the owner, with the credential loaded (the repository
77
+ // returns its tokens flat; a Module reads them from `data`), so the
78
+ // API can call the provider and a token refresh during
79
+ // getEntityDetails updates the owner's credential.
80
+ const { id, userId, externalId, authIsValid, ...data } = credential;
81
+ const module = await this.createModule({
82
+ userId: ownership.ownerId,
83
+ definition,
84
+ entity: {
85
+ credential: { id, userId, externalId, authIsValid, data },
86
+ },
87
+ });
88
+ const entityDetails =
89
+ await definition.requiredAuthMethods.getEntityDetails(
90
+ module.api,
91
+ null,
92
+ null,
93
+ ownership.ownerId
94
+ );
95
+ Object.assign(
96
+ entityDetails.details,
97
+ module.apiParamsFromEntity(module.api)
98
+ );
99
+
100
+ return this.processAuthorizationCallback.findOrCreateEntity(
101
+ entityDetails,
102
+ definition.moduleName,
103
+ credential.id,
104
+ {
105
+ scope: ownership.scope,
106
+ organizationId: ownership.organizationId,
107
+ legacyOrgId: ownership.legacyOrgId,
108
+ }
109
+ );
110
+ }
111
+ }
112
+
113
+ module.exports = { CreateEntityFromCredential };
@@ -0,0 +1,162 @@
1
+ const Boom = require('@hapi/boom');
2
+ const {
3
+ describeRecord,
4
+ visibilityClauses,
5
+ matchesListFilters,
6
+ } = require('../scope-visibility');
7
+ const { DEFAULT_SCOPE_POLICY } = require('../scope-policy');
8
+ const { assertCanManage } = require('./manage-access');
9
+
10
+ /**
11
+ * Management API v2 entity operations. Each one starts from
12
+ * GetEntityModuleForUser, so ownership is checked the same way everywhere
13
+ * (missing or foreign entity = 404).
14
+ */
15
+
16
+ /**
17
+ * The entities the caller may see in the active organization (ADR-056 §3.4,
18
+ * §3.6): the organization's own, and the caller's individual ones by the
19
+ * visibility rule. Each carries `tenancy` ({scope, owner, organizationId,
20
+ * access}); the stored entity is otherwise untouched.
21
+ */
22
+ class ListEntitiesForUser {
23
+ constructor({ moduleRepository, scopePolicy }) {
24
+ this.moduleRepository = moduleRepository;
25
+ this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
26
+ }
27
+
28
+ /**
29
+ * @param {Object} user - authenticated User
30
+ * @param {{scope?: string, owner?: string}} [filters] - `?scope=` and `?owner=`
31
+ */
32
+ async execute(user, filters = {}) {
33
+ const ctx = this.scopePolicy.accessContext(user);
34
+ const entities = await this.moduleRepository.findEntitiesByOwnership(
35
+ visibilityClauses(ctx, 'entity')
36
+ );
37
+ const listed = [];
38
+ for (const entity of Array.isArray(entities) ? entities : []) {
39
+ // The query is the filter; this is the same rule applied again,
40
+ // so a repository that returns too much cannot widen the list.
41
+ const tenancy = describeRecord(entity, ctx, 'entity');
42
+ if (tenancy && matchesListFilters(tenancy, filters)) {
43
+ listed.push({ ...entity, tenancy });
44
+ }
45
+ }
46
+ return listed;
47
+ }
48
+ }
49
+
50
+ /** One entity the caller may use, with its `tenancy`. */
51
+ class GetEntityForUser {
52
+ constructor({ getEntityModuleForUser }) {
53
+ this.getEntityModuleForUser = getEntityModuleForUser;
54
+ }
55
+
56
+ async execute(entityId, user) {
57
+ const entity = await this.getEntityModuleForUser.loadOwnedEntity(
58
+ entityId,
59
+ user
60
+ );
61
+ return {
62
+ ...entity,
63
+ tenancy: this.getEntityModuleForUser.describe(entity, user),
64
+ };
65
+ }
66
+ }
67
+
68
+ /** Runs the module's testAuthRequest against the stored credential. */
69
+ class TestEntityAuthForUser {
70
+ constructor({ getEntityModuleForUser }) {
71
+ this.getEntityModuleForUser = getEntityModuleForUser;
72
+ }
73
+
74
+ /** @returns {Promise<boolean>} */
75
+ async execute(entityId, user) {
76
+ const { module } = await this.getEntityModuleForUser.execute(
77
+ entityId,
78
+ user
79
+ );
80
+ return Boolean(await module.testAuth());
81
+ }
82
+ }
83
+
84
+ /** The module's entity options (e.g. objects a user can pick). */
85
+ class GetEntityOptionsForUser {
86
+ constructor({ getEntityModuleForUser }) {
87
+ this.getEntityModuleForUser = getEntityModuleForUser;
88
+ }
89
+
90
+ async execute(entityId, user) {
91
+ const { module } = await this.getEntityModuleForUser.execute(
92
+ entityId,
93
+ user
94
+ );
95
+ return module.getEntityOptions();
96
+ }
97
+ }
98
+
99
+ /** Refreshes the module's entity options with caller input, then returns them. */
100
+ class RefreshEntityOptionsForUser {
101
+ constructor({ getEntityModuleForUser }) {
102
+ this.getEntityModuleForUser = getEntityModuleForUser;
103
+ }
104
+
105
+ async execute(entityId, user, options = {}) {
106
+ const { module } = await this.getEntityModuleForUser.execute(
107
+ entityId,
108
+ user
109
+ );
110
+ return module.refreshEntityOptions(options);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Deletes an entity the caller may manage (the owner; for an organization
116
+ * entity, an organization admin). Refuses with 409 while integrations still
117
+ * use it, so an integration never points at a missing entity. The credential
118
+ * is left alone (others may share it); delete it through /api/v2/credentials.
119
+ */
120
+ class DeleteEntityForUser {
121
+ constructor({ getEntityModuleForUser, moduleRepository, integrationRepository }) {
122
+ this.getEntityModuleForUser = getEntityModuleForUser;
123
+ this.moduleRepository = moduleRepository;
124
+ this.integrationRepository = integrationRepository;
125
+ }
126
+
127
+ async execute(entityId, user) {
128
+ const entity = await this.getEntityModuleForUser.loadOwnedEntity(
129
+ entityId,
130
+ user
131
+ );
132
+ assertCanManage(
133
+ this.getEntityModuleForUser.describe(entity, user)?.access,
134
+ `entity ${entity.id}`
135
+ );
136
+ const integrations =
137
+ (await this.integrationRepository.findIntegrationsByEntityId(
138
+ entity.id
139
+ )) || [];
140
+ if (integrations.length > 0) {
141
+ throw Boom.conflict(
142
+ `Entity ${entity.id} is used by ${integrations.length} integration(s); delete them first`,
143
+ {
144
+ code: 'ENTITY_IN_USE',
145
+ details: {
146
+ integrationIds: integrations.map((i) => String(i.id)),
147
+ },
148
+ }
149
+ );
150
+ }
151
+ await this.moduleRepository.deleteEntity(entity.id);
152
+ }
153
+ }
154
+
155
+ module.exports = {
156
+ ListEntitiesForUser,
157
+ GetEntityForUser,
158
+ TestEntityAuthForUser,
159
+ GetEntityOptionsForUser,
160
+ RefreshEntityOptionsForUser,
161
+ DeleteEntityForUser,
162
+ };
@@ -0,0 +1,139 @@
1
+ const Boom = require('@hapi/boom');
2
+ const { ModuleConstants } = require('../ModuleConstants');
3
+ const { usedByOf } = require('../entity-type-registry');
4
+
5
+ const AUTH_TYPE_BY_REQUESTER = {
6
+ [ModuleConstants.authType.oauth2]: 'oauth2',
7
+ [ModuleConstants.authType.oauth1]: 'oauth1',
8
+ [ModuleConstants.authType.apiKey]: 'api-key',
9
+ [ModuleConstants.authType.basic]: 'basic',
10
+ };
11
+
12
+ const callIfFunction = (target, name) =>
13
+ typeof target?.[name] === 'function' ? target[name]() : undefined;
14
+
15
+ /** Number of authorization steps a module declares (1 unless multi-step). */
16
+ function getAuthStepCount(definition) {
17
+ const count = callIfFunction(definition, 'getAuthStepCount');
18
+ return Number.isInteger(count) && count > 0 ? count : 1;
19
+ }
20
+
21
+ /**
22
+ * Public description of an API module (an "entity type"). Reads only the
23
+ * definition: display metadata, auth type and step count. Never credentials
24
+ * or env values.
25
+ */
26
+ function describeEntityType(definition, typeName = definition.moduleName) {
27
+ const stepCount = getAuthStepCount(definition);
28
+ const display = definition.display || {};
29
+ const description = {
30
+ type: typeName,
31
+ name:
32
+ callIfFunction(definition, 'getDisplayName') ??
33
+ display.label ??
34
+ display.name ??
35
+ typeName,
36
+ authType:
37
+ callIfFunction(definition, 'getAuthType') ??
38
+ AUTH_TYPE_BY_REQUESTER[definition.API?.requesterType] ??
39
+ 'custom',
40
+ isMultiStep: stepCount > 1,
41
+ stepCount,
42
+ };
43
+ const text = callIfFunction(definition, 'getDescription') ?? display.description;
44
+ if (text) description.description = text;
45
+ const capabilities = callIfFunction(definition, 'getCapabilities');
46
+ if (capabilities !== undefined) description.capabilities = capabilities;
47
+ return description;
48
+ }
49
+
50
+ function findDefinition(moduleDefinitions, entityType) {
51
+ const definition = moduleDefinitions.find(
52
+ (def) => def.moduleName === entityType
53
+ );
54
+ if (!definition) {
55
+ throw Boom.notFound(`Entity type '${entityType}' not found`, {
56
+ code: 'ENTITY_TYPE_NOT_FOUND',
57
+ });
58
+ }
59
+ return definition;
60
+ }
61
+
62
+ /**
63
+ * With a scope policy the entry also says which scope a new connection gets
64
+ * and which a request may choose (ADR-056 §3.1), so a UI can offer the choice.
65
+ * `global` is never offered: a tenant request cannot create one.
66
+ */
67
+ const scopeOf = (scopePolicy, name, user) => {
68
+ if (!scopePolicy) return {};
69
+ const { scope, source, allow } = scopePolicy.resolveScope({
70
+ entityType: name,
71
+ user,
72
+ });
73
+ return {
74
+ scope: {
75
+ default: scope,
76
+ allow: allow.filter((value) => value !== 'global'),
77
+ source,
78
+ },
79
+ };
80
+ };
81
+
82
+ const describeEntry = (name, entry, scopePolicy, user) => ({
83
+ // The registry key is the name the API uses, whatever the definition spells.
84
+ ...describeEntityType(entry.definition, name),
85
+ usedBy: usedByOf(entry),
86
+ ...scopeOf(scopePolicy, name, user),
87
+ });
88
+
89
+ function requireRegistry(entityTypeRegistry) {
90
+ if (!(entityTypeRegistry instanceof Map)) {
91
+ throw new TypeError('entityTypeRegistry is required (see getEntityTypeRegistry)');
92
+ }
93
+ return entityTypeRegistry;
94
+ }
95
+
96
+ /**
97
+ * Lists the entity types this app can authorize: registry entries plus every
98
+ * module an integration binds (ADR-059). `usedBy` names the integrations that
99
+ * bind each type; it is empty for a registry-only type.
100
+ */
101
+ class ListEntityTypes {
102
+ constructor({ entityTypeRegistry, scopePolicy }) {
103
+ this.registry = requireRegistry(entityTypeRegistry);
104
+ this.scopePolicy = scopePolicy;
105
+ }
106
+
107
+ /** @param {{user?: Object}} [params] - the caller, for the level-6 scope default */
108
+ async execute({ user } = {}) {
109
+ return [...this.registry]
110
+ .map(([name, entry]) => describeEntry(name, entry, this.scopePolicy, user))
111
+ .sort((a, b) => a.name.localeCompare(b.name));
112
+ }
113
+ }
114
+
115
+ /** Describes one entity type, looked up by its registry key; 404 when absent. */
116
+ class GetEntityType {
117
+ constructor({ entityTypeRegistry, scopePolicy }) {
118
+ this.registry = requireRegistry(entityTypeRegistry);
119
+ this.scopePolicy = scopePolicy;
120
+ }
121
+
122
+ async execute(entityType, { user } = {}) {
123
+ const entry = this.registry.get(entityType);
124
+ if (!entry) {
125
+ throw Boom.notFound(`Entity type '${entityType}' not found`, {
126
+ code: 'ENTITY_TYPE_NOT_FOUND',
127
+ });
128
+ }
129
+ return describeEntry(entityType, entry, this.scopePolicy, user);
130
+ }
131
+ }
132
+
133
+ module.exports = {
134
+ ListEntityTypes,
135
+ GetEntityType,
136
+ describeEntityType,
137
+ getAuthStepCount,
138
+ findDefinition,
139
+ };