@friggframework/core 2.0.0-next.7 → 2.0.0-next.70

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 (293) hide show
  1. package/CLAUDE.md +694 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +210 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/core/CLAUDE.md +690 -0
  11. package/core/Worker.js +8 -21
  12. package/core/create-handler.js +14 -7
  13. package/credential/repositories/credential-repository-documentdb.js +304 -0
  14. package/credential/repositories/credential-repository-factory.js +54 -0
  15. package/credential/repositories/credential-repository-interface.js +98 -0
  16. package/credential/repositories/credential-repository-mongo.js +269 -0
  17. package/credential/repositories/credential-repository-postgres.js +287 -0
  18. package/credential/repositories/credential-repository.js +300 -0
  19. package/credential/use-cases/get-credential-for-user.js +25 -0
  20. package/credential/use-cases/update-authentication-status.js +15 -0
  21. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  22. package/database/adapters/lambda-invoker.js +97 -0
  23. package/database/config.js +154 -0
  24. package/database/documentdb-encryption-service.js +330 -0
  25. package/database/documentdb-utils.js +136 -0
  26. package/database/encryption/README.md +839 -0
  27. package/database/encryption/documentdb-encryption-service.md +3575 -0
  28. package/database/encryption/encryption-schema-registry.js +268 -0
  29. package/database/encryption/field-encryption-service.js +226 -0
  30. package/database/encryption/logger.js +79 -0
  31. package/database/encryption/prisma-encryption-extension.js +222 -0
  32. package/database/index.js +61 -21
  33. package/database/models/WebsocketConnection.js +16 -10
  34. package/database/models/readme.md +1 -0
  35. package/database/prisma.js +182 -0
  36. package/database/repositories/health-check-repository-documentdb.js +134 -0
  37. package/database/repositories/health-check-repository-factory.js +48 -0
  38. package/database/repositories/health-check-repository-interface.js +82 -0
  39. package/database/repositories/health-check-repository-mongodb.js +89 -0
  40. package/database/repositories/health-check-repository-postgres.js +82 -0
  41. package/database/repositories/health-check-repository.js +108 -0
  42. package/database/repositories/migration-status-repository-s3.js +137 -0
  43. package/database/use-cases/check-database-health-use-case.js +29 -0
  44. package/database/use-cases/check-database-state-use-case.js +81 -0
  45. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  46. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  47. package/database/use-cases/get-migration-status-use-case.js +93 -0
  48. package/database/use-cases/run-database-migration-use-case.js +139 -0
  49. package/database/use-cases/test-encryption-use-case.js +253 -0
  50. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  51. package/database/utils/mongodb-collection-utils.js +91 -0
  52. package/database/utils/mongodb-schema-init.js +106 -0
  53. package/database/utils/prisma-runner.js +477 -0
  54. package/database/utils/prisma-schema-parser.js +182 -0
  55. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  56. package/encrypt/Cryptor.js +34 -168
  57. package/encrypt/index.js +1 -2
  58. package/encrypt/test-encrypt.js +0 -2
  59. package/errors/client-safe-error.js +26 -0
  60. package/errors/fetch-error.js +2 -1
  61. package/errors/index.js +2 -0
  62. package/generated/prisma-mongodb/client.d.ts +1 -0
  63. package/generated/prisma-mongodb/client.js +4 -0
  64. package/generated/prisma-mongodb/default.d.ts +1 -0
  65. package/generated/prisma-mongodb/default.js +4 -0
  66. package/generated/prisma-mongodb/edge.d.ts +1 -0
  67. package/generated/prisma-mongodb/edge.js +335 -0
  68. package/generated/prisma-mongodb/index-browser.js +317 -0
  69. package/generated/prisma-mongodb/index.d.ts +22955 -0
  70. package/generated/prisma-mongodb/index.js +360 -0
  71. package/generated/prisma-mongodb/package.json +183 -0
  72. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  73. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  74. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  75. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  76. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  77. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  78. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  79. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  80. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  81. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  82. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  83. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  84. package/generated/prisma-mongodb/schema.prisma +362 -0
  85. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  86. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  87. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  88. package/generated/prisma-mongodb/wasm.js +342 -0
  89. package/generated/prisma-postgresql/client.d.ts +1 -0
  90. package/generated/prisma-postgresql/client.js +4 -0
  91. package/generated/prisma-postgresql/default.d.ts +1 -0
  92. package/generated/prisma-postgresql/default.js +4 -0
  93. package/generated/prisma-postgresql/edge.d.ts +1 -0
  94. package/generated/prisma-postgresql/edge.js +357 -0
  95. package/generated/prisma-postgresql/index-browser.js +339 -0
  96. package/generated/prisma-postgresql/index.d.ts +25131 -0
  97. package/generated/prisma-postgresql/index.js +382 -0
  98. package/generated/prisma-postgresql/package.json +183 -0
  99. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  100. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  101. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  102. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  103. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  104. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  105. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  106. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  107. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  108. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  109. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  110. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  111. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  112. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  113. package/generated/prisma-postgresql/schema.prisma +345 -0
  114. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  115. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  116. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  117. package/generated/prisma-postgresql/wasm.js +364 -0
  118. package/handlers/WEBHOOKS.md +653 -0
  119. package/handlers/app-definition-loader.js +38 -0
  120. package/handlers/app-handler-helpers.js +56 -0
  121. package/handlers/backend-utils.js +186 -0
  122. package/handlers/database-migration-handler.js +227 -0
  123. package/handlers/integration-event-dispatcher.js +54 -0
  124. package/handlers/routers/HEALTHCHECK.md +342 -0
  125. package/handlers/routers/auth.js +15 -0
  126. package/handlers/routers/db-migration.handler.js +29 -0
  127. package/handlers/routers/db-migration.js +326 -0
  128. package/handlers/routers/health.js +516 -0
  129. package/handlers/routers/integration-defined-routers.js +45 -0
  130. package/handlers/routers/integration-webhook-routers.js +67 -0
  131. package/handlers/routers/user.js +63 -0
  132. package/handlers/routers/websocket.js +57 -0
  133. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  134. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  135. package/handlers/workers/db-migration.js +352 -0
  136. package/handlers/workers/integration-defined-workers.js +27 -0
  137. package/index.js +78 -22
  138. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  139. package/infrastructure/scheduler/index.js +33 -0
  140. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  141. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  142. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  143. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  144. package/integrations/index.js +12 -10
  145. package/integrations/integration-base.js +326 -55
  146. package/integrations/integration-router.js +374 -179
  147. package/integrations/options.js +1 -1
  148. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  149. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  150. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  151. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  152. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  153. package/integrations/repositories/integration-mapping-repository.js +156 -0
  154. package/integrations/repositories/integration-repository-documentdb.js +210 -0
  155. package/integrations/repositories/integration-repository-factory.js +51 -0
  156. package/integrations/repositories/integration-repository-interface.js +127 -0
  157. package/integrations/repositories/integration-repository-mongo.js +303 -0
  158. package/integrations/repositories/integration-repository-postgres.js +352 -0
  159. package/integrations/repositories/process-repository-documentdb.js +243 -0
  160. package/integrations/repositories/process-repository-factory.js +53 -0
  161. package/integrations/repositories/process-repository-interface.js +90 -0
  162. package/integrations/repositories/process-repository-mongo.js +190 -0
  163. package/integrations/repositories/process-repository-postgres.js +217 -0
  164. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  165. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  166. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  167. package/integrations/use-cases/create-integration.js +83 -0
  168. package/integrations/use-cases/create-process.js +128 -0
  169. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  170. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  171. package/integrations/use-cases/get-integration-for-user.js +78 -0
  172. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  173. package/integrations/use-cases/get-integration-instance.js +83 -0
  174. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  175. package/integrations/use-cases/get-possible-integrations.js +27 -0
  176. package/integrations/use-cases/get-process.js +87 -0
  177. package/integrations/use-cases/index.js +19 -0
  178. package/integrations/use-cases/load-integration-context.js +71 -0
  179. package/integrations/use-cases/update-integration-messages.js +44 -0
  180. package/integrations/use-cases/update-integration-status.js +32 -0
  181. package/integrations/use-cases/update-integration.js +92 -0
  182. package/integrations/use-cases/update-process-metrics.js +201 -0
  183. package/integrations/use-cases/update-process-state.js +119 -0
  184. package/integrations/utils/map-integration-dto.js +37 -0
  185. package/jest-global-setup-noop.js +3 -0
  186. package/jest-global-teardown-noop.js +3 -0
  187. package/logs/logger.js +0 -4
  188. package/{module-plugin → modules}/entity.js +1 -1
  189. package/{module-plugin → modules}/index.js +0 -8
  190. package/modules/module-factory.js +56 -0
  191. package/modules/module.js +221 -0
  192. package/modules/repositories/module-repository-documentdb.js +335 -0
  193. package/modules/repositories/module-repository-factory.js +40 -0
  194. package/modules/repositories/module-repository-interface.js +129 -0
  195. package/modules/repositories/module-repository-mongo.js +408 -0
  196. package/modules/repositories/module-repository-postgres.js +453 -0
  197. package/modules/repositories/module-repository.js +345 -0
  198. package/modules/requester/api-key.js +52 -0
  199. package/modules/requester/oauth-2.js +384 -0
  200. package/{module-plugin → modules}/requester/requester.js +4 -2
  201. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  202. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  203. package/modules/tests/doubles/test-module-factory.js +16 -0
  204. package/modules/tests/doubles/test-module-repository.js +39 -0
  205. package/modules/use-cases/get-entities-for-user.js +32 -0
  206. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  207. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  208. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  209. package/modules/use-cases/get-module.js +74 -0
  210. package/modules/use-cases/process-authorization-callback.js +133 -0
  211. package/modules/use-cases/refresh-entity-options.js +72 -0
  212. package/modules/use-cases/test-module-auth.js +72 -0
  213. package/modules/utils/map-module-dto.js +18 -0
  214. package/package.json +82 -50
  215. package/prisma-mongodb/schema.prisma +362 -0
  216. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  217. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  218. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  219. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  220. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  221. package/prisma-postgresql/schema.prisma +345 -0
  222. package/queues/queuer-util.js +27 -22
  223. package/syncs/manager.js +468 -443
  224. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  225. package/syncs/repositories/sync-repository-factory.js +43 -0
  226. package/syncs/repositories/sync-repository-interface.js +109 -0
  227. package/syncs/repositories/sync-repository-mongo.js +239 -0
  228. package/syncs/repositories/sync-repository-postgres.js +319 -0
  229. package/syncs/sync.js +0 -1
  230. package/token/repositories/token-repository-documentdb.js +137 -0
  231. package/token/repositories/token-repository-factory.js +40 -0
  232. package/token/repositories/token-repository-interface.js +131 -0
  233. package/token/repositories/token-repository-mongo.js +219 -0
  234. package/token/repositories/token-repository-postgres.js +264 -0
  235. package/token/repositories/token-repository.js +219 -0
  236. package/types/core/index.d.ts +2 -2
  237. package/types/integrations/index.d.ts +2 -6
  238. package/types/module-plugin/index.d.ts +5 -59
  239. package/types/syncs/index.d.ts +0 -2
  240. package/user/repositories/user-repository-documentdb.js +441 -0
  241. package/user/repositories/user-repository-factory.js +52 -0
  242. package/user/repositories/user-repository-interface.js +201 -0
  243. package/user/repositories/user-repository-mongo.js +308 -0
  244. package/user/repositories/user-repository-postgres.js +360 -0
  245. package/user/tests/doubles/test-user-repository.js +72 -0
  246. package/user/use-cases/authenticate-user.js +127 -0
  247. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  248. package/user/use-cases/create-individual-user.js +61 -0
  249. package/user/use-cases/create-organization-user.js +47 -0
  250. package/user/use-cases/create-token-for-user-id.js +30 -0
  251. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  252. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  253. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  254. package/user/use-cases/login-user.js +122 -0
  255. package/user/user.js +125 -0
  256. package/utils/backend-path.js +38 -0
  257. package/utils/index.js +6 -0
  258. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  259. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  260. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  261. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  262. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  263. package/websocket/repositories/websocket-connection-repository.js +161 -0
  264. package/database/models/State.js +0 -9
  265. package/database/models/Token.js +0 -70
  266. package/database/mongo.js +0 -45
  267. package/encrypt/Cryptor.test.js +0 -32
  268. package/encrypt/encrypt.js +0 -132
  269. package/encrypt/encrypt.test.js +0 -1069
  270. package/errors/base-error.test.js +0 -32
  271. package/errors/fetch-error.test.js +0 -79
  272. package/errors/halt-error.test.js +0 -11
  273. package/errors/validation-errors.test.js +0 -120
  274. package/integrations/create-frigg-backend.js +0 -31
  275. package/integrations/integration-factory.js +0 -251
  276. package/integrations/integration-mapping.js +0 -43
  277. package/integrations/integration-model.js +0 -46
  278. package/integrations/integration-user.js +0 -144
  279. package/integrations/test/integration-base.test.js +0 -144
  280. package/lambda/TimeoutCatcher.test.js +0 -68
  281. package/logs/logger.test.js +0 -76
  282. package/module-plugin/auther.js +0 -393
  283. package/module-plugin/credential.js +0 -22
  284. package/module-plugin/entity-manager.js +0 -70
  285. package/module-plugin/manager.js +0 -169
  286. package/module-plugin/module-factory.js +0 -61
  287. package/module-plugin/requester/api-key.js +0 -36
  288. package/module-plugin/requester/oauth-2.js +0 -219
  289. package/module-plugin/requester/requester.test.js +0 -28
  290. package/module-plugin/test/auther.test.js +0 -97
  291. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  292. /package/{module-plugin → modules}/requester/basic.js +0 -0
  293. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,384 @@
