@friggframework/core 2.0.0--canary.690.08cd07e.0 → 2.0.0--canary.693.d8859c2.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 (261) hide show
  1. package/CLAUDE.md +0 -28
  2. package/README.md +0 -105
  3. package/application/commands/integration-commands.js +10 -20
  4. package/application/commands/scheduler-commands.js +7 -11
  5. package/application/commands/user-commands.js +5 -17
  6. package/application/index.js +2 -6
  7. package/core/Worker.js +7 -31
  8. package/core/create-handler.js +0 -9
  9. package/credential/repositories/credential-repository-documentdb.js +0 -34
  10. package/credential/repositories/credential-repository-interface.js +0 -15
  11. package/credential/repositories/credential-repository-mongo.js +0 -23
  12. package/credential/repositories/credential-repository-postgres.js +0 -28
  13. package/database/config.js +24 -48
  14. package/database/documentdb-utils.js +0 -2
  15. package/database/encryption/encryption-schema-registry.js +12 -93
  16. package/database/prisma.js +2 -4
  17. package/errors/index.js +4 -0
  18. package/errors/mapping-already-exists-error.js +14 -0
  19. package/generated/prisma-mongodb/edge.js +38 -33
  20. package/generated/prisma-mongodb/index-browser.js +35 -30
  21. package/generated/prisma-mongodb/index.d.ts +1880 -3191
  22. package/generated/prisma-mongodb/index.js +38 -33
  23. package/generated/prisma-mongodb/package.json +1 -1
  24. package/generated/prisma-mongodb/schema.prisma +50 -86
  25. package/generated/prisma-mongodb/wasm.js +38 -33
  26. package/generated/prisma-postgresql/edge.js +38 -33
  27. package/generated/prisma-postgresql/index-browser.js +35 -30
  28. package/generated/prisma-postgresql/index.d.ts +2050 -3390
  29. package/generated/prisma-postgresql/index.js +38 -33
  30. package/generated/prisma-postgresql/package.json +1 -1
  31. package/generated/prisma-postgresql/schema.prisma +48 -81
  32. package/generated/prisma-postgresql/wasm.js +38 -33
  33. package/handlers/WEBHOOKS.md +29 -0
  34. package/handlers/app-definition-loader.js +19 -128
  35. package/handlers/app-handler-helpers.js +3 -25
  36. package/handlers/backend-utils.js +25 -31
  37. package/handlers/middleware/admin-auth.js +1 -14
  38. package/handlers/routers/auth.js +10 -46
  39. package/handlers/routers/health.js +4 -13
  40. package/handlers/routers/integration-defined-routers.js +92 -144
  41. package/handlers/routers/integration-webhook-routers.js +64 -102
  42. package/handlers/workers/integration-defined-workers.js +2 -5
  43. package/index.js +3 -65
  44. package/infrastructure/scheduler/index.js +6 -19
  45. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  46. package/integrations/data-defined/README.md +169 -0
  47. package/integrations/data-defined/canonical.js +11 -0
  48. package/integrations/data-defined/capabilities/acmeerp.capabilities.json +608 -0
  49. package/integrations/data-defined/capabilities/asana.capabilities.json +265 -0
  50. package/integrations/data-defined/capabilities/calendly.capabilities.json +385 -0
  51. package/integrations/data-defined/capabilities/docusign.capabilities.json +314 -0
  52. package/integrations/data-defined/capabilities/fathom.capabilities.json +228 -0
  53. package/integrations/data-defined/capabilities/github.capabilities.json +563 -0
  54. package/integrations/data-defined/capabilities/hubspot.capabilities.json +715 -0
  55. package/integrations/data-defined/capabilities/jira.capabilities.json +376 -0
  56. package/integrations/data-defined/capabilities/klaviyo.capabilities.json +475 -0
  57. package/integrations/data-defined/capabilities/linear.capabilities.json +324 -0
  58. package/integrations/data-defined/capabilities/netsuite.capabilities.json +491 -0
  59. package/integrations/data-defined/capabilities/notion.capabilities.json +208 -0
  60. package/integrations/data-defined/capabilities/phone.capabilities.json +470 -0
  61. package/integrations/data-defined/capabilities/pipedrive.capabilities.json +388 -0
  62. package/integrations/data-defined/capabilities/quickbooks.capabilities.json +615 -0
  63. package/integrations/data-defined/capabilities/salesforce.capabilities.json +507 -0
  64. package/integrations/data-defined/capabilities/shopify.capabilities.json +1082 -0
  65. package/integrations/data-defined/capabilities/slack.capabilities.json +363 -0
  66. package/integrations/data-defined/capabilities/stripe.capabilities.json +580 -0
  67. package/integrations/data-defined/capabilities/webflow.capabilities.json +196 -0
  68. package/integrations/data-defined/capabilities/zendesk.capabilities.json +409 -0
  69. package/integrations/data-defined/compile.js +77 -0
  70. package/integrations/data-defined/config-form.js +110 -0
  71. package/integrations/data-defined/data-defined-integration.js +366 -0
  72. package/integrations/data-defined/examples/acme-erp-quickbooks.definition.json +467 -0
  73. package/integrations/data-defined/examples/asana-slack.definition.json +220 -0
  74. package/integrations/data-defined/examples/calendly-salesforce.definition.json +459 -0
  75. package/integrations/data-defined/examples/docusign-salesforce.definition.json +305 -0
  76. package/integrations/data-defined/examples/fathom-hubspot-notion.definition.json +575 -0
  77. package/integrations/data-defined/examples/github-linear.definition.json +316 -0
  78. package/integrations/data-defined/examples/hubspot-slack.definition.json +409 -0
  79. package/integrations/data-defined/examples/klaviyo-hubspot.definition.json +339 -0
  80. package/integrations/data-defined/examples/phone-hubspot.definition.json +637 -0
  81. package/integrations/data-defined/examples/pipedrive-hubspot.definition.json +136 -0
  82. package/integrations/data-defined/examples/pipedrive-phone.definition.json +340 -0
  83. package/integrations/data-defined/examples/records/phone-hubspot.definition-version.json +652 -0
  84. package/integrations/data-defined/examples/records/phone-hubspot.integration.json +86 -0
  85. package/integrations/data-defined/examples/records/phone-hubspot.mappings.json +39 -0
  86. package/integrations/data-defined/examples/records/phone-hubspot.workflow-run.json +62 -0
  87. package/integrations/data-defined/examples/shopify-klaviyo.definition.json +488 -0
  88. package/integrations/data-defined/examples/shopify-netsuite.definition.json +622 -0
  89. package/integrations/data-defined/examples/stripe-quickbooks.definition.json +476 -0
  90. package/integrations/data-defined/examples/webflow-hubspot-slack.definition.json +262 -0
  91. package/integrations/data-defined/examples/zendesk-jira.definition.json +604 -0
  92. package/integrations/data-defined/expressions.js +225 -0
  93. package/integrations/data-defined/index.js +39 -0
  94. package/integrations/data-defined/interpreter.js +89 -0
  95. package/integrations/data-defined/manifests.js +125 -0
  96. package/integrations/data-defined/repositories/definition-version-repository.js +67 -0
  97. package/integrations/data-defined/repositories/workflow-run-repository.js +59 -0
  98. package/integrations/data-defined/router.js +97 -0
  99. package/integrations/data-defined/runtime.js +208 -0
  100. package/integrations/data-defined/schema/capability-manifest.v0.1.schema.json +377 -0
  101. package/integrations/data-defined/schema/installation.v0.1.schema.json +83 -0
  102. package/integrations/data-defined/schema/integration-definition.v0.1.schema.json +848 -0
  103. package/integrations/data-defined/schema/records.md +98 -0
  104. package/integrations/data-defined/schema-check.js +58 -0
  105. package/integrations/data-defined/scripts/validate.js +66 -0
  106. package/integrations/data-defined/steps.js +89 -0
  107. package/integrations/data-defined/transforms.js +91 -0
  108. package/integrations/data-defined/use-cases/definition-use-cases.js +187 -0
  109. package/integrations/data-defined/validate.js +370 -0
  110. package/integrations/data-defined/versioning.js +161 -0
  111. package/integrations/data-defined/webhooks.js +119 -0
  112. package/integrations/index.js +0 -1
  113. package/integrations/integration-base.js +29 -0
  114. package/integrations/integration-router.js +101 -131
  115. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  116. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  117. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  118. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  119. package/integrations/repositories/integration-mapping-repository.js +29 -0
  120. package/integrations/repositories/integration-repository-documentdb.js +3 -35
  121. package/integrations/repositories/integration-repository-interface.js +1 -17
  122. package/integrations/repositories/integration-repository-mongo.js +77 -60
  123. package/integrations/repositories/integration-repository-postgres.js +76 -62
  124. package/integrations/repositories/process-repository-mongo.js +1 -1
  125. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  126. package/integrations/use-cases/create-integration.js +20 -55
  127. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  128. package/integrations/use-cases/index.js +1 -7
  129. package/integrations/utils/map-integration-dto.js +14 -43
  130. package/modules/index.js +0 -14
  131. package/modules/module-factory.js +0 -12
  132. package/modules/module.js +2 -53
  133. package/modules/repositories/module-repository-documentdb.js +6 -51
  134. package/modules/repositories/module-repository-interface.js +2 -19
  135. package/modules/repositories/module-repository-mongo.js +74 -67
  136. package/modules/repositories/module-repository-postgres.js +74 -66
  137. package/modules/repositories/module-repository.js +1 -2
  138. package/modules/requester/requester.js +5 -35
  139. package/modules/use-cases/get-entities-for-user.js +13 -26
  140. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  141. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  142. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  143. package/modules/use-cases/get-module.js +4 -10
  144. package/modules/use-cases/process-authorization-callback.js +19 -53
  145. package/modules/use-cases/refresh-entity-options.js +4 -10
  146. package/modules/use-cases/test-module-auth.js +4 -10
  147. package/package.json +6 -6
  148. package/prisma-mongodb/schema.prisma +50 -86
  149. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +66 -0
  150. package/prisma-postgresql/schema.prisma +48 -81
  151. package/queues/index.js +0 -25
  152. package/types/index.d.ts +0 -2
  153. package/user/repositories/user-repository-documentdb.js +2 -27
  154. package/user/repositories/user-repository-interface.js +0 -18
  155. package/user/repositories/user-repository-mongo.js +0 -24
  156. package/user/repositories/user-repository-postgres.js +0 -18
  157. package/user/use-cases/authenticate-user.js +1 -7
  158. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  159. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  160. package/user/user.js +1 -64
  161. package/credential/use-cases/credential-operations-for-user.js +0 -202
  162. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  163. package/database/organization-where.js +0 -18
  164. package/database/unique-violation.js +0 -25
  165. package/extensions/bootstrap-runner.js +0 -43
  166. package/extensions/extension-loader.js +0 -187
  167. package/extensions/index.js +0 -41
  168. package/extensions/initialize-app.js +0 -123
  169. package/extensions/route-mounter.js +0 -103
  170. package/extensions/schema-composer.js +0 -188
  171. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -216
  172. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  173. package/integrations/repositories/integration-not-found.js +0 -14
  174. package/integrations/schedule-config.js +0 -80
  175. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  176. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  177. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  178. package/management-api/create-management-api-router.js +0 -49
  179. package/management-api/error-response.js +0 -116
  180. package/management-api/management-api-config.js +0 -151
  181. package/management-api/meta-router.js +0 -76
  182. package/management-api/openapi/build-openapi.js +0 -144
  183. package/management-api/openapi/components.js +0 -415
  184. package/management-api/openapi/v1-operations.js +0 -83
  185. package/management-api/openapi/v2-operations.js +0 -264
  186. package/management-api/route-registry.js +0 -411
  187. package/management-api/test-support/http-client.js +0 -39
  188. package/management-api/use-cases/get-management-api-meta.js +0 -57
  189. package/management-api/v1-deprecation.js +0 -120
  190. package/management-api/v2/create-v2-router.js +0 -85
  191. package/management-api/v2/dependencies.js +0 -281
  192. package/management-api/v2/dto.js +0 -143
  193. package/management-api/v2/handlers/authorization.js +0 -94
  194. package/management-api/v2/handlers/credentials.js +0 -33
  195. package/management-api/v2/handlers/entities.js +0 -77
  196. package/management-api/v2/handlers/integrations.js +0 -128
  197. package/management-api/v2/handlers/proxy.js +0 -34
  198. package/management-api/v2/request.js +0 -85
  199. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  200. package/modules/domain/authorization-session.js +0 -144
  201. package/modules/entity-scope.js +0 -15
  202. package/modules/entity-type-registry.js +0 -311
  203. package/modules/proxy/proxy-bases.js +0 -169
  204. package/modules/proxy/proxy-errors.js +0 -31
  205. package/modules/proxy/proxy-path.js +0 -183
  206. package/modules/proxy/proxy-policy.js +0 -277
  207. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  208. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  209. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  210. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  211. package/modules/repositories/entity-not-found.js +0 -18
  212. package/modules/requester/proxy-request.js +0 -245
  213. package/modules/scope-config.js +0 -192
  214. package/modules/scope-policy.js +0 -112
  215. package/modules/scope-resolution.js +0 -288
  216. package/modules/scope-visibility.js +0 -247
  217. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  218. package/modules/tests/doubles/ownership-matching.js +0 -18
  219. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  220. package/modules/use-cases/create-entity-from-credential.js +0 -113
  221. package/modules/use-cases/entity-operations-for-user.js +0 -162
  222. package/modules/use-cases/entity-types.js +0 -139
  223. package/modules/use-cases/execute-entity-proxy-request.js +0 -331
  224. package/modules/use-cases/get-authorization-requirements.js +0 -100
  225. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  226. package/modules/use-cases/manage-access.js +0 -21
  227. package/modules/use-cases/multi-step-authorization.js +0 -329
  228. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  229. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  230. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  231. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  232. package/providers/index.js +0 -23
  233. package/providers/resolve-provider.js +0 -93
  234. package/providers/runtime-detection.js +0 -124
  235. package/queues/providers/index.js +0 -11
  236. package/queues/providers/netlify-background-provider.js +0 -150
  237. package/queues/providers/qstash-queue-provider.js +0 -219
  238. package/queues/providers/sqs-queue-provider.js +0 -70
  239. package/queues/queue-client-interface.js +0 -55
  240. package/queues/queue-message-auth.js +0 -193
  241. package/queues/queue-provider-factory.js +0 -101
  242. package/queues/queue-provider.js +0 -65
  243. package/queues/sqs-queue-client.js +0 -81
  244. package/types/modules/index.d.ts +0 -214
  245. package/types/user/index.d.ts +0 -134
  246. package/user/adopter-jwt-config.js +0 -177
  247. package/user/adopter-jwt-errors.js +0 -59
  248. package/user/adopter-token-verifier.js +0 -122
  249. package/user/app-user-helpers.js +0 -97
  250. package/user/create-user-authentication.js +0 -64
  251. package/user/membership.js +0 -51
  252. package/user/repositories/organization-membership-record.js +0 -19
  253. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  254. package/user/repositories/organization-membership-repository-factory.js +0 -42
  255. package/user/repositories/organization-membership-repository-interface.js +0 -57
  256. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  257. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  258. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  259. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  260. package/user/use-cases/ensure-organization-membership.js +0 -92
  261. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -1,112 +0,0 @@
