@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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 (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -15,9 +15,10 @@ class GetModule {
15
15
  */
16
16
  async execute(entityId, userIdOrUser) {
17
17
  // Support both userId (backward compatible) and User object (new pattern)
18
- const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
19
- ? userIdOrUser.getId()
20
- : userIdOrUser;
18
+ const userId =
19
+ typeof userIdOrUser === 'object' && userIdOrUser?.getId
20
+ ? userIdOrUser.getId()
21
+ : userIdOrUser;
21
22
 
22
23
  const entity = await this.moduleRepository.findEntityById(
23
24
  entityId,
@@ -31,9 +32,10 @@ class GetModule {
31
32
  // Validate entity ownership
32
33
  // If User object provided, use ownsUserId to check linked users
33
34
  // Otherwise fall back to simple equality check
34
- const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
35
- ? userIdOrUser.ownsUserId(entity.userId)
36
- : entity.userId?.toString() === userId?.toString();
35
+ const isOwned =
36
+ typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
37
+ ? userIdOrUser.ownsUserId(entity.userId)
38
+ : entity.userId?.toString() === userId?.toString();
37
39
 
38
40
  if (!isOwned) {
39
41
  throw new Error(
@@ -43,7 +45,8 @@ class GetModule {
43
45
 
44
46
  const entityType = entity.moduleName;
45
47
  const moduleDefinition = this.moduleDefinitions.find((def) => {
46
- const modelName = Module.getEntityModelFromDefinition(def).modelName;
48
+ const modelName =
49
+ Module.getEntityModelFromDefinition(def).modelName;
47
50
  return entityType === modelName;
48
51
  });
49
52
 
@@ -67,8 +70,8 @@ class GetModule {
67
70
  credential: module.credential,
68
71
  externalId: module.entity.externalId,
69
72
  userId: module.entity.user.toString(),
70
- }
73
+ };
71
74
  }
72
75
  }
73
76
 
74
- module.exports = { GetModule };
77
+ module.exports = { GetModule };
@@ -14,7 +14,7 @@ class ProcessAuthorizationCallback {
14
14
  this.moduleDefinitions = moduleDefinitions;
15
15
  }
16
16
 
17
- async execute(userId, entityType, params) {
17
+ async execute(userId, entityType, params, isGlobal = false) {
18
18
  const moduleDefinition = this.moduleDefinitions.find((def) => {
19
19
  return entityType === def.moduleName;
20
20
  });
@@ -70,7 +70,8 @@ class ProcessAuthorizationCallback {
70
70
  const persistedEntity = await this.findOrCreateEntity(
71
71
  entityDetails,
72
72
  entityType,
73
- module.credential.id
73
+ module.credential.id,
74
+ isGlobal
74
75
  );
75
76
 
76
77
  return {
@@ -93,18 +94,22 @@ class ProcessAuthorizationCallback {
93
94
  );
94
95
  credentialDetails.details.authIsValid = true;
95
96
 
96
- const persisted = await this.credentialRepository.upsertCredential(credentialDetails);
97
+ const persisted = await this.credentialRepository.upsertCredential(
98
+ credentialDetails
99
+ );
97
100
  module.credential = persisted;
98
101
  }
99
102
 
100
- async findOrCreateEntity(entityDetails, moduleName, credentialId) {
103
+ async findOrCreateEntity(entityDetails, moduleName, credentialId, isGlobal = false) {
101
104
  const { identifiers, details } = entityDetails;
102
105
 
103
106
  // Support both 'user' and 'userId' field names from module definitions
104
107
  // Some modules use 'user' (legacy), others use 'userId' (newer pattern)
105
108
  const userId = identifiers.user || identifiers.userId;
106
109
 
107
- if (!userId) {
110
+ // For global entities, userId is not required (it will be null)
111
+ // For user-specific entities, userId must be provided for security
112
+ if (!isGlobal && !userId) {
108
113
  throw new Error(
109
114
  `Module definition for ${moduleName} must return 'user' or 'userId' in identifiers from getEntityDetails(). ` +
110
115
  `Without userId, entity lookup would match across all users (security issue).`
@@ -113,7 +118,7 @@ class ProcessAuthorizationCallback {
113
118
 
114
119
  const existingEntity = await this.moduleRepository.findEntity({
115
120
  externalId: identifiers.externalId,
116
- user: userId,
121
+ user: isGlobal ? null : userId,
117
122
  moduleName: moduleName,
118
123
  });
119
124
 
@@ -126,6 +131,7 @@ class ProcessAuthorizationCallback {
126
131
  ...details,
127
132
  moduleName: moduleName,
128
133
  credential: credentialId,
134
+ isGlobal,
129
135
  });
130
136
  }
131
137
  }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Process Authorization Step Use Case
3
+ * Business logic for processing individual steps in multi-step authorization workflows
4
+ *
5
+ * Responsibilities:
6
+ * - Load and validate authorization session
7
+ * - Verify step sequence and user ownership
8
+ * - Delegate to module's step processing logic
9
+ * - Update session state and persist changes
10
+ * - Return next step requirements or completion data
11
+ *
12
+ * @example
13
+ * ```javascript
14
+ * const useCase = new ProcessAuthorizationStepUseCase({
15
+ * authSessionRepository: createAuthorizationSessionRepository(),
16
+ * moduleDefinitions: [{ moduleName: 'nagaris', definition: NagarisDefinition, apiClass: NagarisApi }]
17
+ * });
18
+ *
19
+ * // Process step 1 (email submission)
20
+ * const result = await useCase.execute('session-id', 'user123', 1, { email: 'user@example.com' });
21
+ * // Returns: { nextStep: 2, sessionId, requirements: {...}, message: 'OTP sent to email' }
22
+ *
23
+ * // Process step 2 (OTP verification)
24
+ * const result = await useCase.execute('session-id', 'user123', 2, { email: 'user@example.com', otp: '123456' });
25
+ * // Returns: { completed: true, authData: {...}, sessionId }
26
+ * ```
27
+ */
28
+ class ProcessAuthorizationStepUseCase {
29
+ /**
30
+ * @param {Object} params - Dependencies
31
+ * @param {import('../repositories/authorization-session-repository-interface').AuthorizationSessionRepositoryInterface} params.authSessionRepository - Session repository
32
+ * @param {Array<Object>} params.moduleDefinitions - Array of module definitions with structure: { moduleName, definition, apiClass }
33
+ */
34
+ constructor({ authSessionRepository, moduleDefinitions }) {
35
+ if (!authSessionRepository) {
36
+ throw new Error('authSessionRepository is required');
37
+ }
38
+ if (!moduleDefinitions || !Array.isArray(moduleDefinitions)) {
39
+ throw new Error('moduleDefinitions array is required');
40
+ }
41
+ this.authSessionRepository = authSessionRepository;
42
+ this.moduleDefinitions = moduleDefinitions;
43
+ }
44
+
45
+ /**
46
+ * Process a single step of multi-step authorization
47
+ *
48
+ * @param {string} sessionId - Unique session identifier
49
+ * @param {string} userId - User ID (for security validation)
50
+ * @param {number} step - Current step number being processed
51
+ * @param {Object} stepData - Data submitted for this step
52
+ * @returns {Promise<Object>} Result object with nextStep info or completion data
53
+ * @throws {Error} If session not found, validation fails, or step processing fails
54
+ */
55
+ async execute(sessionId, userId, step, stepData) {
56
+ // Validate inputs
57
+ if (!sessionId) {
58
+ throw new Error('sessionId is required');
59
+ }
60
+ if (!userId) {
61
+ throw new Error('userId is required');
62
+ }
63
+ if (!step || step < 1) {
64
+ throw new Error('step must be >= 1');
65
+ }
66
+ if (!stepData || typeof stepData !== 'object') {
67
+ throw new Error('stepData object is required');
68
+ }
69
+
70
+ // Load session from repository
71
+ const session = await this.authSessionRepository.findBySessionId(
72
+ sessionId
73
+ );
74
+
75
+ if (!session) {
76
+ throw new Error('Authorization session not found or expired');
77
+ }
78
+
79
+ // Security: Verify session belongs to this user
80
+ if (session.userId !== userId) {
81
+ throw new Error('Session does not belong to this user');
82
+ }
83
+
84
+ // Verify session hasn't expired (double-check beyond repository filter)
85
+ if (session.isExpired()) {
86
+ throw new Error('Authorization session has expired');
87
+ }
88
+
89
+ // Validate step sequence - prevent skipping steps
90
+ // Allow step 1 to be re-submitted (restart flow), otherwise must be sequential
91
+ if (session.currentStep !== step && step !== 1) {
92
+ throw new Error(
93
+ `Expected step ${session.currentStep}, received step ${step}`
94
+ );
95
+ }
96
+
97
+ // Find module definition for this entity type
98
+ const moduleDefinition = this.moduleDefinitions.find(
99
+ (def) => def.moduleName === session.entityType
100
+ );
101
+
102
+ if (!moduleDefinition) {
103
+ throw new Error(
104
+ `Module definition not found: ${session.entityType}`
105
+ );
106
+ }
107
+
108
+ // Get module's Definition class
109
+ const ModuleDefinition = moduleDefinition.definition;
110
+
111
+ // Validate module supports multi-step auth
112
+ if (!ModuleDefinition.processAuthorizationStep) {
113
+ throw new Error(
114
+ `Module ${session.entityType} does not support multi-step authorization`
115
+ );
116
+ }
117
+
118
+ // Create API instance for this step
119
+ const ApiClass = moduleDefinition.apiClass;
120
+ const api = new ApiClass({ userId });
121
+
122
+ // Delegate to module's step processing logic
123
+ const result = await ModuleDefinition.processAuthorizationStep(
124
+ api,
125
+ step,
126
+ stepData,
127
+ session.stepData // Pass accumulated data from previous steps
128
+ );
129
+
130
+ // Handle final step completion
131
+ if (result.completed) {
132
+ session.markComplete();
133
+ await this.authSessionRepository.update(session);
134
+
135
+ return {
136
+ completed: true,
137
+ authData: result.authData,
138
+ sessionId,
139
+ };
140
+ }
141
+
142
+ // Handle intermediate step - advance session
143
+ session.advanceStep(result.stepData || {});
144
+ await this.authSessionRepository.update(session);
145
+
146
+ // Get requirements for next step
147
+ const nextRequirements =
148
+ await ModuleDefinition.getAuthRequirementsForStep(result.nextStep);
149
+
150
+ return {
151
+ nextStep: result.nextStep,
152
+ totalSteps: session.maxSteps,
153
+ sessionId,
154
+ requirements: nextRequirements,
155
+ message: result.message || undefined,
156
+ };
157
+ }
158
+ }
159
+
160
+ module.exports = { ProcessAuthorizationStepUseCase };
@@ -21,9 +21,10 @@ class RefreshEntityOptions {
21
21
  */
22
22
  async execute(entityId, userIdOrUser, options) {
23
23
  // Support both userId (backward compatible) and User object (new pattern)
24
- const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
25
- ? userIdOrUser.getId()
26
- : userIdOrUser;
24
+ const userId =
25
+ typeof userIdOrUser === 'object' && userIdOrUser?.getId
26
+ ? userIdOrUser.getId()
27
+ : userIdOrUser;
27
28
 
28
29
  const entity = await this.moduleRepository.findEntityById(
29
30
  entityId,
@@ -35,9 +36,10 @@ class RefreshEntityOptions {
35
36
  }
36
37
 
37
38
  // Validate entity ownership
38
- const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
39
- ? userIdOrUser.ownsUserId(entity.userId)
40
- : entity.userId?.toString() === userId?.toString();
39
+ const isOwned =
40
+ typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
41
+ ? userIdOrUser.ownsUserId(entity.userId)
42
+ : entity.userId?.toString() === userId?.toString();
41
43
 
42
44
  if (!isOwned) {
43
45
  throw new Error(
@@ -0,0 +1,86 @@
1
+ const crypto = require('crypto');
2
+ const {
3
+ AuthorizationSession,
4
+ } = require('../domain/entities/AuthorizationSession');
5
+
6
+ /**
7
+ * Start Authorization Session Use Case
8
+ * Business logic for initiating multi-step authorization workflows
9
+ *
10
+ * Responsibilities:
11
+ * - Generate unique session identifiers
12
+ * - Set appropriate session expiration (15 minutes)
13
+ * - Create and persist new authorization session
14
+ * - Validate input parameters
15
+ *
16
+ * @example
17
+ * ```javascript
18
+ * const useCase = new StartAuthorizationSessionUseCase({
19
+ * authSessionRepository: createAuthorizationSessionRepository()
20
+ * });
21
+ *
22
+ * const session = await useCase.execute('user123', 'nagaris', 2);
23
+ * // Returns new AuthorizationSession ready for step 1
24
+ * ```
25
+ */
26
+ class StartAuthorizationSessionUseCase {
27
+ /**
28
+ * @param {Object} params - Dependencies
29
+ * @param {import('../repositories/authorization-session-repository-interface').AuthorizationSessionRepositoryInterface} params.authSessionRepository - Session repository
30
+ */
31
+ constructor({ authSessionRepository }) {
32
+ if (!authSessionRepository) {
33
+ throw new Error('authSessionRepository is required');
34
+ }
35
+ this.authSessionRepository = authSessionRepository;
36
+ }
37
+
38
+ /**
39
+ * Start a new multi-step authorization session
40
+ *
41
+ * @param {string} userId - User ID initiating the auth flow
42
+ * @param {string} entityType - Type of entity being authorized (module name)
43
+ * @param {number} maxSteps - Total number of steps in the auth flow
44
+ * @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession>} Created session
45
+ * @throws {Error} If validation fails
46
+ */
47
+ async execute(userId, entityType, maxSteps) {
48
+ // Validate inputs
49
+ if (!userId) {
50
+ throw new Error('userId is required');
51
+ }
52
+ if (!entityType) {
53
+ throw new Error('entityType is required');
54
+ }
55
+ if (!maxSteps || maxSteps < 1) {
56
+ throw new Error('maxSteps must be >= 1');
57
+ }
58
+
59
+ // Generate cryptographically secure session ID
60
+ const sessionId = crypto.randomUUID();
61
+
62
+ // Set 15 minute expiration (configurable via env in future)
63
+ const expirationMinutes = parseInt(
64
+ process.env.AUTH_SESSION_EXPIRY_MINUTES || '15',
65
+ 10
66
+ );
67
+ const expiresAt = new Date(Date.now() + expirationMinutes * 60 * 1000);
68
+
69
+ // Create domain entity
70
+ const session = new AuthorizationSession({
71
+ sessionId,
72
+ userId,
73
+ entityType,
74
+ currentStep: 1,
75
+ maxSteps,
76
+ stepData: {},
77
+ expiresAt,
78
+ completed: false,
79
+ });
80
+
81
+ // Persist to database
82
+ return await this.authSessionRepository.create(session);
83
+ }
84
+ }
85
+
86
+ module.exports = { StartAuthorizationSessionUseCase };
@@ -20,9 +20,10 @@ class TestModuleAuth {
20
20
  */
21
21
  async execute(entityId, userIdOrUser) {
22
22
  // Support both userId (backward compatible) and User object (new pattern)
23
- const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
24
- ? userIdOrUser.getId()
25
- : userIdOrUser;
23
+ const userId =
24
+ typeof userIdOrUser === 'object' && userIdOrUser?.getId
25
+ ? userIdOrUser.getId()
26
+ : userIdOrUser;
26
27
 
27
28
  const entity = await this.moduleRepository.findEntityById(
28
29
  entityId,
@@ -34,9 +35,10 @@ class TestModuleAuth {
34
35
  }
35
36
 
36
37
  // Validate entity ownership
37
- const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
38
- ? userIdOrUser.ownsUserId(entity.userId)
39
- : entity.userId?.toString() === userId?.toString();
38
+ const isOwned =
39
+ typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
40
+ ? userIdOrUser.ownsUserId(entity.userId)
41
+ : entity.userId?.toString() === userId?.toString();
40
42
 
41
43
  if (!isOwned) {
42
44
  throw new Error(
@@ -0,0 +1,27 @@
1
+ /**
2
+ * UpdateModuleEntity Use Case
3
+ * Updates a module entity with new data
4
+ */
5
+ class UpdateModuleEntity {
6
+ constructor({ moduleRepository }) {
7
+ this.moduleRepository = moduleRepository;
8
+ }
9
+
10
+ async execute(entityId, updates) {
11
+ const entity = await this.moduleRepository.findEntityById(entityId);
12
+
13
+ if (!entity) {
14
+ throw new Error(`Entity not found: ${entityId}`);
15
+ }
16
+
17
+ // Update the entity using repository method
18
+ const updatedEntity = await this.moduleRepository.updateEntity(
19
+ entityId,
20
+ updates
21
+ );
22
+
23
+ return updatedEntity;
24
+ }
25
+ }
26
+
27
+ module.exports = { UpdateModuleEntity };
@@ -11,8 +11,8 @@ function mapModuleClassToModuleDTO(moduleInstance) {
11
11
  userId: moduleInstance.userId,
12
12
  entity: moduleInstance.entity,
13
13
  credentialId: moduleInstance.credential?._id?.toString(),
14
- type: moduleInstance.getName()
14
+ type: moduleInstance.getName(),
15
15
  };
16
16
  }
17
17
 
18
- module.exports = { mapModuleClassToModuleDTO };
18
+ module.exports = { mapModuleClassToModuleDTO };