1
+ const { Requester } = require('./requester');
2
+ const { get } = require('../../assertions');
3
+ const { ModuleConstants } = require('../ModuleConstants');
4
+
5
+ /**
6
+ * OAuth 2.0 Requester - Base class for API modules using OAuth 2.0 authentication.
7
+ *
8
+ * Supports multiple OAuth 2.0 grant types:
9
+ * - `authorization_code` (default): Standard OAuth flow with user consent
10
+ * - `client_credentials`: Server-to-server authentication without user
11
+ * - `password`: Resource Owner Password Credentials grant
12
+ *
13
+ * @extends Requester
14
+ *
15
+ * @example
16
+ * // Authorization Code flow (default)
17
+ * const api = new MyApi({ grant_type: 'authorization_code' });
18
+ * const authUrl = api.getAuthorizationUri();
19
+ * // After user authorizes...
20
+ * await api.getTokenFromCode(code);
21
+ *
22
+ * @example
23
+ * // Client Credentials flow
24
+ * const api = new MyApi({
25
+ * grant_type: 'client_credentials',
26
+ * client_id: process.env.CLIENT_ID,
27
+ * client_secret: process.env.CLIENT_SECRET,
28
+ * audience: 'https://api.example.com',
29
+ * });
30
+ * await api.getTokenFromClientCredentials();
31
+ */
32
+ class OAuth2Requester extends Requester {
33
+
34
+ static requesterType = ModuleConstants.authType.oauth2;
35
+
36
+ /**
37
+ * Creates an OAuth2Requester instance.
38
+ *
39
+ * @param {Object} params - Configuration parameters
40
+ * @param {string} [params.grant_type='authorization_code'] - OAuth grant type:
41
+ * 'authorization_code', 'client_credentials', or 'password'
42
+ * @param {string} [params.client_id] - OAuth client ID
43
+ * @param {string} [params.client_secret] - OAuth client secret
44
+ * @param {string} [params.redirect_uri] - OAuth redirect URI for authorization code flow
45
+ * @param {string} [params.scope] - OAuth scopes (space-separated)
46
+ * @param {string} [params.authorizationUri] - Authorization endpoint URL
47
+ * @param {string} [params.tokenUri] - Token endpoint URL for exchanging codes/credentials
48
+ * @param {string} [params.baseURL] - Base URL for API requests
49
+ * @param {string} [params.access_token] - Existing access token
50
+ * @param {string} [params.refresh_token] - Existing refresh token
51
+ * @param {Date} [params.accessTokenExpire] - Access token expiration date
52
+ * @param {Date} [params.refreshTokenExpire] - Refresh token expiration date
53
+ * @param {string} [params.audience] - Token audience (for client_credentials)
54
+ * @param {string} [params.username] - Username (for password grant)
55
+ * @param {string} [params.password] - Password (for password grant)
56
+ * @param {string} [params.state] - OAuth state parameter for CSRF protection
57
+ */
58
+ constructor(params) {
59
+ super(params);
60
+ /** @type {string} Delegate type for token update notifications */
61
+ this.DLGT_TOKEN_UPDATE = 'TOKEN_UPDATE';
62
+ /** @type {string} Delegate type for token deauthorization notifications */
63
+ this.DLGT_TOKEN_DEAUTHORIZED = 'TOKEN_DEAUTHORIZED';
64
+
65
+ this.delegateTypes.push(this.DLGT_TOKEN_UPDATE);
66
+ this.delegateTypes.push(this.DLGT_TOKEN_DEAUTHORIZED);
67
+
68
+ /** @type {string} OAuth grant type */
69
+ this.grant_type = get(params, 'grant_type', 'authorization_code');
70
+ /** @type {string|null} OAuth client ID */
71
+ this.client_id = get(params, 'client_id', null);
72
+ /** @type {string|null} OAuth client secret */
73
+ this.client_secret = get(params, 'client_secret', null);
74
+ /** @type {string|null} OAuth redirect URI */
75
+ this.redirect_uri = get(params, 'redirect_uri', null);
76
+ /** @type {string|null} OAuth scopes */
77
+ this.scope = get(params, 'scope', null);
78
+ /** @type {string|null} Authorization endpoint URL */
79
+ this.authorizationUri = get(params, 'authorizationUri', null);
80
+ /** @type {string|null} Token endpoint URL */
81
+ this.tokenUri = get(params, 'tokenUri', null);
82
+ /** @type {string|null} Base URL for API requests */
83
+ this.baseURL = get(params, 'baseURL', null);
84
+ /** @type {string|null} Current access token */
85
+ this.access_token = get(params, 'access_token', null);
86
+ /** @type {string|null} Current refresh token */
87
+ this.refresh_token = get(params, 'refresh_token', null);
88
+ /** @type {Date|null} Access token expiration */
89
+ this.accessTokenExpire = get(params, 'accessTokenExpire', null);
90
+ /** @type {Date|null} Refresh token expiration */
91
+ this.refreshTokenExpire = get(params, 'refreshTokenExpire', null);
92
+ /** @type {string|null} Token audience */
93
+ this.audience = get(params, 'audience', null);
94
+ /** @type {string|null} Username for password grant */
95
+ this.username = get(params, 'username', null);
96
+ /** @type {string|null} Password for password grant */
97
+ this.password = get(params, 'password', null);
98
+ /** @type {string|null} OAuth state for CSRF protection */
99
+ this.state = get(params, 'state', null);
100
+
101
+ /** @type {boolean} Whether this requester supports token refresh */
102
+ this.isRefreshable = true;
103
+ }
104
+
105
+ /**
106
+ * Sets OAuth tokens and calculates expiration times.
107
+ * Notifies delegates of token update via DLGT_TOKEN_UPDATE.
108
+ *
109
+ * @param {Object} params - Token response from OAuth server
110
+ * @param {string} params.access_token - The access token
111
+ * @param {string} [params.refresh_token] - The refresh token (if provided)
112
+ * @param {number} [params.expires_in] - Access token lifetime in seconds
113
+ * @param {number} [params.x_refresh_token_expires_in] - Refresh token lifetime in seconds
114
+ * @returns {Promise<void>}
115
+ */
116
+ async setTokens(params) {
117
+ this.access_token = get(params, 'access_token');
118
+ const newRefreshToken = get(params, 'refresh_token', null);
119
+ if (newRefreshToken !== null) {
120
+ this.refresh_token = newRefreshToken;
121
+ }
122
+ const accessExpiresIn = get(params, 'expires_in', null);
123
+ const refreshExpiresIn = get(
124
+ params,
125
+ 'x_refresh_token_expires_in',
126
+ null
127
+ );
128
+
129
+ this.accessTokenExpire = new Date(Date.now() + accessExpiresIn * 1000);
130
+ if (refreshExpiresIn !== null) {
131
+ this.refreshTokenExpire = new Date(Date.now() + refreshExpiresIn * 1000);
132
+ }
133
+
134
+ await this.notify(this.DLGT_TOKEN_UPDATE);
135
+ }
136
+
137
+ /**
138
+ * Gets the OAuth authorization URL for initiating the authorization code flow.
139
+ *
140
+ * @returns {string|null} The authorization URL
141
+ */
142
+ getAuthorizationUri() {
143
+ return this.authorizationUri;
144
+ }
145
+
146
+ /**
147
+ * Returns authorization requirements for this OAuth flow.
148
+ *
149
+ * @returns {{url: string|null, type: string}} Authorization requirements
150
+ */
151
+ getAuthorizationRequirements() {
152
+ return {
153
+ url: this.getAuthorizationUri(),
154
+ type: 'oauth2',
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Exchanges an authorization code for access and refresh tokens.
160
+ * Requires client_id, client_secret, redirect_uri, and tokenUri to be set.
161
+ *
162
+ * @param {string} code - The authorization code from the OAuth callback
163
+ * @returns {Promise<Object>} Token response containing access_token, refresh_token, etc.
164
+ */
165
+ async getTokenFromCode(code) {
166
+ const params = new URLSearchParams();
167
+ params.append('grant_type', 'authorization_code');
168
+ params.append('client_id', this.client_id);
169
+ params.append('client_secret', this.client_secret);
170
+ params.append('redirect_uri', this.redirect_uri);
171
+ params.append('scope', this.scope);
172
+ params.append('code', code);
173
+ const options = {
174
+ body: params,
175
+ headers: {
176
+ 'Content-Type': 'application/x-www-form-urlencoded',
177
+ },
178
+ url: this.tokenUri,
179
+ };
180
+ const response = await this._post(options, false);
181
+ await this.setTokens(response);
182
+ return response;
183
+ }
184
+
185
+ /**
186
+ * Exchanges an authorization code for tokens using Basic Auth header.
187
+ * Alternative to getTokenFromCode() for OAuth servers requiring Basic Auth.
188
+ * Override getTokenFromCode() in child class to use this instead.
189
+ *
190
+ * @param {string} code - The authorization code from the OAuth callback
191
+ * @returns {Promise<Object>} Token response containing access_token, refresh_token, etc.
192
+ */
193
+ async getTokenFromCodeBasicAuthHeader(code) {
194
+ const params = new URLSearchParams();
195
+ params.append('grant_type', 'authorization_code');
196
+ params.append('client_id', this.client_id);
197
+ params.append('redirect_uri', this.redirect_uri);
198
+ params.append('code', code);
199
+
200
+ const options = {
201
+ body: params,
202
+ headers: {
203
+ 'Content-Type': 'application/x-www-form-urlencoded',
204
+ Authorization: `Basic ${Buffer.from(
205
+ `${this.client_id}:${this.client_secret}`
206
+ ).toString('base64')}`,
207
+ },
208
+ url: this.tokenUri,
209
+ };
210
+
211
+ const response = await this._post(options, false);
212
+ await this.setTokens(response);
213
+ return response;
214
+ }
215
+
216
+ /**
217
+ * Refreshes the access token using the refresh token.
218
+ * Used for authorization_code and password grant types.
219
+ *
220
+ * @param {Object} refreshTokenObject - Object containing refresh_token
221
+ * @param {string} refreshTokenObject.refresh_token - The refresh token
222
+ * @returns {Promise<Object>} New token response
223
+ */
224
+ async refreshAccessToken(refreshTokenObject) {
225
+ this.access_token = undefined;
226
+ const params = new URLSearchParams();
227
+ params.append('grant_type', 'refresh_token');
228
+ params.append('client_id', this.client_id);
229
+ params.append('client_secret', this.client_secret);
230
+ params.append('refresh_token', refreshTokenObject.refresh_token);
231
+ params.append('redirect_uri', this.redirect_uri);
232
+
233
+ const options = {
234
+ body: params,
235
+ url: this.tokenUri,
236
+ headers: {
237
+ 'Content-Type': 'application/x-www-form-urlencoded',
238
+ },
239
+ };
240
+ const response = await this._post(options, false);
241
+ await this.setTokens(response);
242
+ return response;
243
+ }
244
+
245
+ /**
246
+ * Adds OAuth Bearer token to request headers.
247
+ * Clears any existing Authorization header first to prevent stale tokens
248
+ * from being reused after failed refresh attempts.
249
+ *
250
+ * @param {Object} headers - Headers object to modify
251
+ * @returns {Promise<Object>} Headers with Authorization added
252
+ */
253
+ async addAuthHeaders(headers) {
254
+ delete headers.Authorization;
255
+ if (this.access_token) {
256
+ headers.Authorization = `Bearer ${this.access_token}`;
257
+ }
258
+
259
+ return headers;
260
+ }
261
+
262
+ /**
263
+ * Checks if the requester has valid authentication.
264
+ *
265
+ * @returns {boolean} True if authenticated with valid tokens
266
+ */
267
+ isAuthenticated() {
268
+ return !!(
269
+ this.access_token !== null &&
270
+ this.refresh_token !== null &&
271
+ this.accessTokenExpire &&
272
+ this.refreshTokenExpire
273
+ );
274
+ }
275
+
276
+ /**
277
+ * Refreshes authentication based on the configured grant type.
278
+ * - For authorization_code/password: Uses refreshAccessToken() with refresh_token
279
+ * - For client_credentials: Uses getTokenFromClientCredentials() to get new token
280
+ *
281
+ * On failure, notifies delegates via DLGT_INVALID_AUTH.
282
+ *
283
+ * @returns {Promise<boolean>} True if refresh succeeded, false if failed
284
+ */
285
+ async refreshAuth() {
286
+ try {
287
+ console.log('[OAuth2Requester.refreshAuth] Starting token refresh', {
288
+ grant_type: this.grant_type,
289
+ has_refresh_token: !!this.refresh_token,
290
+ has_client_id: !!this.client_id,
291
+ has_client_secret: !!this.client_secret,
292
+ has_token_uri: !!this.tokenUri,
293
+ tokenUri: this.tokenUri,
294
+ });
295
+
296
+ if (this.grant_type !== 'client_credentials') {
297
+ await this.refreshAccessToken({
298
+ refresh_token: this.refresh_token,
299
+ });
300
+ } else {
301
+ await this.getTokenFromClientCredentials();
302
+ }
303
+ console.log('[OAuth2Requester.refreshAuth] Token refresh succeeded');
304
+ return true;
305
+ } catch (error) {
306
+ console.error('[OAuth2Requester.refreshAuth] Token refresh failed', {
307
+ error_message: error?.message,
308
+ error_name: error?.name,
309
+ response_status: error?.response?.status,
310
+ response_data: error?.response?.data,
311
+ });
312
+ await this.notify(this.DLGT_INVALID_AUTH);
313
+ return false;
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Obtains tokens using the Resource Owner Password Credentials grant.
319
+ * Requires username and password to be set.
320
+ *
321
+ * @returns {Promise<Object|undefined>} Token response or undefined on error
322
+ */
323
+ async getTokenFromUsernamePassword() {
324
+ try {
325
+ const url = this.tokenUri;
326
+
327
+ const body = {
328
+ username: this.username,
329
+ password: this.password,
330
+ grant_type: 'password',
331
+ };
332
+ const headers = {
333
+ 'Content-Type': 'application/json',
334
+ };
335
+
336
+ const tokenRes = await this._post({
337
+ url,
338
+ body,
339
+ headers,
340
+ });
341
+
342
+ await this.setTokens(tokenRes);
343
+ return tokenRes;
344
+ } catch {
345
+ await this.notify(this.DLGT_INVALID_AUTH);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Obtains tokens using the Client Credentials grant.
351
+ * Used for server-to-server authentication without a user context.
352
+ * Requires client_id, client_secret, and optionally audience to be set.
353
+ *
354
+ * @returns {Promise<Object|undefined>} Token response or undefined on error
355
+ */
356
+ async getTokenFromClientCredentials() {
357
+ try {
358
+ const url = this.tokenUri;
359
+
360
+ const body = {
361
+ audience: this.audience,
362
+ client_id: this.client_id,
363
+ client_secret: this.client_secret,
364
+ grant_type: 'client_credentials',
365
+ };
366
+ const headers = {
367
+ 'Content-Type': 'application/json',
368
+ };
369
+
370
+ const tokenRes = await this._post({
371
+ url,
372
+ body,
373
+ headers,
374
+ });
375
+
376
+ await this.setTokens(tokenRes);
377
+ return tokenRes;
378
+ } catch {
379
+ await this.notify(this.DLGT_INVALID_AUTH);
380
+ }
381
+ }
382
+ }
383
+
384
+ module.exports = { OAuth2Requester };
@@ -75,8 +75,10 @@ class Requester extends Delegate {
75
75
  await this.notify(this.DLGT_INVALID_AUTH);
76
76
  } else {
77
77
  this.refreshCount++;
78
- await this.refreshAuth();
79
- return this._request(url, options, i + 1); // Retries
78
+ const refreshSucceeded = await this.refreshAuth();
79
+ if (refreshSucceeded) {
80
+ return this._request(url, options, i + 1);
81
+ }
80
82
  }
81
83
  }
82
84
 
@@ -1,5 +1,5 @@
1
- const { get } = require('../../assertions');
2
- const { OAuth2Requester } = require('../../module-plugin');
1
+ const { get } = require('../../../assertions');
2
+ const { OAuth2Requester } = require('../..');
3
3
 
4
4
  class Api extends OAuth2Requester {
5
5
  constructor(params) {
@@ -23,7 +23,12 @@ class Api extends OAuth2Requester {
23
23
  return this.authorizationUri;
24
24
  }
25
25
 
26
-
26
+ getAuthorizationRequirements() {
27
+ return {
28
+ url: this.getAuthUri(),
29
+ type: 'oauth2',
30
+ };
31
+ }
27
32
  }
28
33
 
29
34
  module.exports = { Api };
@@ -1,22 +1,26 @@
1
1
  require('dotenv').config();
2
- const {Api} = require('./api');
3
- const {get} = require('../../assertions');
4
- const config = {name: 'anapi'}
2
+ const { Api } = require('./api');
3
+ const { get } = require('../../../assertions');
4
+ const config = { name: 'anapi' }
5
5
 
6
6
  const Definition = {
7
7
  API: Api,
8
- getName: function() {return config.name},
8
+ getAuthorizationRequirements: () => ({
9
+ url: 'http://localhost:3000/redirect/anapi',
10
+ type: 'oauth2',
11
+ }),
12
+ getName: function () { return config.name },
9
13
  moduleName: config.name,
10
14
  modelName: 'AnApi',
11
15
  requiredAuthMethods: {
12
- getToken: async function(api, params){
16
+ getToken: async function (api, params) {
13
17
  const code = get(params.data, 'code');
14
18
  return api.getTokenFromCode(code);
15
19
  },
16
- getEntityDetails: async function(api, callbackParams, tokenResponse, userId) {
20
+ getEntityDetails: async function (api, callbackParams, tokenResponse, userId) {
17
21
  const userDetails = await api.getUserDetails();
18
22
  return {
19
- identifiers: { externalId: userDetails.portalId, user: userId },
23
+ identifiers: { externalId: userDetails.portalId, userId },
20
24
  details: { name: userDetails.hub_domain },
21
25
  }
22
26
  },
@@ -26,14 +30,14 @@ const Definition = {
26
30
  ],
27
31
  entity: [],
28
32
  },
29
- getCredentialDetails: async function(api, userId) {
33
+ getCredentialDetails: async function (api, userId) {
30
34
  const userDetails = await api.getUserDetails();
31
35
  return {
32
- identifiers: { externalId: userDetails.portalId, user: userId },
36
+ identifiers: { externalId: userDetails.portalId, userId },
33
37
  details: {}
34
38
  };
35
39
  },
36
- testAuthRequest: async function(api){
40
+ testAuthRequest: async function (api) {
37
41
  return api.getUserDetails()
38
42
  },
39
43
  },
@@ -0,0 +1,16 @@
1
+ class TestModuleFactory {
2
+ constructor() { }
3
+
4
+ async getModuleInstance(entityId, userId) {
5
+ // return minimal stub module with getName and api property
6
+ return {
7
+ getName() { return 'stubModule'; },
8
+ api: {},
9
+ entityId,
10
+ userId,
11
+ testAuth: async () => true,
12
+ };
13
+ }
14
+ }
15
+
16
+ module.exports = { TestModuleFactory };
@@ -0,0 +1,39 @@
1
+ class TestModuleRepository {
2
+ constructor() {
3
+ this.entities = new Map();
4
+ }
5
+
6
+ addEntity(entity) {
7
+ this.entities.set(entity.id, entity);
8
+ }
9
+
10
+ async findEntityById(id) {
11
+ return this.entities.get(id);
12
+ }
13
+
14
+ async findEntitiesByIds(ids) {
15
+ return ids.map((id) => this.entities.get(id));
16
+ }
17
+
18
+ async findEntity(filter) {
19
+ if (!filter || typeof filter !== 'object') {
20
+ return null;
21
+ }
22
+
23
+ if (filter.id && this.entities.has(filter.id)) {
24
+ return this.entities.get(filter.id);
25
+ }
26
+
27
+ if (filter.externalId) {
28
+ for (const entity of this.entities.values()) {
29
+ if (entity.externalId === filter.externalId) {
30
+ return entity;
31
+ }
32
+ }
33
+ }
34
+
35
+ return null;
36
+ }
37
+ }
38
+
39
+ module.exports = { TestModuleRepository };
@@ -0,0 +1,32 @@
1
+ const { Module } = require('../module');
2
+ const { mapModuleClassToModuleDTO } = require('../utils/map-module-dto');
3
+
4
+ class GetEntitiesForUser {
5
+ constructor({ moduleRepository, moduleDefinitions }) {
6
+ this.moduleRepository = moduleRepository;
7
+
8
+ this.definitionMap = new Map();
9
+ for (const definition of moduleDefinitions) {
10
+ this.definitionMap.set(definition.moduleName, definition);
11
+ }
12
+ }
13
+
14
+ async execute(userId) {
15
+ const entities = await this.moduleRepository.findEntitiesByUserId(
16
+ userId
17
+ );
18
+
19
+ return entities.map((entity) => {
20
+ const definition = this.definitionMap.get(entity.moduleName);
21
+
22
+ const moduleInstance = new Module({
23
+ userId,
24
+ definition: definition,
25
+ entity: entity,
26
+ });
27
+ return mapModuleClassToModuleDTO(moduleInstance);
28
+ });
29
+ }
30
+ }
31
+
32
+ module.exports = { GetEntitiesForUser };
@@ -0,0 +1,71 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetEntityOptionsById {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
7
+ * @param {} params.moduleDefinitions
8
+ */
9
+ constructor({ moduleRepository, moduleDefinitions }) {
10
+ this.moduleRepository = moduleRepository;
11
+ this.moduleDefinitions = moduleDefinitions;
12
+ }
13
+
14
+ /**
15
+ * Retrieve entity options for a given entity
16
+ *
17
+ * @param {string|number} entityId - Entity ID to retrieve options for
18
+ * @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
19
+ * @returns {Promise<Object>} Entity options
20
+ */
21
+ async execute(entityId, userIdOrUser) {
22
+ // Support both userId (backward compatible) and User object (new pattern)
23
+ const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
24
+ ? userIdOrUser.getId()
25
+ : userIdOrUser;
26
+
27
+ const entity = await this.moduleRepository.findEntityById(
28
+ entityId,
29
+ userId
30
+ );
31
+
32
+ if (!entity) {
33
+ throw new Error(`Entity ${entityId} not found`);
34
+ }
35
+
36
+ // Validate entity ownership
37
+ const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
38
+ ? userIdOrUser.ownsUserId(entity.userId)
39
+ : entity.userId?.toString() === userId?.toString();
40
+
41
+ if (!isOwned) {
42
+ throw new Error(
43
+ `Entity ${entityId} does not belong to user ${userId}`
44
+ );
45
+ }
46
+
47
+ const entityType = entity.moduleName;
48
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
49
+ const modelName =
50
+ Module.getEntityModelFromDefinition(def).modelName;
51
+ return entityType === modelName;
52
+ });
53
+
54
+ if (!moduleDefinition) {
55
+ throw new Error(
56
+ `Module definition not found for entity type: ${entityType}`
57
+ );
58
+ }
59
+
60
+ const module = new Module({
61
+ userId,
62
+ entity,
63
+ definition: moduleDefinition,
64
+ });
65
+
66
+ const entityOptions = await module.getEntityOptions();
67
+ return entityOptions;
68
+ }
69
+ }
70
+
71
+ module.exports = { GetEntityOptionsById };
@@ -0,0 +1,34 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetEntityOptionsByType {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {} params.moduleDefinitions
7
+ */
8
+ constructor({ moduleDefinitions }) {
9
+ this.moduleDefinitions = moduleDefinitions;
10
+ }
11
+
12
+ /**
13
+ * Retrieve a Module instance for a given user and entity/module type.
14
+ * @param {string} userId
15
+ * @param {string} type – human-readable module/entity type (e.g. "Hubspot")
16
+ */
17
+ async execute(userId, type) {
18
+ const moduleDefinition = this.moduleDefinitions.find(
19
+ (def) => def.getName() === type
20
+ );
21
+ if (!moduleDefinition) {
22
+ throw new Error(`Module definition not found for type: ${type}`);
23
+ }
24
+ const moduleInstance = new Module({
25
+ userId,
26
+ definition: moduleDefinition,
27
+ });
28
+
29
+ const entityOptions = await moduleInstance.getEntityOptions();
30
+ return entityOptions;
31
+ }
32
+ }
33
+
34
+ module.exports = { GetEntityOptionsByType };