1
- const { prisma } = require('../../database/prisma');
2
- const {
3
- findOne,
4
- insertOne,
5
- updateOne,
6
- deleteMany,
7
- } = require('../../database/documentdb-utils');
8
- const {
9
- DocumentDBEncryptionService,
10
- } = require('../../database/documentdb-encryption-service');
11
- const { AuthorizationSession } = require('../domain/authorization-session');
12
- const {
13
- AuthorizationSessionRepositoryInterface,
14
- } = require('./authorization-session-repository-interface');
15
-
16
- const COLLECTION = 'AuthorizationSession';
17
-
18
- /**
19
- * AuthorizationSession persistence for DocumentDB through raw commands.
20
- * `stepData` is encrypted with DocumentDBEncryptionService, as the Prisma
21
- * extension does not run for raw commands.
22
- */
23
- class AuthorizationSessionRepositoryDocumentDB extends AuthorizationSessionRepositoryInterface {
24
- constructor() {
25
- super();
26
- this.prisma = prisma;
27
- this.encryptionService = new DocumentDBEncryptionService();
28
- }
29
-
30
- async create(session) {
31
- const now = new Date();
32
- const document = await this.encryptionService.encryptFields(COLLECTION, {
33
- sessionId: session.sessionId,
34
- userId: session.userId,
35
- entityType: session.entityType,
36
- credentialId: session.credentialId,
37
- currentStep: session.currentStep,
38
- maxSteps: session.maxSteps,
39
- stepData: session.stepData,
40
- expiresAt: session.expiresAt,
41
- completed: session.completed,
42
- createdAt: now,
43
- updatedAt: now,
44
- });
45
- await insertOne(this.prisma, COLLECTION, document);
46
- return this._toDomain(
47
- await this.encryptionService.decryptFields(COLLECTION, document)
48
- );
49
- }
50
-
51
- async findBySessionId(sessionId) {
52
- if (typeof sessionId !== 'string' || !sessionId) return null;
53
- const document = await findOne(this.prisma, COLLECTION, {
54
- sessionId,
55
- expiresAt: { $gt: new Date() },
56
- });
57
- if (!document) return null;
58
- return this._toDomain(
59
- await this.encryptionService.decryptFields(COLLECTION, document)
60
- );
61
- }
62
-
63
- async update(session) {
64
- const encrypted = await this.encryptionService.encryptFields(COLLECTION, {
65
- stepData: session.stepData,
66
- });
67
- await updateOne(
68
- this.prisma,
69
- COLLECTION,
70
- { sessionId: session.sessionId },
71
- {
72
- $set: {
73
- currentStep: session.currentStep,
74
- stepData: encrypted.stepData,
75
- completed: session.completed,
76
- updatedAt: new Date(),
77
- },
78
- }
79
- );
80
- return session;
81
- }
82
-
83
- async deleteBySessionId(sessionId) {
84
- const result = await deleteMany(this.prisma, COLLECTION, { sessionId });
85
- return result?.n ?? 0;
86
- }
87
-
88
- async deleteExpired(now = new Date()) {
89
- const result = await deleteMany(this.prisma, COLLECTION, {
90
- expiresAt: { $lte: now },
91
- });
92
- return result?.n ?? 0;
93
- }
94
-
95
- _toDomain(document) {
96
- return new AuthorizationSession({
97
- sessionId: document.sessionId,
98
- userId: document.userId,
99
- entityType: document.entityType,
100
- credentialId: document.credentialId ?? null,
101
- currentStep: document.currentStep,
102
- maxSteps: document.maxSteps,
103
- stepData: document.stepData || {},
104
- expiresAt: new Date(document.expiresAt),
105
- completed: Boolean(document.completed),
106
- createdAt: document.createdAt,
107
- updatedAt: document.updatedAt,
108
- });
109
- }
110
- }
111
-
112
- module.exports = { AuthorizationSessionRepositoryDocumentDB };
@@ -1,31 +0,0 @@
1
- const config = require('../../database/config');
2
- const {
3
- AuthorizationSessionRepositoryPrisma,
4
- } = require('./authorization-session-repository-prisma');
5
- const {
6
- AuthorizationSessionRepositoryDocumentDB,
7
- } = require('./authorization-session-repository-documentdb');
8
-
9
- /**
10
- * AuthorizationSession repository for the configured database. PostgreSQL
11
- * and MongoDB share the Prisma adapter; DocumentDB uses raw commands.
12
- */
13
- function createAuthorizationSessionRepository() {
14
- switch (config.DB_TYPE) {
15
- case 'mongodb':
16
- case 'postgresql':
17
- return new AuthorizationSessionRepositoryPrisma();
18
- case 'documentdb':
19
- return new AuthorizationSessionRepositoryDocumentDB();
20
- default:
21
- throw new Error(
22
- `Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
23
- );
24
- }
25
- }
26
-
27
- module.exports = {
28
- createAuthorizationSessionRepository,
29
- AuthorizationSessionRepositoryPrisma,
30
- AuthorizationSessionRepositoryDocumentDB,
31
- };
@@ -1,47 +0,0 @@
1
- /**
2
- * Port for persisting AuthorizationSession records (Management API v2
3
- * multi-step authorization). Adapters return AuthorizationSession domain
4
- * objects and never expired sessions from lookups.
5
- */
6
- class AuthorizationSessionRepositoryInterface {
7
- /**
8
- * @param {import('../domain/authorization-session').AuthorizationSession} session
9
- * @returns {Promise<import('../domain/authorization-session').AuthorizationSession>}
10
- */
11
- async create(session) {
12
- throw new Error('Method create must be implemented by subclass');
13
- }
14
-
15
- /**
16
- * @param {string} sessionId
17
- * @returns {Promise<import('../domain/authorization-session').AuthorizationSession|null>}
18
- * null when missing or expired.
19
- */
20
- async findBySessionId(sessionId) {
21
- throw new Error('Method findBySessionId must be implemented by subclass');
22
- }
23
-
24
- /**
25
- * Saves currentStep, stepData and completed.
26
- * @param {import('../domain/authorization-session').AuthorizationSession} session
27
- */
28
- async update(session) {
29
- throw new Error('Method update must be implemented by subclass');
30
- }
31
-
32
- /** @param {string} sessionId */
33
- async deleteBySessionId(sessionId) {
34
- throw new Error('Method deleteBySessionId must be implemented by subclass');
35
- }
36
-
37
- /**
38
- * Removes sessions that expired before `now`.
39
- * @param {Date} [now]
40
- * @returns {Promise<number>} how many were removed
41
- */
42
- async deleteExpired(now) {
43
- throw new Error('Method deleteExpired must be implemented by subclass');
44
- }
45
- }
46
-
47
- module.exports = { AuthorizationSessionRepositoryInterface };
@@ -1,87 +0,0 @@
1
- const { prisma } = require('../../database/prisma');
2
- const { AuthorizationSession } = require('../domain/authorization-session');
3
- const {
4
- AuthorizationSessionRepositoryInterface,
5
- } = require('./authorization-session-repository-interface');
6
-
7
- /**
8
- * AuthorizationSession persistence through the Prisma client, for PostgreSQL
9
- * and MongoDB (both schemas declare the same model; the session id, not the
10
- * row id, is the key, so no id conversion is needed). `stepData` is
11
- * encrypted by the Prisma encryption extension (encryption-schema-registry).
12
- */
13
- class AuthorizationSessionRepositoryPrisma extends AuthorizationSessionRepositoryInterface {
14
- constructor(prismaClient = prisma) {
15
- super();
16
- this.prisma = prismaClient;
17
- }
18
-
19
- async create(session) {
20
- const record = await this.prisma.authorizationSession.create({
21
- data: {
22
- sessionId: session.sessionId,
23
- userId: session.userId,
24
- entityType: session.entityType,
25
- credentialId: session.credentialId,
26
- currentStep: session.currentStep,
27
- maxSteps: session.maxSteps,
28
- stepData: session.stepData,
29
- expiresAt: session.expiresAt,
30
- completed: session.completed,
31
- },
32
- });
33
- return toDomain(record);
34
- }
35
-
36
- async findBySessionId(sessionId) {
37
- if (typeof sessionId !== 'string' || !sessionId) return null;
38
- const record = await this.prisma.authorizationSession.findFirst({
39
- where: { sessionId, expiresAt: { gt: new Date() } },
40
- });
41
- return record ? toDomain(record) : null;
42
- }
43
-
44
- async update(session) {
45
- const record = await this.prisma.authorizationSession.update({
46
- where: { sessionId: session.sessionId },
47
- data: {
48
- currentStep: session.currentStep,
49
- stepData: session.stepData,
50
- completed: session.completed,
51
- },
52
- });
53
- return toDomain(record);
54
- }
55
-
56
- async deleteBySessionId(sessionId) {
57
- const result = await this.prisma.authorizationSession.deleteMany({
58
- where: { sessionId },
59
- });
60
- return result.count;
61
- }
62
-
63
- async deleteExpired(now = new Date()) {
64
- const result = await this.prisma.authorizationSession.deleteMany({
65
- where: { expiresAt: { lte: now } },
66
- });
67
- return result.count;
68
- }
69
- }
70
-
71
- function toDomain(record) {
72
- return new AuthorizationSession({
73
- sessionId: record.sessionId,
74
- userId: record.userId,
75
- entityType: record.entityType,
76
- credentialId: record.credentialId ?? null,
77
- currentStep: record.currentStep,
78
- maxSteps: record.maxSteps,
79
- stepData: record.stepData || {},
80
- expiresAt: record.expiresAt,
81
- completed: record.completed,
82
- createdAt: record.createdAt,
83
- updatedAt: record.updatedAt,
84
- });
85
- }
86
-
87
- module.exports = { AuthorizationSessionRepositoryPrisma, toDomain };
@@ -1,18 +0,0 @@
1
- /**
2
- * The error `findEntityById` throws for an id with no row, and the one
3
- * predicate that recognises it (as integration-not-found.js does for
4
- * integrations). The adapters build the error here and use cases match it
5
- * here, so a missing entity can be told from any other failure without
6
- * widening a regex.
7
- *
8
- * Why it matters: a use case that answers 404 for another tenant's entity must
9
- * answer the same 404 for an id that does not exist. Otherwise the status code
10
- * says which ids exist (ADR-052 §3, ADR-056 §3.6).
11
- */
12
- const MESSAGE = /^Entity .+ not found$/;
13
-
14
- const entityNotFoundError = (id) => new Error(`Entity ${id} not found`);
15
-
16
- const isEntityNotFound = (error) => MESSAGE.test(error?.message || '');
17
-
18
- module.exports = { entityNotFoundError, isEntityNotFound };
@@ -1,245 +0,0 @@
1
- const { FetchError } = require('../../errors/fetch-error');
2
- const { ProxyRequestError } = require('../proxy/proxy-errors');
3
- const {
4
- normalizeProxyPath,
5
- joinProxyUrl,
6
- } = require('../proxy/proxy-path');
7
-
8
- const MAX_REDIRECTS = 3;
9
-
10
- /**
11
- * The generic, path-checked request behind the Management API entity proxy
12
- * (ADR-052 §8). It runs through the requester's own `_request`, so auth
13
- * headers, token refresh (single-flight), the per-attempt timeout, telemetry
14
- * and log redaction all apply. What it adds:
15
- *
16
- * - the URL is always `module base URL + normalised path`; the caller cannot
17
- * name a host, scheme or port;
18
- * - no backoff retries (the caller decides; a 429 comes back with its
19
- * Retry-After), and upstream errors are returned, not thrown;
20
- * - redirects are handled manually: same origin and under the base path only,
21
- * at most three hops, each re-checked with `isAllowedRedirect`;
22
- * - one deadline for the whole call and a cap on the response body.
23
- *
24
- * @param {import('./requester').Requester} requester
25
- * @param {Object} params
26
- * @param {string} params.method - upper case
27
- * @param {string} params.path - caller path, validated here
28
- * @param {Object} [params.query]
29
- * @param {Object} [params.headers] - already filtered by the caller's policy
30
- * @param {string|Buffer} [params.body] - already serialised
31
- * @param {string} [params.baseUrl] - the module-declared URL of the base the
32
- * caller named (ADR-052 amendment; resolved by the use case); falls
33
- * back to the requester's `proxyBaseUrl`, `baseUrl` or `baseURL`
34
- * @param {number} params.timeoutMs - budget for the whole call
35
- * @param {number} params.maxResponseBytes
36
- * @param {(path: string, method: string) => boolean} [params.isAllowedRedirect]
37
- * @returns {Promise<{ status: number, headers: Object, body: Buffer, contentType: string, redirectBlocked: boolean }>}
38
- * @throws {ProxyRequestError} INVALID_PROXY_REQUEST, PROXY_NOT_CONFIGURED,
39
- * INVALID_CREDENTIALS, TIMEOUT, NETWORK_ERROR, RESPONSE_TOO_LARGE
40
- */
41
- async function proxyRequest(requester, params) {
42
- const {
43
- method,
44
- path,
45
- query,
46
- headers = {},
47
- body,
48
- baseUrl,
49
- timeoutMs,
50
- maxResponseBytes,
51
- isAllowedRedirect,
52
- } = params;
53
-
54
- const normalizedPath = normalizeProxyPath(path);
55
- const base =
56
- baseUrl ??
57
- requester.proxyBaseUrl ??
58
- requester.baseUrl ??
59
- requester.baseURL;
60
- if (!base) {
61
- throw new ProxyRequestError(
62
- 'PROXY_NOT_CONFIGURED',
63
- 'The module declares no base URL to proxy to'
64
- );
65
- }
66
- const target = joinProxyUrl(base, normalizedPath);
67
- const deadline = Date.now() + timeoutMs;
68
-
69
- const run = async () => {
70
- let url = target.url;
71
- let currentMethod = method;
72
- let currentBody = body;
73
- let currentQuery = query;
74
- for (let hop = 0; ; hop++) {
75
- const remaining = deadline - Date.now();
76
- if (remaining <= 0) throw timeoutError();
77
-
78
- const response = await requester._request(url, {
79
- method: currentMethod,
80
- headers: { ...headers },
81
- query: currentQuery,
82
- body: currentBody,
83
- returnFullRes: true,
84
- returnErrorResponses: true,
85
- noRetry: true,
86
- redirect: 'manual',
87
- size: maxResponseBytes,
88
- timeoutMs: remaining,
89
- });
90
-
91
- const location = response.headers.get('location');
92
- if (response.status >= 300 && response.status < 400 && location) {
93
- const next = sameApiRedirect(location, url, target);
94
- const nextMethod = redirectMethod(response.status, currentMethod);
95
- if (
96
- next &&
97
- hop < MAX_REDIRECTS &&
98
- (!isAllowedRedirect || isAllowedRedirect(next.path, nextMethod))
99
- ) {
100
- if (nextMethod !== currentMethod) currentBody = undefined;
101
- currentMethod = nextMethod;
102
- url = next.url;
103
- currentQuery = undefined;
104
- continue;
105
- }
106
- return readResponse(response, maxResponseBytes, {
107
- redirectBlocked: !next,
108
- });
109
- }
110
- return readResponse(response, maxResponseBytes, {
111
- redirectBlocked: false,
112
- });
113
- }
114
- };
115
-
116
- let timer;
117
- const timeout = new Promise((_, reject) => {
118
- timer = setTimeout(() => reject(timeoutError()), timeoutMs);
119
- });
120
- const work = run();
121
- // If the deadline wins, the request still settles later; nothing awaits it.
122
- work.catch(() => {});
123
- try {
124
- return await Promise.race([work, timeout]);
125
- } catch (error) {
126
- throw toProxyError(error);
127
- } finally {
128
- clearTimeout(timer);
129
- }
130
- }
131
-
132
- function timeoutError() {
133
- return new ProxyRequestError('TIMEOUT', 'The upstream API did not answer in time');
134
- }
135
-
136
- /**
137
- * A redirect target the proxy may follow: same origin as the module base URL
138
- * and under its base path. Returns null for anything else.
139
- */
140
- function sameApiRedirect(location, currentUrl, target) {
141
- let next;
142
- try {
143
- next = new URL(location, currentUrl);
144
- } catch {
145
- return null;
146
- }
147
- if (next.origin !== target.origin || next.protocol !== 'https:') return null;
148
- let decodedPath;
149
- try {
150
- decodedPath = decodeURIComponent(next.pathname);
151
- } catch {
152
- return null;
153
- }
154
- if (
155
- !(decodedPath === target.basePath || decodedPath.startsWith(`${target.basePath}/`))
156
- ) {
157
- return null;
158
- }
159
- const relativePath = decodedPath.slice(target.basePath.length) || '/';
160
- let checkedPath;
161
- try {
162
- checkedPath = normalizeProxyPath(relativePath);
163
- } catch {
164
- return null;
165
- }
166
- // _rawRequest percent-encodes the URL once, so hand it the decoded form.
167
- return {
168
- url: `${target.origin}${target.basePath}${checkedPath}${next.search ? decodeURI(next.search) : ''}`,
169
- path: checkedPath,
170
- };
171
- }
172
-
173
- /** Method for the next hop, as fetch does it. */
174
- function redirectMethod(status, method) {
175
- if (status === 303 && method !== 'HEAD') return 'GET';
176
- if ((status === 301 || status === 302) && method === 'POST') return 'GET';
177
- return method;
178
- }
179
-
180
- async function readResponse(response, maxResponseBytes, { redirectBlocked }) {
181
- const declaredLength = Number(response.headers.get('content-length'));
182
- if (Number.isFinite(declaredLength) && declaredLength > maxResponseBytes) {
183
- destroyBody(response);
184
- throw tooLarge();
185
- }
186
- const buffer = Buffer.from(await response.arrayBuffer());
187
- if (buffer.length > maxResponseBytes) throw tooLarge();
188
- const headers = {};
189
- response.headers.forEach((value, name) => {
190
- headers[name.toLowerCase()] = value;
191
- });
192
- return {
193
- status: response.status,
194
- headers,
195
- body: buffer,
196
- contentType: headers['content-type'] || '',
197
- redirectBlocked,
198
- };
199
- }
200
-
201
- function destroyBody(response) {
202
- try {
203
- response.body?.destroy?.();
204
- } catch {
205
- // nothing to clean up
206
- }
207
- }
208
-
209
- function tooLarge() {
210
- return new ProxyRequestError(
211
- 'RESPONSE_TOO_LARGE',
212
- 'The upstream response is larger than the proxy allows'
213
- );
214
- }
215
-
216
- /** Maps requester and fetch failures to the proxy's error codes. */
217
- function toProxyError(error) {
218
- if (error instanceof ProxyRequestError) return error;
219
- const causeType = error?.cause?.type ?? error?.type;
220
- if (causeType === 'max-size') return tooLarge();
221
- if (error?.isTimeout) return timeoutError();
222
- if (error instanceof FetchError || error?.name === 'FetchError') {
223
- if (error.statusCode === 401) {
224
- return new ProxyRequestError(
225
- 'INVALID_CREDENTIALS',
226
- 'The upstream API rejected the stored credentials; re-authorize the entity',
227
- { upstreamStatus: 401 }
228
- );
229
- }
230
- if (!error.statusCode) {
231
- return new ProxyRequestError(
232
- 'NETWORK_ERROR',
233
- 'Could not reach the upstream API'
234
- );
235
- }
236
- return new ProxyRequestError(
237
- 'UPSTREAM_ERROR',
238
- 'The upstream API returned an error',
239
- { upstreamStatus: error.statusCode }
240
- );
241
- }
242
- return error;
243
- }
244
-
245
- module.exports = { proxyRequest, MAX_REDIRECTS };