@friggframework/core 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.35ee143.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.
- package/CHANGELOG.md +67 -66
- package/CLAUDE.md +124 -148
- package/README.md +130 -165
- package/admin-scripts/index.js +16 -30
- package/admin-scripts/repositories/{admin-api-key-repository-documentdb.js → admin-process-repository-documentdb.js} +5 -5
- package/admin-scripts/repositories/{admin-api-key-repository-factory.js → admin-process-repository-factory.js} +15 -19
- package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
- package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
- package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +2 -6
- package/admin-scripts/repositories/script-schedule-repository-interface.js +14 -36
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +13 -25
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +13 -25
- package/application/commands/admin-script-commands.js +102 -266
- package/application/commands/integration-commands.js +6 -12
- package/application/index.js +3 -1
- package/assertions/get.js +4 -1
- package/associations/association.js +57 -59
- package/associations/model.js +41 -41
- package/core/CLAUDE.md +156 -222
- package/core/Worker.js +1 -5
- package/credential/repositories/credential-repository-documentdb.js +1 -71
- package/credential/repositories/credential-repository-interface.js +2 -8
- package/credential/repositories/credential-repository-mongo.js +4 -45
- package/credential/repositories/credential-repository-postgres.js +3 -44
- package/credential/repositories/credential-repository.js +4 -6
- package/credential/use-cases/get-credential-for-user.js +3 -21
- package/credential/use-cases/update-authentication-status.js +2 -5
- package/database/MONGODB_TRANSACTION_FIX.md +52 -72
- package/database/adapters/lambda-invoker.js +7 -8
- package/database/config.js +13 -15
- package/database/documentdb-encryption-service.js +13 -29
- package/database/documentdb-utils.js +12 -21
- package/database/encryption/README.md +36 -53
- package/database/encryption/encryption-schema-registry.js +9 -21
- package/database/encryption/field-encryption-service.js +4 -15
- package/database/encryption/logger.js +2 -3
- package/database/encryption/prisma-encryption-extension.js +8 -5
- package/database/index.js +2 -4
- package/database/models/IndividualUser.js +15 -15
- package/database/models/OrganizationUser.js +4 -6
- package/database/models/UserModel.js +2 -2
- package/database/models/WebsocketConnection.js +1 -4
- package/database/mongoose.js +2 -2
- package/database/prisma.js +14 -22
- package/database/repositories/health-check-repository-documentdb.js +7 -22
- package/database/repositories/health-check-repository-factory.js +3 -9
- package/database/repositories/health-check-repository-interface.js +5 -15
- package/database/repositories/health-check-repository-mongodb.js +6 -9
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/repositories/migration-status-repository-s3.js +5 -10
- package/database/use-cases/check-database-health-use-case.js +2 -3
- package/database/use-cases/check-database-state-use-case.js +7 -9
- package/database/use-cases/get-database-state-via-worker-use-case.js +12 -15
- package/database/use-cases/get-migration-status-use-case.js +2 -4
- package/database/use-cases/run-database-migration-use-case.js +9 -41
- package/database/use-cases/test-encryption-use-case.js +5 -4
- package/database/use-cases/trigger-database-migration-use-case.js +8 -9
- package/database/utils/mongodb-collection-utils.js +3 -11
- package/database/utils/mongodb-schema-init.js +4 -10
- package/database/utils/prisma-runner.js +74 -144
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +80 -135
- package/encrypt/Cryptor.js +2 -8
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +16 -59
- package/generated/prisma-mongodb/index-browser.js +12 -62
- package/generated/prisma-mongodb/index.d.ts +4850 -8136
- package/generated/prisma-mongodb/index.js +16 -59
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +33 -75
- package/generated/prisma-mongodb/wasm.js +16 -59
- package/generated/prisma-postgresql/edge.js +15 -59
- package/generated/prisma-postgresql/index-browser.js +11 -62
- package/generated/prisma-postgresql/index.d.ts +5963 -8930
- package/generated/prisma-postgresql/index.js +15 -59
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +33 -73
- package/generated/prisma-postgresql/wasm.js +15 -59
- package/handlers/WEBHOOKS.md +81 -104
- package/handlers/app-definition-loader.js +1 -1
- package/handlers/app-handler-helpers.js +2 -14
- package/handlers/backend-utils.js +1 -0
- package/handlers/database-migration-handler.js +45 -64
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +135 -170
- package/handlers/routers/auth.js +2 -23
- package/handlers/routers/db-migration.handler.js +3 -2
- package/handlers/routers/db-migration.js +33 -69
- package/handlers/routers/health.js +8 -23
- package/handlers/routers/integration-defined-routers.js +4 -4
- package/handlers/routers/integration-webhook-routers.js +7 -17
- package/handlers/routers/websocket.js +3 -9
- package/handlers/use-cases/check-integrations-health-use-case.js +6 -13
- package/handlers/workers/db-migration.js +26 -54
- package/index.js +9 -5
- package/integrations/WEBHOOK-QUICKSTART.md +14 -21
- package/integrations/index.js +0 -4
- package/integrations/integration-base.js +6 -9
- package/integrations/integration-router.js +146 -1817
- package/integrations/options.js +20 -47
- package/integrations/repositories/integration-repository-documentdb.js +24 -53
- package/integrations/repositories/integration-repository-factory.js +2 -6
- package/integrations/repositories/integration-repository-interface.js +9 -27
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +1 -0
- package/integrations/repositories/process-repository-interface.js +5 -9
- package/integrations/repositories/process-repository-mongo.js +3 -7
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +8 -9
- package/integrations/tests/doubles/dummy-integration-class.js +8 -81
- package/integrations/tests/doubles/test-integration-repository.js +11 -51
- package/integrations/use-cases/create-integration.js +32 -36
- package/integrations/use-cases/create-process.js +9 -20
- package/integrations/use-cases/delete-integration-for-user.js +4 -5
- package/integrations/use-cases/get-integration-for-user.js +10 -25
- package/integrations/use-cases/get-integration-instance-by-definition.js +7 -13
- package/integrations/use-cases/get-integrations-for-user.js +1 -4
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +6 -5
- package/integrations/use-cases/index.js +1 -3
- package/integrations/use-cases/load-integration-context.js +5 -1
- package/integrations/use-cases/update-process-state.js +8 -10
- package/integrations/utils/map-integration-dto.js +3 -9
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +30 -32
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/module.js +1 -3
- package/modules/repositories/module-repository-documentdb.js +48 -114
- package/modules/repositories/module-repository-interface.js +0 -13
- package/modules/repositories/module-repository-mongo.js +6 -46
- package/modules/repositories/module-repository-postgres.js +6 -48
- package/modules/repositories/module-repository.js +1 -34
- package/modules/requester/api-key.js +2 -0
- package/modules/requester/basic.js +1 -0
- package/modules/requester/oauth-2.js +2 -3
- package/modules/test/mock-api/definition.js +10 -15
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +4 -10
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +6 -8
- package/modules/use-cases/get-module.js +9 -12
- package/modules/use-cases/process-authorization-callback.js +6 -12
- package/modules/use-cases/refresh-entity-options.js +6 -8
- package/modules/use-cases/test-module-auth.js +6 -8
- package/modules/utils/map-module-dto.js +2 -2
- package/package.json +5 -7
- package/prisma-mongodb/schema.prisma +33 -75
- package/prisma-postgresql/schema.prisma +33 -73
- package/queues/queuer-util.js +1 -5
- package/syncs/model.js +51 -51
- package/syncs/repositories/sync-repository-documentdb.js +19 -39
- package/syncs/repositories/sync-repository-interface.js +2 -6
- package/syncs/repositories/sync-repository-postgres.js +13 -19
- package/syncs/sync.js +100 -104
- package/token/repositories/token-repository-factory.js +3 -1
- package/types/assertions/index.d.ts +72 -72
- package/types/associations/index.d.ts +57 -57
- package/types/core/index.d.ts +53 -59
- package/types/database/index.d.ts +2 -2
- package/types/encrypt/index.d.ts +3 -3
- package/types/errors/index.d.ts +54 -61
- package/types/eslint-config/index.d.ts +40 -40
- package/types/integrations/index.d.ts +183 -185
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/module-plugin/index.d.ts +237 -239
- package/types/prettier-config/index.d.ts +5 -5
- package/types/syncs/index.d.ts +108 -102
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +93 -95
- package/user/tests/doubles/test-user-repository.js +2 -5
- package/user/use-cases/authenticate-user.js +4 -2
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +7 -8
- package/user/use-cases/create-organization-user.js +1 -1
- package/user/use-cases/create-token-for-user-id.js +1 -1
- package/user/use-cases/get-user-from-adopter-jwt.js +2 -0
- package/user/use-cases/get-user-from-bearer-token.js +3 -7
- package/user/use-cases/get-user-from-x-frigg-headers.js +11 -14
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +4 -18
- package/utils/backend-path.js +2 -2
- package/utils/index.js +1 -4
- package/websocket/repositories/websocket-connection-repository-documentdb.js +8 -21
- package/websocket/repositories/websocket-connection-repository-mongo.js +4 -8
- package/websocket/repositories/websocket-connection-repository-postgres.js +4 -8
- package/websocket/repositories/websocket-connection-repository.js +4 -8
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +0 -121
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +0 -155
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +0 -189
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +0 -21
- package/admin-scripts/repositories/script-execution-repository-factory.js +0 -55
- package/admin-scripts/repositories/script-execution-repository-interface.js +0 -193
- package/admin-scripts/repositories/script-execution-repository-mongo.js +0 -278
- package/admin-scripts/repositories/script-execution-repository-postgres.js +0 -320
- package/credential/credential-router.js +0 -262
- package/credential/use-cases/delete-credential-for-user.js +0 -50
- package/credential/use-cases/list-credentials-for-user.js +0 -31
- package/credential/use-cases/reauthorize-credential.js +0 -103
- package/handlers/routers/admin.js +0 -450
- package/handlers/routers/docs.js +0 -180
- package/handlers/routers/middleware/loadUser.js +0 -39
- package/handlers/routers/middleware/requireAdmin.js +0 -41
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -19
- package/integrations/use-cases/execute-proxy-request.js +0 -564
- package/modules/domain/entities/AuthorizationSession.js +0 -131
- package/modules/domain/entities/index.js +0 -10
- package/modules/repositories/authorization-session-repository-factory.js +0 -55
- package/modules/repositories/authorization-session-repository-interface.js +0 -75
- package/modules/repositories/authorization-session-repository-mongo.js +0 -169
- package/modules/repositories/authorization-session-repository-postgres.js +0 -170
- package/modules/use-cases/delete-module-entity.js +0 -23
- package/modules/use-cases/get-authorization-requirements.js +0 -110
- package/modules/use-cases/get-module-entity-by-id.js +0 -16
- package/modules/use-cases/process-authorization-step.js +0 -160
- package/modules/use-cases/start-authorization-session.js +0 -86
- package/modules/use-cases/update-module-entity.js +0 -27
- package/openapi/openapi-spec-generator.js +0 -370
- package/openapi/openapi-v1.yaml +0 -1360
- package/openapi/openapi-v2.yaml +0 -1535
- package/user/use-cases/delete-user.js +0 -47
|
@@ -17,15 +17,6 @@ const {
|
|
|
17
17
|
const {
|
|
18
18
|
GetCredentialForUser,
|
|
19
19
|
} = require('../credential/use-cases/get-credential-for-user');
|
|
20
|
-
const {
|
|
21
|
-
ListCredentialsForUser,
|
|
22
|
-
} = require('../credential/use-cases/list-credentials-for-user');
|
|
23
|
-
const {
|
|
24
|
-
DeleteCredentialForUser,
|
|
25
|
-
} = require('../credential/use-cases/delete-credential-for-user');
|
|
26
|
-
const {
|
|
27
|
-
ReauthorizeCredential,
|
|
28
|
-
} = require('../credential/use-cases/reauthorize-credential');
|
|
29
20
|
const { CreateIntegration } = require('./use-cases/create-integration');
|
|
30
21
|
const { ModuleFactory } = require('../modules/module-factory');
|
|
31
22
|
const {
|
|
@@ -78,19 +69,6 @@ const { AuthenticateUser } = require('../user/use-cases/authenticate-user');
|
|
|
78
69
|
const {
|
|
79
70
|
ProcessAuthorizationCallback,
|
|
80
71
|
} = require('../modules/use-cases/process-authorization-callback');
|
|
81
|
-
const {
|
|
82
|
-
createAuthorizationSessionRepository,
|
|
83
|
-
} = require('../modules/repositories/authorization-session-repository-factory');
|
|
84
|
-
const {
|
|
85
|
-
StartAuthorizationSessionUseCase,
|
|
86
|
-
} = require('../modules/use-cases/start-authorization-session');
|
|
87
|
-
const {
|
|
88
|
-
ProcessAuthorizationStepUseCase,
|
|
89
|
-
} = require('../modules/use-cases/process-authorization-step');
|
|
90
|
-
const {
|
|
91
|
-
GetAuthorizationRequirementsUseCase,
|
|
92
|
-
} = require('../modules/use-cases/get-authorization-requirements');
|
|
93
|
-
const { ExecuteProxyRequest } = require('./use-cases/execute-proxy-request');
|
|
94
72
|
|
|
95
73
|
function createIntegrationRouter() {
|
|
96
74
|
const { integrations: integrationClasses, userConfig } =
|
|
@@ -99,7 +77,6 @@ function createIntegrationRouter() {
|
|
|
99
77
|
const integrationRepository = createIntegrationRepository();
|
|
100
78
|
const credentialRepository = createCredentialRepository();
|
|
101
79
|
const userRepository = createUserRepository();
|
|
102
|
-
const authSessionRepository = createAuthorizationSessionRepository();
|
|
103
80
|
|
|
104
81
|
const getUserFromBearerToken = new GetUserFromBearerToken({
|
|
105
82
|
userRepository,
|
|
@@ -126,18 +103,10 @@ function createIntegrationRouter() {
|
|
|
126
103
|
userConfig,
|
|
127
104
|
});
|
|
128
105
|
|
|
129
|
-
// Support both integration classes and direct module definitions (for testing)
|
|
130
|
-
const isModuleDefinitionFormat =
|
|
131
|
-
integrationClasses &&
|
|
132
|
-
integrationClasses[0] &&
|
|
133
|
-
integrationClasses[0].moduleName &&
|
|
134
|
-
integrationClasses[0].definition;
|
|
135
|
-
|
|
136
106
|
const moduleFactory = new ModuleFactory({
|
|
137
107
|
moduleRepository,
|
|
138
|
-
moduleDefinitions:
|
|
139
|
-
|
|
140
|
-
: getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
108
|
+
moduleDefinitions:
|
|
109
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
141
110
|
});
|
|
142
111
|
const deleteIntegrationForUser = new DeleteIntegrationForUser({
|
|
143
112
|
integrationRepository,
|
|
@@ -156,32 +125,16 @@ function createIntegrationRouter() {
|
|
|
156
125
|
credentialRepository,
|
|
157
126
|
});
|
|
158
127
|
|
|
159
|
-
const listCredentialsForUser = new ListCredentialsForUser({
|
|
160
|
-
credentialRepository,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const deleteCredentialForUser = new DeleteCredentialForUser({
|
|
164
|
-
credentialRepository,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const reauthorizeCredential = new ReauthorizeCredential({
|
|
168
|
-
credentialRepository,
|
|
169
|
-
moduleRepository,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
128
|
const createIntegration = new CreateIntegration({
|
|
173
129
|
integrationRepository,
|
|
174
130
|
integrationClasses,
|
|
175
131
|
moduleFactory,
|
|
176
132
|
});
|
|
177
133
|
|
|
178
|
-
const moduleDefinitions = isModuleDefinitionFormat
|
|
179
|
-
? integrationClasses
|
|
180
|
-
: getModulesDefinitionFromIntegrationClasses(integrationClasses);
|
|
181
|
-
|
|
182
134
|
const getEntitiesForUser = new GetEntitiesForUser({
|
|
183
135
|
moduleRepository,
|
|
184
|
-
moduleDefinitions
|
|
136
|
+
moduleDefinitions:
|
|
137
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
185
138
|
});
|
|
186
139
|
|
|
187
140
|
const getIntegrationInstance = new GetIntegrationInstance({
|
|
@@ -197,31 +150,37 @@ function createIntegrationRouter() {
|
|
|
197
150
|
});
|
|
198
151
|
|
|
199
152
|
const getModuleInstanceFromType = new GetModuleInstanceFromType({
|
|
200
|
-
moduleDefinitions
|
|
153
|
+
moduleDefinitions:
|
|
154
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
201
155
|
});
|
|
202
156
|
|
|
203
157
|
const getEntityOptionsByType = new GetEntityOptionsByType({
|
|
204
|
-
moduleDefinitions
|
|
158
|
+
moduleDefinitions:
|
|
159
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
205
160
|
});
|
|
206
161
|
|
|
207
162
|
const testModuleAuth = new TestModuleAuth({
|
|
208
163
|
moduleRepository,
|
|
209
|
-
moduleDefinitions
|
|
164
|
+
moduleDefinitions:
|
|
165
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
210
166
|
});
|
|
211
167
|
|
|
212
168
|
const getModule = new GetModule({
|
|
213
169
|
moduleRepository,
|
|
214
|
-
moduleDefinitions
|
|
170
|
+
moduleDefinitions:
|
|
171
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
215
172
|
});
|
|
216
173
|
|
|
217
174
|
const getEntityOptionsById = new GetEntityOptionsById({
|
|
218
175
|
moduleRepository,
|
|
219
|
-
moduleDefinitions
|
|
176
|
+
moduleDefinitions:
|
|
177
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
220
178
|
});
|
|
221
179
|
|
|
222
180
|
const refreshEntityOptions = new RefreshEntityOptions({
|
|
223
181
|
moduleRepository,
|
|
224
|
-
moduleDefinitions
|
|
182
|
+
moduleDefinitions:
|
|
183
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
225
184
|
});
|
|
226
185
|
|
|
227
186
|
const getPossibleIntegrations = new GetPossibleIntegrations({
|
|
@@ -231,28 +190,8 @@ function createIntegrationRouter() {
|
|
|
231
190
|
const processAuthorizationCallback = new ProcessAuthorizationCallback({
|
|
232
191
|
moduleRepository,
|
|
233
192
|
credentialRepository,
|
|
234
|
-
moduleDefinitions
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const startAuthorizationSession = new StartAuthorizationSessionUseCase({
|
|
238
|
-
authSessionRepository,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const processAuthorizationStep = new ProcessAuthorizationStepUseCase({
|
|
242
|
-
authSessionRepository,
|
|
243
|
-
moduleDefinitions,
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const getAuthorizationRequirements =
|
|
247
|
-
new GetAuthorizationRequirementsUseCase({
|
|
248
|
-
moduleDefinitions,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const executeProxyRequest = new ExecuteProxyRequest({
|
|
252
|
-
moduleRepository,
|
|
253
|
-
credentialRepository,
|
|
254
|
-
moduleFactory,
|
|
255
|
-
moduleDefinitions,
|
|
193
|
+
moduleDefinitions:
|
|
194
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
256
195
|
});
|
|
257
196
|
|
|
258
197
|
const router = express();
|
|
@@ -275,22 +214,6 @@ function createIntegrationRouter() {
|
|
|
275
214
|
getEntityOptionsById,
|
|
276
215
|
refreshEntityOptions,
|
|
277
216
|
processAuthorizationCallback,
|
|
278
|
-
moduleDefinitions,
|
|
279
|
-
startAuthorizationSession,
|
|
280
|
-
processAuthorizationStep,
|
|
281
|
-
getAuthorizationRequirements,
|
|
282
|
-
moduleRepository,
|
|
283
|
-
credentialRepository,
|
|
284
|
-
authSessionRepository,
|
|
285
|
-
executeProxyRequest,
|
|
286
|
-
});
|
|
287
|
-
setCredentialRoutes(router, authenticateUser, {
|
|
288
|
-
listCredentialsForUser,
|
|
289
|
-
getCredentialForUser,
|
|
290
|
-
deleteCredentialForUser,
|
|
291
|
-
reauthorizeCredential,
|
|
292
|
-
getAuthorizationRequirements,
|
|
293
|
-
moduleDefinitions,
|
|
294
217
|
});
|
|
295
218
|
return router;
|
|
296
219
|
}
|
|
@@ -335,84 +258,20 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
335
258
|
updateIntegration,
|
|
336
259
|
getPossibleIntegrations,
|
|
337
260
|
} = useCases;
|
|
338
|
-
|
|
339
|
-
// =========================================================================
|
|
340
|
-
// v1 API Routes (backwards compatible - legacy format)
|
|
341
|
-
// =========================================================================
|
|
342
|
-
|
|
343
|
-
// GET /api/integrations - v1 format: combined response with entities and integrations
|
|
344
261
|
router.route('/api/integrations').get(
|
|
345
262
|
catchAsyncError(async (req, res) => {
|
|
346
263
|
const user = await authenticateUser.execute(req);
|
|
347
264
|
const userId = user.getId();
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const [integrations, options, authorized] = await Promise.all([
|
|
351
|
-
getIntegrationsForUser.execute(userId),
|
|
352
|
-
getPossibleIntegrations.execute(),
|
|
353
|
-
getEntitiesForUser.execute(userId),
|
|
354
|
-
]);
|
|
355
|
-
|
|
356
|
-
res.json({
|
|
265
|
+
const integrations = await getIntegrationsForUser.execute(userId);
|
|
266
|
+
const results = {
|
|
357
267
|
entities: {
|
|
358
|
-
options,
|
|
359
|
-
authorized,
|
|
268
|
+
options: await getPossibleIntegrations.execute(),
|
|
269
|
+
authorized: await getEntitiesForUser.execute(userId),
|
|
360
270
|
},
|
|
361
|
-
integrations,
|
|
362
|
-
}
|
|
363
|
-
})
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
// GET /api/integrations/options - Get available integration options (v1 compatible)
|
|
367
|
-
router.route('/api/integrations/options').get(
|
|
368
|
-
catchAsyncError(async (req, res) => {
|
|
369
|
-
const options = await getPossibleIntegrations.execute();
|
|
370
|
-
res.json({ integrations: options });
|
|
371
|
-
})
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
// GET /api/entities - Get user's connected entities/accounts (v1 compatible)
|
|
375
|
-
router.route('/api/entities').get(
|
|
376
|
-
catchAsyncError(async (req, res) => {
|
|
377
|
-
const user = await authenticateUser.execute(req);
|
|
378
|
-
const userId = user.getId();
|
|
379
|
-
const entities = await getEntitiesForUser.execute(userId);
|
|
380
|
-
|
|
381
|
-
res.json({ entities });
|
|
382
|
-
})
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
// =========================================================================
|
|
386
|
-
// v2 API Routes (new clean format)
|
|
387
|
-
// =========================================================================
|
|
388
|
-
|
|
389
|
-
// GET /api/v2/integrations - v2 format: only integrations
|
|
390
|
-
router.route('/api/v2/integrations').get(
|
|
391
|
-
catchAsyncError(async (req, res) => {
|
|
392
|
-
const user = await authenticateUser.execute(req);
|
|
393
|
-
const userId = user.getId();
|
|
394
|
-
const integrations = await getIntegrationsForUser.execute(userId);
|
|
395
|
-
|
|
396
|
-
res.json({ integrations });
|
|
397
|
-
})
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
// GET /api/v2/integrations/options - Get available integration options
|
|
401
|
-
router.route('/api/v2/integrations/options').get(
|
|
402
|
-
catchAsyncError(async (req, res) => {
|
|
403
|
-
const options = await getPossibleIntegrations.execute();
|
|
404
|
-
res.json({ integrations: options });
|
|
405
|
-
})
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
// GET /api/v2/entities - Get user's connected entities/accounts
|
|
409
|
-
router.route('/api/v2/entities').get(
|
|
410
|
-
catchAsyncError(async (req, res) => {
|
|
411
|
-
const user = await authenticateUser.execute(req);
|
|
412
|
-
const userId = user.getId();
|
|
413
|
-
const entities = await getEntitiesForUser.execute(userId);
|
|
271
|
+
integrations: integrations,
|
|
272
|
+
};
|
|
414
273
|
|
|
415
|
-
res.json(
|
|
274
|
+
res.json(results);
|
|
416
275
|
})
|
|
417
276
|
);
|
|
418
277
|
|
|
@@ -621,1717 +480,187 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
|
|
|
621
480
|
}
|
|
622
481
|
})
|
|
623
482
|
);
|
|
483
|
+
}
|
|
624
484
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
485
|
+
/**
|
|
486
|
+
* Sets up entity-related routes for the integration router
|
|
487
|
+
* @param {Object} router - Express router instance
|
|
488
|
+
* @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
|
|
489
|
+
*/
|
|
490
|
+
function setEntityRoutes(router, authenticateUser, useCases) {
|
|
491
|
+
const {
|
|
492
|
+
getCredentialForUser,
|
|
493
|
+
getModuleInstanceFromType,
|
|
494
|
+
getEntityOptionsByType,
|
|
495
|
+
testModuleAuth,
|
|
496
|
+
getModule,
|
|
497
|
+
getEntityOptionsById,
|
|
498
|
+
refreshEntityOptions,
|
|
499
|
+
processAuthorizationCallback,
|
|
500
|
+
} = useCases;
|
|
628
501
|
|
|
629
|
-
|
|
630
|
-
router.route('/api/v2/integrations').post(
|
|
502
|
+
router.route('/api/authorize').get(
|
|
631
503
|
catchAsyncError(async (req, res) => {
|
|
632
504
|
const user = await authenticateUser.execute(req);
|
|
633
505
|
const userId = user.getId();
|
|
634
|
-
const params = checkRequiredParams(req.
|
|
635
|
-
|
|
636
|
-
'config',
|
|
637
|
-
]);
|
|
638
|
-
|
|
639
|
-
get(params.config, 'type');
|
|
640
|
-
|
|
641
|
-
const integration = await createIntegration.execute(
|
|
642
|
-
params.entities,
|
|
506
|
+
const params = checkRequiredParams(req.query, ['entityType']);
|
|
507
|
+
const module = await getModuleInstanceFromType.execute(
|
|
643
508
|
userId,
|
|
644
|
-
params.
|
|
509
|
+
params.entityType
|
|
645
510
|
);
|
|
511
|
+
const areRequirementsValid =
|
|
512
|
+
module.validateAuthorizationRequirements();
|
|
513
|
+
if (!areRequirementsValid) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Error: Entity of type ${params.entityType} requires a valid url`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
646
518
|
|
|
647
|
-
res.
|
|
519
|
+
res.json(module.getAuthorizationRequirements());
|
|
648
520
|
})
|
|
649
521
|
);
|
|
650
522
|
|
|
651
|
-
|
|
652
|
-
router.route('/api/v2/integrations/:integrationId').patch(
|
|
523
|
+
router.route('/api/authorize').post(
|
|
653
524
|
catchAsyncError(async (req, res) => {
|
|
654
525
|
const user = await authenticateUser.execute(req);
|
|
655
526
|
const userId = user.getId();
|
|
656
|
-
const params = checkRequiredParams(req.body, [
|
|
527
|
+
const params = checkRequiredParams(req.body, [
|
|
528
|
+
'entityType',
|
|
529
|
+
'data',
|
|
530
|
+
]);
|
|
657
531
|
|
|
658
|
-
const
|
|
659
|
-
req.params.integrationId,
|
|
532
|
+
const entityDetails = await processAuthorizationCallback.execute(
|
|
660
533
|
userId,
|
|
661
|
-
params.
|
|
534
|
+
params.entityType,
|
|
535
|
+
params.data
|
|
662
536
|
);
|
|
663
|
-
|
|
537
|
+
|
|
538
|
+
res.json(entityDetails);
|
|
664
539
|
})
|
|
665
540
|
);
|
|
666
541
|
|
|
667
|
-
|
|
668
|
-
router.route('/api/v2/integrations/:integrationId').delete(
|
|
542
|
+
router.route('/api/entity').post(
|
|
669
543
|
catchAsyncError(async (req, res) => {
|
|
670
544
|
const user = await authenticateUser.execute(req);
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
545
|
+
const userId = user.getId();
|
|
546
|
+
const params = checkRequiredParams(req.body, [
|
|
547
|
+
'entityType',
|
|
548
|
+
'data',
|
|
549
|
+
]);
|
|
550
|
+
checkRequiredParams(req.body.data, ['credential_id']);
|
|
551
|
+
|
|
552
|
+
// May want to pass along the user ID as well so credential ID's can't be fished???
|
|
553
|
+
const credential = await getCredentialForUser.execute(
|
|
554
|
+
params.data.credential_id,
|
|
555
|
+
userId
|
|
675
556
|
);
|
|
676
|
-
|
|
557
|
+
|
|
558
|
+
if (!credential) {
|
|
559
|
+
throw Boom.badRequest('Invalid credential ID');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const module = await getModuleInstanceFromType.execute(
|
|
563
|
+
userId,
|
|
564
|
+
params.entityType
|
|
565
|
+
);
|
|
566
|
+
const entityDetails = await module.getEntityDetails(
|
|
567
|
+
module.api,
|
|
568
|
+
null,
|
|
569
|
+
null,
|
|
570
|
+
userId
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
res.json(await module.findOrCreateEntity(entityDetails));
|
|
677
574
|
})
|
|
678
575
|
);
|
|
679
576
|
|
|
680
|
-
|
|
681
|
-
router.route('/api/v2/integrations/:integrationId').get(
|
|
577
|
+
router.route('/api/entity/options/:credentialId').get(
|
|
682
578
|
catchAsyncError(async (req, res) => {
|
|
683
579
|
const user = await authenticateUser.execute(req);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
580
|
+
const userId = user.getId();
|
|
581
|
+
// TODO May want to pass along the user ID as well so credential ID's can't be fished???
|
|
582
|
+
// TODO **flagging this for review** -MW
|
|
583
|
+
const credential = await getCredentialForUser.execute(
|
|
584
|
+
req.params.credentialId,
|
|
585
|
+
userId
|
|
586
|
+
);
|
|
587
|
+
if (credential.userId.toString() !== userId) {
|
|
588
|
+
throw Boom.forbidden('Credential does not belong to user');
|
|
687
589
|
}
|
|
688
590
|
|
|
689
|
-
const params = checkRequiredParams(req.
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
591
|
+
const params = checkRequiredParams(req.query, ['entityType']);
|
|
592
|
+
const entityOptions = await getEntityOptionsByType.execute(
|
|
593
|
+
userId,
|
|
594
|
+
params.entityType
|
|
693
595
|
);
|
|
694
596
|
|
|
695
|
-
res.json(
|
|
696
|
-
id: integration.id,
|
|
697
|
-
entities: integration.entities,
|
|
698
|
-
status: integration.status,
|
|
699
|
-
config: integration.config,
|
|
700
|
-
});
|
|
597
|
+
res.json(entityOptions);
|
|
701
598
|
})
|
|
702
599
|
);
|
|
703
600
|
|
|
704
|
-
|
|
705
|
-
router.route('/api/v2/integrations/:integrationId/config/options').get(
|
|
601
|
+
router.route('/api/entities/:entityId/test-auth').get(
|
|
706
602
|
catchAsyncError(async (req, res) => {
|
|
707
603
|
const user = await authenticateUser.execute(req);
|
|
708
|
-
const params = checkRequiredParams(req.params, ['
|
|
709
|
-
const
|
|
710
|
-
params.
|
|
711
|
-
user
|
|
604
|
+
const params = checkRequiredParams(req.params, ['entityId']);
|
|
605
|
+
const testAuthResponse = await testModuleAuth.execute(
|
|
606
|
+
params.entityId,
|
|
607
|
+
user // Pass User object for proper validation
|
|
712
608
|
);
|
|
713
|
-
|
|
609
|
+
|
|
610
|
+
if (!testAuthResponse) {
|
|
611
|
+
res.status(400);
|
|
612
|
+
res.json({
|
|
613
|
+
errors: [
|
|
614
|
+
{
|
|
615
|
+
title: 'Authentication Error',
|
|
616
|
+
message: `There was an error with your Entity. Please reconnect/re-authenticate, or reach out to Support for assistance.`,
|
|
617
|
+
timestamp: Date.now(),
|
|
618
|
+
},
|
|
619
|
+
],
|
|
620
|
+
});
|
|
621
|
+
} else {
|
|
622
|
+
res.json({ status: 'ok' });
|
|
623
|
+
}
|
|
714
624
|
})
|
|
715
625
|
);
|
|
716
626
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const user = await authenticateUser.execute(req);
|
|
723
|
-
const params = checkRequiredParams(req.params, [
|
|
724
|
-
'integrationId',
|
|
725
|
-
]);
|
|
726
|
-
const integration = await getIntegrationInstance.execute(
|
|
727
|
-
params.integrationId,
|
|
728
|
-
user.getId()
|
|
729
|
-
);
|
|
627
|
+
router.route('/api/entities/:entityId').get(
|
|
628
|
+
catchAsyncError(async (req, res) => {
|
|
629
|
+
const user = await authenticateUser.execute(req);
|
|
630
|
+
const params = checkRequiredParams(req.params, ['entityId']);
|
|
631
|
+
const module = await getModule.execute(params.entityId, user); // Pass User object
|
|
730
632
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
})
|
|
735
|
-
);
|
|
633
|
+
res.json(module);
|
|
634
|
+
})
|
|
635
|
+
);
|
|
736
636
|
|
|
737
|
-
|
|
738
|
-
router.route('/api/v2/integrations/:integrationId/actions').all(
|
|
637
|
+
router.route('/api/entities/:entityId/options').post(
|
|
739
638
|
catchAsyncError(async (req, res) => {
|
|
740
639
|
const user = await authenticateUser.execute(req);
|
|
741
|
-
const params = checkRequiredParams(req.params, ['
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
640
|
+
const params = checkRequiredParams(req.params, ['entityId']);
|
|
641
|
+
|
|
642
|
+
const entityOptions = await getEntityOptionsById.execute(
|
|
643
|
+
params.entityId,
|
|
644
|
+
user // Pass User object
|
|
745
645
|
);
|
|
746
|
-
|
|
646
|
+
|
|
647
|
+
res.json(entityOptions);
|
|
747
648
|
})
|
|
748
649
|
);
|
|
749
650
|
|
|
750
|
-
|
|
751
|
-
router
|
|
752
|
-
.route('/api/v2/integrations/:integrationId/actions/:actionId/options')
|
|
753
|
-
.all(
|
|
754
|
-
catchAsyncError(async (req, res) => {
|
|
755
|
-
const user = await authenticateUser.execute(req);
|
|
756
|
-
const params = checkRequiredParams(req.params, [
|
|
757
|
-
'integrationId',
|
|
758
|
-
'actionId',
|
|
759
|
-
]);
|
|
760
|
-
const integration = await getIntegrationInstance.execute(
|
|
761
|
-
params.integrationId,
|
|
762
|
-
user.getId()
|
|
763
|
-
);
|
|
764
|
-
|
|
765
|
-
res.json(
|
|
766
|
-
await integration.send('GET_USER_ACTION_OPTIONS', {
|
|
767
|
-
actionId: params.actionId,
|
|
768
|
-
data: req.body,
|
|
769
|
-
})
|
|
770
|
-
);
|
|
771
|
-
})
|
|
772
|
-
);
|
|
773
|
-
|
|
774
|
-
// POST /api/v2/integrations/:integrationId/actions/:actionId/options/refresh (v2)
|
|
775
|
-
router
|
|
776
|
-
.route(
|
|
777
|
-
'/api/v2/integrations/:integrationId/actions/:actionId/options/refresh'
|
|
778
|
-
)
|
|
779
|
-
.post(
|
|
780
|
-
catchAsyncError(async (req, res) => {
|
|
781
|
-
const user = await authenticateUser.execute(req);
|
|
782
|
-
const params = checkRequiredParams(req.params, [
|
|
783
|
-
'integrationId',
|
|
784
|
-
'actionId',
|
|
785
|
-
]);
|
|
786
|
-
const integration = await getIntegrationInstance.execute(
|
|
787
|
-
params.integrationId,
|
|
788
|
-
user.getId()
|
|
789
|
-
);
|
|
790
|
-
|
|
791
|
-
res.json(
|
|
792
|
-
await integration.send('REFRESH_USER_ACTION_OPTIONS', {
|
|
793
|
-
actionId: params.actionId,
|
|
794
|
-
data: req.body,
|
|
795
|
-
})
|
|
796
|
-
);
|
|
797
|
-
})
|
|
798
|
-
);
|
|
799
|
-
|
|
800
|
-
// POST /api/v2/integrations/:integrationId/actions/:actionId - Execute action (v2)
|
|
801
|
-
router.route('/api/v2/integrations/:integrationId/actions/:actionId').post(
|
|
802
|
-
catchAsyncError(async (req, res) => {
|
|
803
|
-
const user = await authenticateUser.execute(req);
|
|
804
|
-
const params = checkRequiredParams(req.params, [
|
|
805
|
-
'integrationId',
|
|
806
|
-
'actionId',
|
|
807
|
-
]);
|
|
808
|
-
const integration = await getIntegrationInstance.execute(
|
|
809
|
-
params.integrationId,
|
|
810
|
-
user.getId()
|
|
811
|
-
);
|
|
812
|
-
res.json(await integration.send(params.actionId, req.body));
|
|
813
|
-
})
|
|
814
|
-
);
|
|
815
|
-
|
|
816
|
-
// GET /api/v2/integrations/:integrationId/test-auth - Test auth (v2)
|
|
817
|
-
router.route('/api/v2/integrations/:integrationId/test-auth').get(
|
|
818
|
-
catchAsyncError(async (req, res) => {
|
|
819
|
-
const user = await authenticateUser.execute(req);
|
|
820
|
-
const params = checkRequiredParams(req.params, ['integrationId']);
|
|
821
|
-
const instance = await getIntegrationInstance.execute(
|
|
822
|
-
params.integrationId,
|
|
823
|
-
user.getId()
|
|
824
|
-
);
|
|
825
|
-
|
|
826
|
-
if (!instance) {
|
|
827
|
-
throw Boom.notFound();
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
const start = Date.now();
|
|
831
|
-
await instance.testAuth();
|
|
832
|
-
const errors = instance.record.messages?.errors?.filter(
|
|
833
|
-
({ timestamp }) => timestamp >= start
|
|
834
|
-
);
|
|
835
|
-
|
|
836
|
-
if (errors?.length) {
|
|
837
|
-
res.status(400);
|
|
838
|
-
res.json({ errors });
|
|
839
|
-
} else {
|
|
840
|
-
res.json({ status: 'ok' });
|
|
841
|
-
}
|
|
842
|
-
})
|
|
843
|
-
);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* Sets up entity-related routes for the integration router
|
|
848
|
-
* @param {Object} router - Express router instance
|
|
849
|
-
* @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
|
|
850
|
-
*/
|
|
851
|
-
function setEntityRoutes(router, authenticateUser, useCases) {
|
|
852
|
-
const {
|
|
853
|
-
getCredentialForUser,
|
|
854
|
-
getModuleInstanceFromType,
|
|
855
|
-
getEntityOptionsByType,
|
|
856
|
-
testModuleAuth,
|
|
857
|
-
getModule,
|
|
858
|
-
getEntityOptionsById,
|
|
859
|
-
refreshEntityOptions,
|
|
860
|
-
processAuthorizationCallback,
|
|
861
|
-
moduleDefinitions,
|
|
862
|
-
startAuthorizationSession,
|
|
863
|
-
processAuthorizationStep,
|
|
864
|
-
getAuthorizationRequirements,
|
|
865
|
-
moduleRepository,
|
|
866
|
-
credentialRepository,
|
|
867
|
-
authSessionRepository,
|
|
868
|
-
executeProxyRequest,
|
|
869
|
-
} = useCases;
|
|
870
|
-
|
|
871
|
-
// GET /api/authorize - Get authorization requirements (supports multi-step)
|
|
872
|
-
router.route('/api/authorize').get(
|
|
873
|
-
catchAsyncError(async (req, res) => {
|
|
874
|
-
const user = await authenticateUser.execute(req);
|
|
875
|
-
const userId = user.getId();
|
|
876
|
-
const params = checkRequiredParams(req.query, ['entityType']);
|
|
877
|
-
const step = parseInt(req.query.step || '1', 10);
|
|
878
|
-
const sessionId = req.query.sessionId;
|
|
879
|
-
|
|
880
|
-
// Validate session if step > 1
|
|
881
|
-
if (step > 1 && !sessionId) {
|
|
882
|
-
throw Boom.badRequest('sessionId required for step > 1');
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// Check if module supports multi-step auth
|
|
886
|
-
const requirements = await getAuthorizationRequirements.execute(
|
|
887
|
-
params.entityType,
|
|
888
|
-
step
|
|
889
|
-
);
|
|
890
|
-
|
|
891
|
-
// Generate session ID for multi-step flows on step 1
|
|
892
|
-
if (requirements.isMultiStep && step === 1) {
|
|
893
|
-
const crypto = require('crypto');
|
|
894
|
-
requirements.sessionId = crypto.randomUUID();
|
|
895
|
-
} else if (sessionId) {
|
|
896
|
-
requirements.sessionId = sessionId;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// Validate requirements for backward compatibility
|
|
900
|
-
if (!requirements.isMultiStep) {
|
|
901
|
-
const module = await getModuleInstanceFromType.execute(
|
|
902
|
-
userId,
|
|
903
|
-
params.entityType
|
|
904
|
-
);
|
|
905
|
-
const areRequirementsValid =
|
|
906
|
-
module.validateAuthorizationRequirements();
|
|
907
|
-
if (!areRequirementsValid) {
|
|
908
|
-
throw new Error(
|
|
909
|
-
`Error: Entity of type ${params.entityType} requires a valid url`
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
res.json(requirements);
|
|
915
|
-
})
|
|
916
|
-
);
|
|
917
|
-
|
|
918
|
-
// POST /api/authorize - Process authorization (supports multi-step)
|
|
919
|
-
router.route('/api/authorize').post(
|
|
920
|
-
catchAsyncError(async (req, res) => {
|
|
921
|
-
const user = await authenticateUser.execute(req);
|
|
922
|
-
const userId = user.getId();
|
|
923
|
-
const params = checkRequiredParams(req.body, [
|
|
924
|
-
'entityType',
|
|
925
|
-
'data',
|
|
926
|
-
]);
|
|
927
|
-
const step = parseInt(req.body.step || '1', 10);
|
|
928
|
-
const sessionId = req.body.sessionId;
|
|
929
|
-
const isGlobal = req.body.isGlobal || false;
|
|
930
|
-
|
|
931
|
-
// Find module definition to check step count
|
|
932
|
-
const moduleDefinition = moduleDefinitions.find(
|
|
933
|
-
(def) => def.moduleName === params.entityType
|
|
934
|
-
);
|
|
935
|
-
|
|
936
|
-
if (!moduleDefinition) {
|
|
937
|
-
throw Boom.badRequest(
|
|
938
|
-
`Unknown entity type: ${params.entityType}`
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
const ModuleDefinition = moduleDefinition.definition;
|
|
943
|
-
const stepCount = ModuleDefinition.getAuthStepCount
|
|
944
|
-
? ModuleDefinition.getAuthStepCount()
|
|
945
|
-
: 1;
|
|
946
|
-
|
|
947
|
-
// Single-step flow - use existing ProcessAuthorizationCallback
|
|
948
|
-
if (stepCount === 1) {
|
|
949
|
-
const entityDetails =
|
|
950
|
-
await processAuthorizationCallback.execute(
|
|
951
|
-
userId,
|
|
952
|
-
params.entityType,
|
|
953
|
-
params.data,
|
|
954
|
-
isGlobal
|
|
955
|
-
);
|
|
956
|
-
|
|
957
|
-
return res.json(entityDetails);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// Multi-step flow
|
|
961
|
-
if (!sessionId) {
|
|
962
|
-
throw Boom.badRequest(
|
|
963
|
-
'sessionId required for multi-step authorization'
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
let session;
|
|
968
|
-
|
|
969
|
-
if (step === 1) {
|
|
970
|
-
// Create new session for step 1
|
|
971
|
-
session = await startAuthorizationSession.execute(
|
|
972
|
-
userId,
|
|
973
|
-
params.entityType,
|
|
974
|
-
stepCount
|
|
975
|
-
);
|
|
976
|
-
|
|
977
|
-
// Override with client-provided sessionId
|
|
978
|
-
session.sessionId = sessionId;
|
|
979
|
-
await useCases.authSessionRepository?.update(session);
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// Process this step
|
|
983
|
-
const result = await processAuthorizationStep.execute(
|
|
984
|
-
sessionId,
|
|
985
|
-
userId,
|
|
986
|
-
step,
|
|
987
|
-
params.data
|
|
988
|
-
);
|
|
989
|
-
|
|
990
|
-
if (result.completed) {
|
|
991
|
-
// Final step - create entity using standard flow
|
|
992
|
-
const entityDetails =
|
|
993
|
-
await processAuthorizationCallback.execute(
|
|
994
|
-
userId,
|
|
995
|
-
params.entityType,
|
|
996
|
-
result.authData,
|
|
997
|
-
isGlobal
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
return res.json(entityDetails);
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// Return next step requirements
|
|
1004
|
-
res.json({
|
|
1005
|
-
step: result.nextStep,
|
|
1006
|
-
totalSteps: result.totalSteps,
|
|
1007
|
-
sessionId: result.sessionId,
|
|
1008
|
-
requirements: result.requirements,
|
|
1009
|
-
message: result.message,
|
|
1010
|
-
});
|
|
1011
|
-
})
|
|
1012
|
-
);
|
|
1013
|
-
|
|
1014
|
-
router.route('/api/entities').post(
|
|
1015
|
-
catchAsyncError(async (req, res) => {
|
|
1016
|
-
const user = await authenticateUser.execute(req);
|
|
1017
|
-
const userId = user.getId();
|
|
1018
|
-
const params = checkRequiredParams(req.body, [
|
|
1019
|
-
'entityType',
|
|
1020
|
-
'data',
|
|
1021
|
-
]);
|
|
1022
|
-
checkRequiredParams(req.body.data, ['credential_id']);
|
|
1023
|
-
|
|
1024
|
-
// May want to pass along the user ID as well so credential ID's can't be fished???
|
|
1025
|
-
const credential = await getCredentialForUser.execute(
|
|
1026
|
-
params.data.credential_id,
|
|
1027
|
-
userId
|
|
1028
|
-
);
|
|
1029
|
-
|
|
1030
|
-
if (!credential) {
|
|
1031
|
-
throw Boom.badRequest('Invalid credential ID');
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
const module = await getModuleInstanceFromType.execute(
|
|
1035
|
-
userId,
|
|
1036
|
-
params.entityType
|
|
1037
|
-
);
|
|
1038
|
-
const entityDetails = await module.getEntityDetails(
|
|
1039
|
-
module.api,
|
|
1040
|
-
null,
|
|
1041
|
-
null,
|
|
1042
|
-
userId
|
|
1043
|
-
);
|
|
1044
|
-
|
|
1045
|
-
res.json(await module.findOrCreateEntity(entityDetails));
|
|
1046
|
-
})
|
|
1047
|
-
);
|
|
1048
|
-
|
|
1049
|
-
// GET /api/entities/types - List all available entity types
|
|
1050
|
-
// NOTE: This route MUST come before /api/entities/:entityId
|
|
1051
|
-
router.route('/api/entities/types').get(
|
|
1052
|
-
catchAsyncError(async (req, res) => {
|
|
1053
|
-
await authenticateUser.execute(req);
|
|
1054
|
-
|
|
1055
|
-
// Map module definitions to entity type format
|
|
1056
|
-
const types = moduleDefinitions.map((moduleDef) => {
|
|
1057
|
-
const Definition = moduleDef.definition;
|
|
1058
|
-
|
|
1059
|
-
return {
|
|
1060
|
-
type: moduleDef.moduleName,
|
|
1061
|
-
name:
|
|
1062
|
-
typeof Definition.getDisplayName === 'function'
|
|
1063
|
-
? Definition.getDisplayName()
|
|
1064
|
-
: moduleDef.moduleName,
|
|
1065
|
-
description:
|
|
1066
|
-
typeof Definition.getDescription === 'function'
|
|
1067
|
-
? Definition.getDescription()
|
|
1068
|
-
: undefined,
|
|
1069
|
-
authType:
|
|
1070
|
-
typeof Definition.getAuthType === 'function'
|
|
1071
|
-
? Definition.getAuthType()
|
|
1072
|
-
: 'oauth2',
|
|
1073
|
-
isMultiStep:
|
|
1074
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1075
|
-
? Definition.getAuthStepCount() > 1
|
|
1076
|
-
: false,
|
|
1077
|
-
stepCount:
|
|
1078
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1079
|
-
? Definition.getAuthStepCount()
|
|
1080
|
-
: 1,
|
|
1081
|
-
capabilities:
|
|
1082
|
-
typeof Definition.getCapabilities === 'function'
|
|
1083
|
-
? Definition.getCapabilities()
|
|
1084
|
-
: undefined,
|
|
1085
|
-
};
|
|
1086
|
-
});
|
|
1087
|
-
|
|
1088
|
-
// Sort by name
|
|
1089
|
-
types.sort((a, b) => a.name.localeCompare(b.name));
|
|
1090
|
-
|
|
1091
|
-
res.json({ types });
|
|
1092
|
-
})
|
|
1093
|
-
);
|
|
1094
|
-
|
|
1095
|
-
// GET /api/entities/types/:typeName - Get details for specific entity type
|
|
1096
|
-
// NOTE: This route MUST come before /api/entities/:entityId
|
|
1097
|
-
router.route('/api/entities/types/:typeName').get(
|
|
1098
|
-
catchAsyncError(async (req, res) => {
|
|
1099
|
-
await authenticateUser.execute(req);
|
|
1100
|
-
const { typeName } = req.params;
|
|
1101
|
-
|
|
1102
|
-
// Validate type name format
|
|
1103
|
-
if (!/^[a-z0-9-_]+$/i.test(typeName)) {
|
|
1104
|
-
throw Boom.badRequest('Invalid type name format');
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// Find module definition
|
|
1108
|
-
const moduleDef = moduleDefinitions.find(
|
|
1109
|
-
(def) => def.moduleName === typeName
|
|
1110
|
-
);
|
|
1111
|
-
|
|
1112
|
-
if (!moduleDef) {
|
|
1113
|
-
throw Boom.notFound(`Entity type '${typeName}' not found`);
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
const Definition = moduleDef.definition;
|
|
1117
|
-
|
|
1118
|
-
const entityType = {
|
|
1119
|
-
type: moduleDef.moduleName,
|
|
1120
|
-
name:
|
|
1121
|
-
typeof Definition.getDisplayName === 'function'
|
|
1122
|
-
? Definition.getDisplayName()
|
|
1123
|
-
: moduleDef.moduleName,
|
|
1124
|
-
description:
|
|
1125
|
-
typeof Definition.getDescription === 'function'
|
|
1126
|
-
? Definition.getDescription()
|
|
1127
|
-
: undefined,
|
|
1128
|
-
authType:
|
|
1129
|
-
typeof Definition.getAuthType === 'function'
|
|
1130
|
-
? Definition.getAuthType()
|
|
1131
|
-
: 'oauth2',
|
|
1132
|
-
isMultiStep:
|
|
1133
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1134
|
-
? Definition.getAuthStepCount() > 1
|
|
1135
|
-
: false,
|
|
1136
|
-
stepCount:
|
|
1137
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1138
|
-
? Definition.getAuthStepCount()
|
|
1139
|
-
: 1,
|
|
1140
|
-
capabilities:
|
|
1141
|
-
typeof Definition.getCapabilities === 'function'
|
|
1142
|
-
? Definition.getCapabilities()
|
|
1143
|
-
: undefined,
|
|
1144
|
-
};
|
|
1145
|
-
|
|
1146
|
-
res.json(entityType);
|
|
1147
|
-
})
|
|
1148
|
-
);
|
|
1149
|
-
|
|
1150
|
-
// GET /api/entities/types/:typeName/requirements - Get auth requirements for entity type
|
|
1151
|
-
// NOTE: This route MUST come before /api/entities/:entityId
|
|
1152
|
-
router.route('/api/entities/types/:typeName/requirements').get(
|
|
1153
|
-
catchAsyncError(async (req, res) => {
|
|
1154
|
-
await authenticateUser.execute(req);
|
|
1155
|
-
const { typeName } = req.params;
|
|
1156
|
-
const step = parseInt(req.query.step || '1', 10);
|
|
1157
|
-
const sessionId = req.query.sessionId;
|
|
1158
|
-
|
|
1159
|
-
// Validate step
|
|
1160
|
-
if (step < 1) {
|
|
1161
|
-
throw Boom.badRequest('step must be >= 1');
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// Find module definition
|
|
1165
|
-
const moduleDef = moduleDefinitions.find(
|
|
1166
|
-
(def) => def.moduleName === typeName
|
|
1167
|
-
);
|
|
1168
|
-
|
|
1169
|
-
if (!moduleDef) {
|
|
1170
|
-
throw Boom.notFound(`Entity type '${typeName}' not found`);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
const Definition = moduleDef.definition;
|
|
1174
|
-
const stepCount =
|
|
1175
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1176
|
-
? Definition.getAuthStepCount()
|
|
1177
|
-
: 1;
|
|
1178
|
-
|
|
1179
|
-
// Validate step is within range
|
|
1180
|
-
if (step > stepCount) {
|
|
1181
|
-
throw Boom.badRequest(
|
|
1182
|
-
`step ${step} exceeds total steps (${stepCount})`
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
// Validate sessionId for step > 1
|
|
1187
|
-
if (step > 1 && !sessionId) {
|
|
1188
|
-
throw Boom.badRequest('sessionId required for step > 1');
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
// For multi-step, validate sessionId format
|
|
1192
|
-
if (step > 1 && sessionId) {
|
|
1193
|
-
// Basic validation - sessionId should be a non-empty string
|
|
1194
|
-
if (typeof sessionId !== 'string' || sessionId.trim() === '') {
|
|
1195
|
-
throw Boom.badRequest('Invalid sessionId format');
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// Get requirements from module definition
|
|
1200
|
-
const requirements = await getAuthorizationRequirements.execute(
|
|
1201
|
-
typeName,
|
|
1202
|
-
step
|
|
1203
|
-
);
|
|
1204
|
-
|
|
1205
|
-
// Add sessionId for multi-step flows
|
|
1206
|
-
if (stepCount > 1) {
|
|
1207
|
-
if (step === 1) {
|
|
1208
|
-
// Generate new sessionId for step 1
|
|
1209
|
-
const crypto = require('crypto');
|
|
1210
|
-
requirements.sessionId = crypto.randomUUID();
|
|
1211
|
-
} else {
|
|
1212
|
-
// Use provided sessionId for subsequent steps
|
|
1213
|
-
requirements.sessionId = sessionId;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
res.json(requirements);
|
|
1218
|
-
})
|
|
1219
|
-
);
|
|
1220
|
-
|
|
1221
|
-
// GET /api/entities/options/:credentialId - Get entity options for credential
|
|
1222
|
-
// NOTE: This route MUST come before /api/entities/:entityId
|
|
1223
|
-
router.route('/api/entities/options/:credentialId').get(
|
|
1224
|
-
catchAsyncError(async (req, res) => {
|
|
1225
|
-
const user = await authenticateUser.execute(req);
|
|
1226
|
-
const userId = user.getId();
|
|
1227
|
-
// TODO May want to pass along the user ID as well so credential ID's can't be fished???
|
|
1228
|
-
// TODO **flagging this for review** -MW
|
|
1229
|
-
const credential = await getCredentialForUser.execute(
|
|
1230
|
-
req.params.credentialId,
|
|
1231
|
-
userId
|
|
1232
|
-
);
|
|
1233
|
-
if (credential.userId.toString() !== userId) {
|
|
1234
|
-
throw Boom.forbidden('Credential does not belong to user');
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const params = checkRequiredParams(req.query, ['entityType']);
|
|
1238
|
-
const entityOptions = await getEntityOptionsByType.execute(
|
|
1239
|
-
userId,
|
|
1240
|
-
params.entityType
|
|
1241
|
-
);
|
|
1242
|
-
|
|
1243
|
-
res.json(entityOptions);
|
|
1244
|
-
})
|
|
1245
|
-
);
|
|
1246
|
-
|
|
1247
|
-
// GET /api/entities/:entityId/test-auth - Test authentication for entity
|
|
1248
|
-
router.route('/api/entities/:entityId/test-auth').get(
|
|
1249
|
-
catchAsyncError(async (req, res) => {
|
|
1250
|
-
const user = await authenticateUser.execute(req);
|
|
1251
|
-
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1252
|
-
const testAuthResponse = await testModuleAuth.execute(
|
|
1253
|
-
params.entityId,
|
|
1254
|
-
user // Pass User object for proper validation
|
|
1255
|
-
);
|
|
1256
|
-
|
|
1257
|
-
if (!testAuthResponse) {
|
|
1258
|
-
res.status(400);
|
|
1259
|
-
res.json({
|
|
1260
|
-
errors: [
|
|
1261
|
-
{
|
|
1262
|
-
title: 'Authentication Error',
|
|
1263
|
-
message: `There was an error with your Entity. Please reconnect/re-authenticate, or reach out to Support for assistance.`,
|
|
1264
|
-
timestamp: Date.now(),
|
|
1265
|
-
},
|
|
1266
|
-
],
|
|
1267
|
-
});
|
|
1268
|
-
} else {
|
|
1269
|
-
res.json({ status: 'ok' });
|
|
1270
|
-
}
|
|
1271
|
-
})
|
|
1272
|
-
);
|
|
1273
|
-
|
|
1274
|
-
// GET /api/entities/:entityId - Get entity by ID
|
|
1275
|
-
router.route('/api/entities/:entityId').get(
|
|
1276
|
-
catchAsyncError(async (req, res) => {
|
|
1277
|
-
const user = await authenticateUser.execute(req);
|
|
1278
|
-
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1279
|
-
const module = await getModule.execute(params.entityId, user); // Pass User object
|
|
1280
|
-
|
|
1281
|
-
res.json(module);
|
|
1282
|
-
})
|
|
1283
|
-
);
|
|
1284
|
-
|
|
1285
|
-
// POST /api/entities/:entityId/options - Get entity options by ID
|
|
1286
|
-
router.route('/api/entities/:entityId/options').post(
|
|
1287
|
-
catchAsyncError(async (req, res) => {
|
|
1288
|
-
const user = await authenticateUser.execute(req);
|
|
1289
|
-
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1290
|
-
|
|
1291
|
-
const entityOptions = await getEntityOptionsById.execute(
|
|
1292
|
-
params.entityId,
|
|
1293
|
-
user // Pass User object
|
|
1294
|
-
);
|
|
1295
|
-
|
|
1296
|
-
res.json(entityOptions);
|
|
1297
|
-
})
|
|
1298
|
-
);
|
|
1299
|
-
|
|
1300
|
-
// POST /api/entities/:entityId/options/refresh - Refresh entity options
|
|
1301
|
-
router.route('/api/entities/:entityId/options/refresh').post(
|
|
1302
|
-
catchAsyncError(async (req, res) => {
|
|
1303
|
-
const user = await authenticateUser.execute(req);
|
|
1304
|
-
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1305
|
-
const updatedOptions = await refreshEntityOptions.execute(
|
|
1306
|
-
params.entityId,
|
|
1307
|
-
user, // Pass User object
|
|
1308
|
-
req.body
|
|
1309
|
-
);
|
|
1310
|
-
|
|
1311
|
-
res.json(updatedOptions);
|
|
1312
|
-
})
|
|
1313
|
-
);
|
|
1314
|
-
|
|
1315
|
-
// POST /api/entities/:id/reauthorize - Reauthorize specific entity
|
|
1316
|
-
router.route('/api/entities/:id/reauthorize').post(
|
|
1317
|
-
catchAsyncError(async (req, res) => {
|
|
1318
|
-
const user = await authenticateUser.execute(req);
|
|
1319
|
-
const userId = user.getId();
|
|
1320
|
-
const entityId = req.params.id;
|
|
1321
|
-
|
|
1322
|
-
// Validate data parameter
|
|
1323
|
-
const params = checkRequiredParams(req.body, ['data']);
|
|
1324
|
-
if (typeof params.data !== 'object' || Array.isArray(params.data)) {
|
|
1325
|
-
throw Boom.badRequest('data must be an object');
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
const step = parseInt(req.body.step || '1', 10);
|
|
1329
|
-
const sessionId = req.body.sessionId;
|
|
1330
|
-
|
|
1331
|
-
// Validate step
|
|
1332
|
-
if (step < 1) {
|
|
1333
|
-
throw Boom.badRequest('step must be >= 1');
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// Get entity
|
|
1337
|
-
const entity = await moduleRepository?.findById(entityId);
|
|
1338
|
-
if (!entity) {
|
|
1339
|
-
throw Boom.notFound('Entity not found');
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
// Check ownership
|
|
1343
|
-
if (entity.userId.toString() !== userId) {
|
|
1344
|
-
throw Boom.forbidden(
|
|
1345
|
-
'User is not authorized to access this entity'
|
|
1346
|
-
);
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
// Get credential
|
|
1350
|
-
const credential = await credentialRepository?.findById(
|
|
1351
|
-
entity.credentialId
|
|
1352
|
-
);
|
|
1353
|
-
|
|
1354
|
-
// Find module definition
|
|
1355
|
-
const moduleDef = moduleDefinitions.find(
|
|
1356
|
-
(def) => def.moduleName === entity.type
|
|
1357
|
-
);
|
|
1358
|
-
|
|
1359
|
-
if (!moduleDef) {
|
|
1360
|
-
throw Boom.badRequest(`Unknown entity type: ${entity.type}`);
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
const Definition = moduleDef.definition;
|
|
1364
|
-
const stepCount =
|
|
1365
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1366
|
-
? Definition.getAuthStepCount()
|
|
1367
|
-
: 1;
|
|
1368
|
-
|
|
1369
|
-
// Validate step is within range
|
|
1370
|
-
if (step > stepCount) {
|
|
1371
|
-
throw Boom.badRequest(
|
|
1372
|
-
`step ${step} exceeds total steps (${stepCount})`
|
|
1373
|
-
);
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// Single-step reauthorization
|
|
1377
|
-
if (stepCount === 1) {
|
|
1378
|
-
try {
|
|
1379
|
-
// Process reauthorization using existing flow
|
|
1380
|
-
const result = await processAuthorizationCallback.execute(
|
|
1381
|
-
userId,
|
|
1382
|
-
entity.type,
|
|
1383
|
-
params.data
|
|
1384
|
-
);
|
|
1385
|
-
|
|
1386
|
-
// Update entity status
|
|
1387
|
-
const updatedEntity = await moduleRepository?.update({
|
|
1388
|
-
id: entityId,
|
|
1389
|
-
authIsValid: true,
|
|
1390
|
-
});
|
|
1391
|
-
|
|
1392
|
-
res.json({
|
|
1393
|
-
success: true,
|
|
1394
|
-
credential_id: result.credential_id,
|
|
1395
|
-
entity_id: entityId,
|
|
1396
|
-
authIsValid: true,
|
|
1397
|
-
});
|
|
1398
|
-
} catch (error) {
|
|
1399
|
-
throw Boom.badRequest(
|
|
1400
|
-
error.message || 'Reauthorization failed'
|
|
1401
|
-
);
|
|
1402
|
-
}
|
|
1403
|
-
} else {
|
|
1404
|
-
// Multi-step reauthorization
|
|
1405
|
-
if (step > 1 && !sessionId) {
|
|
1406
|
-
throw Boom.badRequest('sessionId required for step > 1');
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
let session;
|
|
1410
|
-
|
|
1411
|
-
if (step === 1) {
|
|
1412
|
-
// Create new session for step 1
|
|
1413
|
-
session = await startAuthorizationSession.execute(
|
|
1414
|
-
userId,
|
|
1415
|
-
entity.type,
|
|
1416
|
-
stepCount
|
|
1417
|
-
);
|
|
1418
|
-
|
|
1419
|
-
// Override with client-provided sessionId
|
|
1420
|
-
if (sessionId) {
|
|
1421
|
-
session.sessionId = sessionId;
|
|
1422
|
-
await authSessionRepository?.update(session);
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// Process this step
|
|
1427
|
-
const result = await processAuthorizationStep.execute(
|
|
1428
|
-
sessionId || session?.sessionId,
|
|
1429
|
-
userId,
|
|
1430
|
-
step,
|
|
1431
|
-
params.data
|
|
1432
|
-
);
|
|
1433
|
-
|
|
1434
|
-
if (result.completed) {
|
|
1435
|
-
// Final step - update credential and entity
|
|
1436
|
-
try {
|
|
1437
|
-
const authResult =
|
|
1438
|
-
await processAuthorizationCallback.execute(
|
|
1439
|
-
userId,
|
|
1440
|
-
entity.type,
|
|
1441
|
-
result.authData
|
|
1442
|
-
);
|
|
1443
|
-
|
|
1444
|
-
// Update entity status
|
|
1445
|
-
await moduleRepository?.update({
|
|
1446
|
-
id: entityId,
|
|
1447
|
-
authIsValid: true,
|
|
1448
|
-
});
|
|
1449
|
-
|
|
1450
|
-
return res.json({
|
|
1451
|
-
success: true,
|
|
1452
|
-
credential_id: authResult.credential_id,
|
|
1453
|
-
entity_id: entityId,
|
|
1454
|
-
authIsValid: true,
|
|
1455
|
-
});
|
|
1456
|
-
} catch (error) {
|
|
1457
|
-
throw Boom.badRequest(
|
|
1458
|
-
error.message || 'Reauthorization failed'
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// Return next step requirements
|
|
1464
|
-
res.json({
|
|
1465
|
-
step: result.nextStep,
|
|
1466
|
-
totalSteps: result.totalSteps,
|
|
1467
|
-
sessionId: result.sessionId,
|
|
1468
|
-
requirements: result.requirements,
|
|
1469
|
-
message: result.message,
|
|
1470
|
-
});
|
|
1471
|
-
}
|
|
1472
|
-
})
|
|
1473
|
-
);
|
|
1474
|
-
|
|
1475
|
-
// POST /api/entities/:id/proxy - Proxy request through entity's API connection
|
|
1476
|
-
router.route('/api/entities/:id/proxy').post(
|
|
1477
|
-
catchAsyncError(async (req, res) => {
|
|
1478
|
-
const user = await authenticateUser.execute(req);
|
|
1479
|
-
const userId = user.getId();
|
|
1480
|
-
const entityId = req.params.id;
|
|
1481
|
-
|
|
1482
|
-
try {
|
|
1483
|
-
// Execute proxy request via entity
|
|
1484
|
-
const proxyResponse =
|
|
1485
|
-
await executeProxyRequest.executeViaEntity(
|
|
1486
|
-
entityId,
|
|
1487
|
-
userId,
|
|
1488
|
-
req.body
|
|
1489
|
-
);
|
|
1490
|
-
|
|
1491
|
-
// Return success response
|
|
1492
|
-
res.status(200).json(proxyResponse);
|
|
1493
|
-
} catch (error) {
|
|
1494
|
-
// Handle Boom errors
|
|
1495
|
-
if (Boom.isBoom(error)) {
|
|
1496
|
-
const statusCode = error.output.statusCode;
|
|
1497
|
-
const errorData = error.data || {};
|
|
1498
|
-
|
|
1499
|
-
// Build error response matching proxyErrorResponse schema
|
|
1500
|
-
const errorResponse = {
|
|
1501
|
-
success: false,
|
|
1502
|
-
status: statusCode,
|
|
1503
|
-
error: {
|
|
1504
|
-
code:
|
|
1505
|
-
errorData.code ||
|
|
1506
|
-
_getErrorCodeFromStatus(statusCode),
|
|
1507
|
-
message:
|
|
1508
|
-
error.output.payload.message || error.message,
|
|
1509
|
-
details: errorData.details || null,
|
|
1510
|
-
},
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
// Add upstreamStatus if present
|
|
1514
|
-
if (errorData.upstreamStatus) {
|
|
1515
|
-
errorResponse.error.upstreamStatus =
|
|
1516
|
-
errorData.upstreamStatus;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
return res.status(statusCode).json(errorResponse);
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// Unknown error - return 500
|
|
1523
|
-
res.status(500).json({
|
|
1524
|
-
success: false,
|
|
1525
|
-
status: 500,
|
|
1526
|
-
error: {
|
|
1527
|
-
code: 'INTERNAL_ERROR',
|
|
1528
|
-
message: 'An unexpected error occurred',
|
|
1529
|
-
details: null,
|
|
1530
|
-
},
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
})
|
|
1534
|
-
);
|
|
1535
|
-
|
|
1536
|
-
// POST /api/credentials/:id/proxy - Proxy request through credential's API connection
|
|
1537
|
-
router.route('/api/credentials/:id/proxy').post(
|
|
1538
|
-
catchAsyncError(async (req, res) => {
|
|
1539
|
-
const user = await authenticateUser.execute(req);
|
|
1540
|
-
const userId = user.getId();
|
|
1541
|
-
const credentialId = req.params.id;
|
|
1542
|
-
|
|
1543
|
-
try {
|
|
1544
|
-
// Execute proxy request via credential
|
|
1545
|
-
const proxyResponse =
|
|
1546
|
-
await executeProxyRequest.executeViaCredential(
|
|
1547
|
-
credentialId,
|
|
1548
|
-
userId,
|
|
1549
|
-
req.body
|
|
1550
|
-
);
|
|
1551
|
-
|
|
1552
|
-
// Return success response
|
|
1553
|
-
res.status(200).json(proxyResponse);
|
|
1554
|
-
} catch (error) {
|
|
1555
|
-
// Handle Boom errors
|
|
1556
|
-
if (Boom.isBoom(error)) {
|
|
1557
|
-
const statusCode = error.output.statusCode;
|
|
1558
|
-
const errorData = error.data || {};
|
|
1559
|
-
|
|
1560
|
-
// Build error response matching proxyErrorResponse schema
|
|
1561
|
-
const errorResponse = {
|
|
1562
|
-
success: false,
|
|
1563
|
-
status: statusCode,
|
|
1564
|
-
error: {
|
|
1565
|
-
code:
|
|
1566
|
-
errorData.code ||
|
|
1567
|
-
_getErrorCodeFromStatus(statusCode),
|
|
1568
|
-
message:
|
|
1569
|
-
error.output.payload.message || error.message,
|
|
1570
|
-
details: errorData.details || null,
|
|
1571
|
-
},
|
|
1572
|
-
};
|
|
1573
|
-
|
|
1574
|
-
// Add upstreamStatus if present
|
|
1575
|
-
if (errorData.upstreamStatus) {
|
|
1576
|
-
errorResponse.error.upstreamStatus =
|
|
1577
|
-
errorData.upstreamStatus;
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
return res.status(statusCode).json(errorResponse);
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
// Unknown error - return 500
|
|
1584
|
-
res.status(500).json({
|
|
1585
|
-
success: false,
|
|
1586
|
-
status: 500,
|
|
1587
|
-
error: {
|
|
1588
|
-
code: 'INTERNAL_ERROR',
|
|
1589
|
-
message: 'An unexpected error occurred',
|
|
1590
|
-
details: null,
|
|
1591
|
-
},
|
|
1592
|
-
});
|
|
1593
|
-
}
|
|
1594
|
-
})
|
|
1595
|
-
);
|
|
1596
|
-
|
|
1597
|
-
// =========================================================================
|
|
1598
|
-
// v1 Legacy Aliases (backwards compatibility for singular /api/entity)
|
|
1599
|
-
// =========================================================================
|
|
1600
|
-
|
|
1601
|
-
// POST /api/entity - v1 legacy alias for POST /api/entities
|
|
1602
|
-
router.route('/api/entity').post(
|
|
1603
|
-
catchAsyncError(async (req, res) => {
|
|
1604
|
-
const user = await authenticateUser.execute(req);
|
|
1605
|
-
const userId = user.getId();
|
|
1606
|
-
const params = checkRequiredParams(req.body, [
|
|
1607
|
-
'entityType',
|
|
1608
|
-
'data',
|
|
1609
|
-
]);
|
|
1610
|
-
checkRequiredParams(req.body.data, ['credential_id']);
|
|
1611
|
-
|
|
1612
|
-
const credential = await getCredentialForUser.execute(
|
|
1613
|
-
params.data.credential_id,
|
|
1614
|
-
userId
|
|
1615
|
-
);
|
|
1616
|
-
|
|
1617
|
-
if (!credential) {
|
|
1618
|
-
throw Boom.badRequest('Invalid credential ID');
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
const module = await getModuleInstanceFromType.execute(
|
|
1622
|
-
userId,
|
|
1623
|
-
params.entityType
|
|
1624
|
-
);
|
|
1625
|
-
const entityDetails = await module.getEntityDetails(
|
|
1626
|
-
module.api,
|
|
1627
|
-
null,
|
|
1628
|
-
null,
|
|
1629
|
-
userId
|
|
1630
|
-
);
|
|
1631
|
-
|
|
1632
|
-
res.json(await module.findOrCreateEntity(entityDetails));
|
|
1633
|
-
})
|
|
1634
|
-
);
|
|
1635
|
-
|
|
1636
|
-
// GET /api/entity/options/:credentialId - v1 legacy alias
|
|
1637
|
-
router.route('/api/entity/options/:credentialId').get(
|
|
1638
|
-
catchAsyncError(async (req, res) => {
|
|
1639
|
-
const user = await authenticateUser.execute(req);
|
|
1640
|
-
const userId = user.getId();
|
|
1641
|
-
const credential = await getCredentialForUser.execute(
|
|
1642
|
-
req.params.credentialId,
|
|
1643
|
-
userId
|
|
1644
|
-
);
|
|
1645
|
-
if (credential.userId.toString() !== userId) {
|
|
1646
|
-
throw Boom.forbidden('Credential does not belong to user');
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
const params = checkRequiredParams(req.query, ['entityType']);
|
|
1650
|
-
const entityOptions = await getEntityOptionsByType.execute(
|
|
1651
|
-
userId,
|
|
1652
|
-
params.entityType
|
|
1653
|
-
);
|
|
1654
|
-
|
|
1655
|
-
res.json(entityOptions);
|
|
1656
|
-
})
|
|
1657
|
-
);
|
|
1658
|
-
|
|
1659
|
-
// =========================================================================
|
|
1660
|
-
// v2 API Routes - Entity endpoints (can evolve independently from v1)
|
|
1661
|
-
// =========================================================================
|
|
1662
|
-
|
|
1663
|
-
// GET /api/v2/authorize - Get authorization requirements (v2)
|
|
1664
|
-
router.route('/api/v2/authorize').get(
|
|
1665
|
-
catchAsyncError(async (req, res) => {
|
|
1666
|
-
const user = await authenticateUser.execute(req);
|
|
1667
|
-
const userId = user.getId();
|
|
1668
|
-
const params = checkRequiredParams(req.query, ['entityType']);
|
|
1669
|
-
const step = parseInt(req.query.step || '1', 10);
|
|
1670
|
-
const sessionId = req.query.sessionId;
|
|
1671
|
-
|
|
1672
|
-
if (step > 1 && !sessionId) {
|
|
1673
|
-
throw Boom.badRequest('sessionId required for step > 1');
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
const requirements = await getAuthorizationRequirements.execute(
|
|
1677
|
-
params.entityType,
|
|
1678
|
-
step
|
|
1679
|
-
);
|
|
1680
|
-
|
|
1681
|
-
if (requirements.isMultiStep && step === 1 && !sessionId) {
|
|
1682
|
-
const session = await startAuthorizationSession.execute(
|
|
1683
|
-
userId,
|
|
1684
|
-
params.entityType,
|
|
1685
|
-
{
|
|
1686
|
-
step: 1,
|
|
1687
|
-
totalSteps: requirements.totalSteps,
|
|
1688
|
-
}
|
|
1689
|
-
);
|
|
1690
|
-
requirements.sessionId = session.id;
|
|
1691
|
-
} else if (sessionId) {
|
|
1692
|
-
requirements.sessionId = sessionId;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
res.json(requirements);
|
|
1696
|
-
})
|
|
1697
|
-
);
|
|
1698
|
-
|
|
1699
|
-
// POST /api/v2/authorize - Process authorization callback (v2)
|
|
1700
|
-
router.route('/api/v2/authorize').post(
|
|
1701
|
-
catchAsyncError(async (req, res) => {
|
|
1702
|
-
const user = await authenticateUser.execute(req);
|
|
1703
|
-
const userId = user.getId();
|
|
1704
|
-
const params = checkRequiredParams(req.body, [
|
|
1705
|
-
'entityType',
|
|
1706
|
-
'data',
|
|
1707
|
-
]);
|
|
1708
|
-
const step = parseInt(req.body.step || '1', 10);
|
|
1709
|
-
const sessionId = req.body.sessionId;
|
|
1710
|
-
|
|
1711
|
-
if (step > 1 && !sessionId) {
|
|
1712
|
-
throw Boom.badRequest('sessionId required for step > 1');
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
const result = await processAuthorizationStep.execute(
|
|
1716
|
-
userId,
|
|
1717
|
-
params.entityType,
|
|
1718
|
-
params.data,
|
|
1719
|
-
step,
|
|
1720
|
-
sessionId
|
|
1721
|
-
);
|
|
1722
|
-
|
|
1723
|
-
if (result.isComplete) {
|
|
1724
|
-
res.json({
|
|
1725
|
-
status: 'complete',
|
|
1726
|
-
entity: result.entity,
|
|
1727
|
-
credential: result.credential,
|
|
1728
|
-
});
|
|
1729
|
-
} else {
|
|
1730
|
-
res.json({
|
|
1731
|
-
status: 'pending',
|
|
1732
|
-
step: result.nextStep,
|
|
1733
|
-
totalSteps: result.totalSteps,
|
|
1734
|
-
sessionId: result.sessionId,
|
|
1735
|
-
requirements: result.requirements,
|
|
1736
|
-
message: result.message,
|
|
1737
|
-
});
|
|
1738
|
-
}
|
|
1739
|
-
})
|
|
1740
|
-
);
|
|
1741
|
-
|
|
1742
|
-
// POST /api/v2/entities - Create entity (v2)
|
|
1743
|
-
router.route('/api/v2/entities').post(
|
|
1744
|
-
catchAsyncError(async (req, res) => {
|
|
1745
|
-
const user = await authenticateUser.execute(req);
|
|
1746
|
-
const userId = user.getId();
|
|
1747
|
-
const params = checkRequiredParams(req.body, [
|
|
1748
|
-
'entityType',
|
|
1749
|
-
'data',
|
|
1750
|
-
]);
|
|
1751
|
-
checkRequiredParams(req.body.data, ['credential_id']);
|
|
1752
|
-
|
|
1753
|
-
const credential = await getCredentialForUser.execute(
|
|
1754
|
-
params.data.credential_id,
|
|
1755
|
-
userId
|
|
1756
|
-
);
|
|
1757
|
-
|
|
1758
|
-
if (!credential) {
|
|
1759
|
-
throw Boom.badRequest('Invalid credential ID');
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
const module = await getModuleInstanceFromType.execute(
|
|
1763
|
-
userId,
|
|
1764
|
-
params.entityType
|
|
1765
|
-
);
|
|
1766
|
-
const entityDetails = await module.getEntityDetails(
|
|
1767
|
-
module.api,
|
|
1768
|
-
null,
|
|
1769
|
-
null,
|
|
1770
|
-
userId
|
|
1771
|
-
);
|
|
1772
|
-
|
|
1773
|
-
res.json(await module.findOrCreateEntity(entityDetails));
|
|
1774
|
-
})
|
|
1775
|
-
);
|
|
1776
|
-
|
|
1777
|
-
// GET /api/v2/entities/types - List available entity types (v2)
|
|
1778
|
-
router.route('/api/v2/entities/types').get(
|
|
1779
|
-
catchAsyncError(async (req, res) => {
|
|
1780
|
-
await authenticateUser.execute(req);
|
|
1781
|
-
|
|
1782
|
-
const types = moduleDefinitions.map((moduleDef) => {
|
|
1783
|
-
const Definition = moduleDef.definition;
|
|
1784
|
-
return {
|
|
1785
|
-
type: moduleDef.moduleName,
|
|
1786
|
-
name:
|
|
1787
|
-
typeof Definition.getDisplayName === 'function'
|
|
1788
|
-
? Definition.getDisplayName()
|
|
1789
|
-
: moduleDef.moduleName,
|
|
1790
|
-
description:
|
|
1791
|
-
typeof Definition.getDescription === 'function'
|
|
1792
|
-
? Definition.getDescription()
|
|
1793
|
-
: undefined,
|
|
1794
|
-
authType:
|
|
1795
|
-
typeof Definition.getAuthType === 'function'
|
|
1796
|
-
? Definition.getAuthType()
|
|
1797
|
-
: 'oauth2',
|
|
1798
|
-
isMultiStep:
|
|
1799
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1800
|
-
? Definition.getAuthStepCount() > 1
|
|
1801
|
-
: false,
|
|
1802
|
-
stepCount:
|
|
1803
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1804
|
-
? Definition.getAuthStepCount()
|
|
1805
|
-
: 1,
|
|
1806
|
-
capabilities:
|
|
1807
|
-
typeof Definition.getCapabilities === 'function'
|
|
1808
|
-
? Definition.getCapabilities()
|
|
1809
|
-
: undefined,
|
|
1810
|
-
};
|
|
1811
|
-
});
|
|
1812
|
-
|
|
1813
|
-
res.json({ types });
|
|
1814
|
-
})
|
|
1815
|
-
);
|
|
1816
|
-
|
|
1817
|
-
// GET /api/v2/entities/types/:typeName - Get entity type details (v2)
|
|
1818
|
-
router.route('/api/v2/entities/types/:typeName').get(
|
|
1819
|
-
catchAsyncError(async (req, res) => {
|
|
1820
|
-
await authenticateUser.execute(req);
|
|
1821
|
-
const { typeName } = req.params;
|
|
1822
|
-
|
|
1823
|
-
const moduleDef = moduleDefinitions.find(
|
|
1824
|
-
(def) => def.moduleName === typeName
|
|
1825
|
-
);
|
|
1826
|
-
|
|
1827
|
-
if (!moduleDef) {
|
|
1828
|
-
throw Boom.notFound(`Entity type '${typeName}' not found`);
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
const Definition = moduleDef.definition;
|
|
1832
|
-
res.json({
|
|
1833
|
-
type: moduleDef.moduleName,
|
|
1834
|
-
name:
|
|
1835
|
-
typeof Definition.getDisplayName === 'function'
|
|
1836
|
-
? Definition.getDisplayName()
|
|
1837
|
-
: moduleDef.moduleName,
|
|
1838
|
-
description:
|
|
1839
|
-
typeof Definition.getDescription === 'function'
|
|
1840
|
-
? Definition.getDescription()
|
|
1841
|
-
: undefined,
|
|
1842
|
-
authType:
|
|
1843
|
-
typeof Definition.getAuthType === 'function'
|
|
1844
|
-
? Definition.getAuthType()
|
|
1845
|
-
: 'oauth2',
|
|
1846
|
-
isMultiStep:
|
|
1847
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1848
|
-
? Definition.getAuthStepCount() > 1
|
|
1849
|
-
: false,
|
|
1850
|
-
stepCount:
|
|
1851
|
-
typeof Definition.getAuthStepCount === 'function'
|
|
1852
|
-
? Definition.getAuthStepCount()
|
|
1853
|
-
: 1,
|
|
1854
|
-
capabilities:
|
|
1855
|
-
typeof Definition.getCapabilities === 'function'
|
|
1856
|
-
? Definition.getCapabilities()
|
|
1857
|
-
: undefined,
|
|
1858
|
-
});
|
|
1859
|
-
})
|
|
1860
|
-
);
|
|
1861
|
-
|
|
1862
|
-
// GET /api/v2/entities/types/:typeName/requirements - Get auth requirements (v2)
|
|
1863
|
-
router.route('/api/v2/entities/types/:typeName/requirements').get(
|
|
1864
|
-
catchAsyncError(async (req, res) => {
|
|
1865
|
-
const user = await authenticateUser.execute(req);
|
|
1866
|
-
const userId = user.getId();
|
|
1867
|
-
const { typeName } = req.params;
|
|
1868
|
-
const step = parseInt(req.query.step || '1', 10);
|
|
1869
|
-
const sessionId = req.query.sessionId;
|
|
1870
|
-
|
|
1871
|
-
if (step > 1 && !sessionId) {
|
|
1872
|
-
throw Boom.badRequest('sessionId required for step > 1');
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
const requirements = await getAuthorizationRequirements.execute(
|
|
1876
|
-
typeName,
|
|
1877
|
-
step
|
|
1878
|
-
);
|
|
1879
|
-
|
|
1880
|
-
if (requirements.isMultiStep && step === 1 && !sessionId) {
|
|
1881
|
-
const session = await startAuthorizationSession.execute(
|
|
1882
|
-
userId,
|
|
1883
|
-
typeName,
|
|
1884
|
-
{
|
|
1885
|
-
step: 1,
|
|
1886
|
-
totalSteps: requirements.totalSteps,
|
|
1887
|
-
}
|
|
1888
|
-
);
|
|
1889
|
-
requirements.sessionId = session.id;
|
|
1890
|
-
} else if (sessionId) {
|
|
1891
|
-
requirements.sessionId = sessionId;
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
res.json(requirements);
|
|
1895
|
-
})
|
|
1896
|
-
);
|
|
1897
|
-
|
|
1898
|
-
// GET /api/v2/entities/options/:credentialId - Get entity options (v2)
|
|
1899
|
-
router.route('/api/v2/entities/options/:credentialId').get(
|
|
1900
|
-
catchAsyncError(async (req, res) => {
|
|
1901
|
-
const user = await authenticateUser.execute(req);
|
|
1902
|
-
const userId = user.getId();
|
|
1903
|
-
const credential = await getCredentialForUser.execute(
|
|
1904
|
-
req.params.credentialId,
|
|
1905
|
-
userId
|
|
1906
|
-
);
|
|
1907
|
-
if (credential.userId.toString() !== userId) {
|
|
1908
|
-
throw Boom.forbidden('Credential does not belong to user');
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
const params = checkRequiredParams(req.query, ['entityType']);
|
|
1912
|
-
const entityOptions = await getEntityOptionsByType.execute(
|
|
1913
|
-
userId,
|
|
1914
|
-
params.entityType
|
|
1915
|
-
);
|
|
1916
|
-
|
|
1917
|
-
res.json(entityOptions);
|
|
1918
|
-
})
|
|
1919
|
-
);
|
|
1920
|
-
|
|
1921
|
-
// GET /api/v2/entities/:entityId/test-auth - Test auth (v2)
|
|
1922
|
-
router.route('/api/v2/entities/:entityId/test-auth').get(
|
|
1923
|
-
catchAsyncError(async (req, res) => {
|
|
1924
|
-
const user = await authenticateUser.execute(req);
|
|
1925
|
-
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1926
|
-
const testAuthResponse = await testModuleAuth.execute(
|
|
1927
|
-
params.entityId,
|
|
1928
|
-
user
|
|
1929
|
-
);
|
|
1930
|
-
|
|
1931
|
-
if (!testAuthResponse) {
|
|
1932
|
-
res.status(400);
|
|
1933
|
-
res.json({
|
|
1934
|
-
errors: [
|
|
1935
|
-
{
|
|
1936
|
-
title: 'Authentication Error',
|
|
1937
|
-
message:
|
|
1938
|
-
'There was an error with your Entity. Please reconnect/re-authenticate, or reach out to Support for assistance.',
|
|
1939
|
-
timestamp: Date.now(),
|
|
1940
|
-
},
|
|
1941
|
-
],
|
|
1942
|
-
});
|
|
1943
|
-
} else {
|
|
1944
|
-
res.json({ status: 'ok' });
|
|
1945
|
-
}
|
|
1946
|
-
})
|
|
1947
|
-
);
|
|
1948
|
-
|
|
1949
|
-
// GET /api/v2/entities/:entityId - Get entity by ID (v2)
|
|
1950
|
-
router.route('/api/v2/entities/:entityId').get(
|
|
1951
|
-
catchAsyncError(async (req, res) => {
|
|
1952
|
-
const user = await authenticateUser.execute(req);
|
|
1953
|
-
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1954
|
-
const module = await getModule.execute(params.entityId, user);
|
|
1955
|
-
|
|
1956
|
-
res.json(module);
|
|
1957
|
-
})
|
|
1958
|
-
);
|
|
1959
|
-
|
|
1960
|
-
// POST /api/v2/entities/:entityId/options - Get entity options (v2)
|
|
1961
|
-
router.route('/api/v2/entities/:entityId/options').post(
|
|
1962
|
-
catchAsyncError(async (req, res) => {
|
|
1963
|
-
const user = await authenticateUser.execute(req);
|
|
1964
|
-
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1965
|
-
|
|
1966
|
-
const entityOptions = await getEntityOptionsById.execute(
|
|
1967
|
-
params.entityId,
|
|
1968
|
-
user
|
|
1969
|
-
);
|
|
1970
|
-
|
|
1971
|
-
res.json(entityOptions);
|
|
1972
|
-
})
|
|
1973
|
-
);
|
|
1974
|
-
|
|
1975
|
-
// POST /api/v2/entities/:entityId/options/refresh - Refresh entity options (v2)
|
|
1976
|
-
router.route('/api/v2/entities/:entityId/options/refresh').post(
|
|
651
|
+
router.route('/api/entities/:entityId/options/refresh').post(
|
|
1977
652
|
catchAsyncError(async (req, res) => {
|
|
1978
653
|
const user = await authenticateUser.execute(req);
|
|
1979
654
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
1980
655
|
const updatedOptions = await refreshEntityOptions.execute(
|
|
1981
656
|
params.entityId,
|
|
1982
|
-
user,
|
|
657
|
+
user, // Pass User object
|
|
1983
658
|
req.body
|
|
1984
659
|
);
|
|
1985
660
|
|
|
1986
661
|
res.json(updatedOptions);
|
|
1987
662
|
})
|
|
1988
663
|
);
|
|
1989
|
-
|
|
1990
|
-
// POST /api/v2/entities/:id/reauthorize - Reauthorize entity (v2)
|
|
1991
|
-
router.route('/api/v2/entities/:id/reauthorize').post(
|
|
1992
|
-
catchAsyncError(async (req, res) => {
|
|
1993
|
-
const user = await authenticateUser.execute(req);
|
|
1994
|
-
const userId = user.getId();
|
|
1995
|
-
const entityId = req.params.id;
|
|
1996
|
-
|
|
1997
|
-
const params = checkRequiredParams(req.body, ['data']);
|
|
1998
|
-
if (typeof params.data !== 'object' || Array.isArray(params.data)) {
|
|
1999
|
-
throw Boom.badRequest('data must be an object');
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
const step = parseInt(req.body.step || '1', 10);
|
|
2003
|
-
const sessionId = req.body.sessionId;
|
|
2004
|
-
|
|
2005
|
-
const entity = await moduleRepository.findEntityById(entityId);
|
|
2006
|
-
if (!entity) {
|
|
2007
|
-
throw Boom.notFound('Entity not found');
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
const credential = await credentialRepository.findCredentialById(
|
|
2011
|
-
entity.credentialId
|
|
2012
|
-
);
|
|
2013
|
-
if (!credential || credential.userId.toString() !== userId) {
|
|
2014
|
-
throw Boom.forbidden('Access denied');
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
const result = await processAuthorizationStep.execute(
|
|
2018
|
-
userId,
|
|
2019
|
-
entity.type,
|
|
2020
|
-
params.data,
|
|
2021
|
-
step,
|
|
2022
|
-
sessionId
|
|
2023
|
-
);
|
|
2024
|
-
|
|
2025
|
-
if (result.isComplete) {
|
|
2026
|
-
if (result.credential) {
|
|
2027
|
-
try {
|
|
2028
|
-
await credentialRepository.updateCredential(
|
|
2029
|
-
credential.id,
|
|
2030
|
-
{
|
|
2031
|
-
data: result.credential.data,
|
|
2032
|
-
authIsValid: true,
|
|
2033
|
-
}
|
|
2034
|
-
);
|
|
2035
|
-
await moduleRepository.updateEntity(entityId, {
|
|
2036
|
-
authIsValid: true,
|
|
2037
|
-
});
|
|
2038
|
-
} catch (error) {
|
|
2039
|
-
throw Boom.badRequest(
|
|
2040
|
-
error.message || 'Reauthorization failed'
|
|
2041
|
-
);
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
res.json({
|
|
2046
|
-
step: result.nextStep,
|
|
2047
|
-
totalSteps: result.totalSteps,
|
|
2048
|
-
sessionId: result.sessionId,
|
|
2049
|
-
requirements: result.requirements,
|
|
2050
|
-
message: result.message,
|
|
2051
|
-
});
|
|
2052
|
-
}
|
|
2053
|
-
})
|
|
2054
|
-
);
|
|
2055
|
-
|
|
2056
|
-
// POST /api/v2/entities/:id/proxy - Proxy request (v2)
|
|
2057
|
-
router.route('/api/v2/entities/:id/proxy').post(
|
|
2058
|
-
catchAsyncError(async (req, res) => {
|
|
2059
|
-
const user = await authenticateUser.execute(req);
|
|
2060
|
-
const userId = user.getId();
|
|
2061
|
-
const entityId = req.params.id;
|
|
2062
|
-
|
|
2063
|
-
try {
|
|
2064
|
-
const proxyResponse =
|
|
2065
|
-
await executeProxyRequest.executeViaEntity(
|
|
2066
|
-
entityId,
|
|
2067
|
-
userId,
|
|
2068
|
-
req.body
|
|
2069
|
-
);
|
|
2070
|
-
|
|
2071
|
-
res.status(200).json(proxyResponse);
|
|
2072
|
-
} catch (error) {
|
|
2073
|
-
if (Boom.isBoom(error)) {
|
|
2074
|
-
const statusCode = error.output.statusCode;
|
|
2075
|
-
const errorData = error.data || {};
|
|
2076
|
-
|
|
2077
|
-
const errorResponse = {
|
|
2078
|
-
success: false,
|
|
2079
|
-
status: statusCode,
|
|
2080
|
-
error: {
|
|
2081
|
-
code:
|
|
2082
|
-
errorData.code ||
|
|
2083
|
-
_getErrorCodeFromStatus(statusCode),
|
|
2084
|
-
message:
|
|
2085
|
-
error.output.payload.message || error.message,
|
|
2086
|
-
details: errorData.details || null,
|
|
2087
|
-
},
|
|
2088
|
-
};
|
|
2089
|
-
|
|
2090
|
-
if (errorData.upstreamStatus) {
|
|
2091
|
-
errorResponse.error.upstreamStatus =
|
|
2092
|
-
errorData.upstreamStatus;
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
return res.status(statusCode).json(errorResponse);
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
res.status(500).json({
|
|
2099
|
-
success: false,
|
|
2100
|
-
status: 500,
|
|
2101
|
-
error: {
|
|
2102
|
-
code: 'INTERNAL_ERROR',
|
|
2103
|
-
message: 'An unexpected error occurred',
|
|
2104
|
-
details: null,
|
|
2105
|
-
},
|
|
2106
|
-
});
|
|
2107
|
-
}
|
|
2108
|
-
})
|
|
2109
|
-
);
|
|
2110
|
-
}
|
|
2111
|
-
|
|
2112
|
-
/**
|
|
2113
|
-
* Sets up credential-related routes for the integration router
|
|
2114
|
-
* @param {Object} router - Express router instance
|
|
2115
|
-
* @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Authentication use case
|
|
2116
|
-
* @param {Object} useCases - Credential use cases
|
|
2117
|
-
*/
|
|
2118
|
-
function setCredentialRoutes(router, authenticateUser, useCases) {
|
|
2119
|
-
const {
|
|
2120
|
-
listCredentialsForUser,
|
|
2121
|
-
getCredentialForUser,
|
|
2122
|
-
deleteCredentialForUser,
|
|
2123
|
-
reauthorizeCredential,
|
|
2124
|
-
getAuthorizationRequirements,
|
|
2125
|
-
moduleDefinitions,
|
|
2126
|
-
} = useCases;
|
|
2127
|
-
|
|
2128
|
-
/**
|
|
2129
|
-
* Sanitize credential object by removing sensitive data
|
|
2130
|
-
* @param {Object} credential - Credential object
|
|
2131
|
-
* @returns {Object} Sanitized credential
|
|
2132
|
-
*/
|
|
2133
|
-
function sanitizeCredential(credential) {
|
|
2134
|
-
const { data, ...safe } = credential;
|
|
2135
|
-
return safe;
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
// GET /api/credentials - List user's credentials
|
|
2139
|
-
router.route('/api/credentials').get(
|
|
2140
|
-
catchAsyncError(async (req, res) => {
|
|
2141
|
-
const user = await authenticateUser.execute(req);
|
|
2142
|
-
const userId = user.getId();
|
|
2143
|
-
|
|
2144
|
-
const credentials = await listCredentialsForUser.execute(userId);
|
|
2145
|
-
const sanitized = credentials.map(sanitizeCredential);
|
|
2146
|
-
|
|
2147
|
-
res.json({ credentials: sanitized });
|
|
2148
|
-
})
|
|
2149
|
-
);
|
|
2150
|
-
|
|
2151
|
-
// GET /api/credentials/:id - Get single credential
|
|
2152
|
-
router.route('/api/credentials/:id').get(
|
|
2153
|
-
catchAsyncError(async (req, res) => {
|
|
2154
|
-
const user = await authenticateUser.execute(req);
|
|
2155
|
-
const userId = user.getId();
|
|
2156
|
-
|
|
2157
|
-
const credential = await getCredentialForUser.execute(
|
|
2158
|
-
req.params.id,
|
|
2159
|
-
userId
|
|
2160
|
-
);
|
|
2161
|
-
|
|
2162
|
-
res.json(sanitizeCredential(credential));
|
|
2163
|
-
})
|
|
2164
|
-
);
|
|
2165
|
-
|
|
2166
|
-
// DELETE /api/credentials/:id - Delete credential
|
|
2167
|
-
router.route('/api/credentials/:id').delete(
|
|
2168
|
-
catchAsyncError(async (req, res) => {
|
|
2169
|
-
const user = await authenticateUser.execute(req);
|
|
2170
|
-
const userId = user.getId();
|
|
2171
|
-
|
|
2172
|
-
await deleteCredentialForUser.execute(req.params.id, userId);
|
|
2173
|
-
|
|
2174
|
-
res.json({ success: true });
|
|
2175
|
-
})
|
|
2176
|
-
);
|
|
2177
|
-
|
|
2178
|
-
// GET /api/credentials/:id/reauthorize - Get reauth requirements
|
|
2179
|
-
router.route('/api/credentials/:id/reauthorize').get(
|
|
2180
|
-
catchAsyncError(async (req, res) => {
|
|
2181
|
-
const user = await authenticateUser.execute(req);
|
|
2182
|
-
const userId = user.getId();
|
|
2183
|
-
|
|
2184
|
-
// Verify ownership
|
|
2185
|
-
const credential = await getCredentialForUser.execute(
|
|
2186
|
-
req.params.id,
|
|
2187
|
-
userId
|
|
2188
|
-
);
|
|
2189
|
-
|
|
2190
|
-
// Get authorization requirements for this credential's type
|
|
2191
|
-
const step = parseInt(req.query.step || '1', 10);
|
|
2192
|
-
const requirements = await getAuthorizationRequirements.execute(
|
|
2193
|
-
credential.type,
|
|
2194
|
-
step
|
|
2195
|
-
);
|
|
2196
|
-
|
|
2197
|
-
res.json(requirements);
|
|
2198
|
-
})
|
|
2199
|
-
);
|
|
2200
|
-
|
|
2201
|
-
// POST /api/credentials/:id/reauthorize - Submit reauth data
|
|
2202
|
-
router.route('/api/credentials/:id/reauthorize').post(
|
|
2203
|
-
catchAsyncError(async (req, res) => {
|
|
2204
|
-
const user = await authenticateUser.execute(req);
|
|
2205
|
-
const userId = user.getId();
|
|
2206
|
-
|
|
2207
|
-
const params = checkRequiredParams(req.body, ['data']);
|
|
2208
|
-
const step = parseInt(req.body.step || '1', 10);
|
|
2209
|
-
const sessionId = req.body.sessionId || null;
|
|
2210
|
-
|
|
2211
|
-
const result = await reauthorizeCredential.execute(
|
|
2212
|
-
req.params.id,
|
|
2213
|
-
userId,
|
|
2214
|
-
params.data,
|
|
2215
|
-
step,
|
|
2216
|
-
sessionId
|
|
2217
|
-
);
|
|
2218
|
-
|
|
2219
|
-
res.json(result);
|
|
2220
|
-
})
|
|
2221
|
-
);
|
|
2222
|
-
|
|
2223
|
-
// ========================================
|
|
2224
|
-
// V2 CREDENTIAL ROUTES
|
|
2225
|
-
// ========================================
|
|
2226
|
-
// These v2 routes are separate from v1 so v2 can evolve independently
|
|
2227
|
-
// while v1 remains frozen for backwards compatibility.
|
|
2228
|
-
|
|
2229
|
-
// GET /api/v2/credentials - v2: List user's credentials
|
|
2230
|
-
router.route('/api/v2/credentials').get(
|
|
2231
|
-
catchAsyncError(async (req, res) => {
|
|
2232
|
-
const user = await authenticateUser.execute(req);
|
|
2233
|
-
const userId = user.getId();
|
|
2234
|
-
|
|
2235
|
-
const credentials = await listCredentialsForUser.execute(userId);
|
|
2236
|
-
const sanitized = credentials.map(sanitizeCredential);
|
|
2237
|
-
|
|
2238
|
-
res.json({ credentials: sanitized });
|
|
2239
|
-
})
|
|
2240
|
-
);
|
|
2241
|
-
|
|
2242
|
-
// GET /api/v2/credentials/:id - v2: Get single credential
|
|
2243
|
-
router.route('/api/v2/credentials/:id').get(
|
|
2244
|
-
catchAsyncError(async (req, res) => {
|
|
2245
|
-
const user = await authenticateUser.execute(req);
|
|
2246
|
-
const userId = user.getId();
|
|
2247
|
-
|
|
2248
|
-
const credential = await getCredentialForUser.execute(
|
|
2249
|
-
req.params.id,
|
|
2250
|
-
userId
|
|
2251
|
-
);
|
|
2252
|
-
|
|
2253
|
-
res.json(sanitizeCredential(credential));
|
|
2254
|
-
})
|
|
2255
|
-
);
|
|
2256
|
-
|
|
2257
|
-
// DELETE /api/v2/credentials/:id - v2: Delete credential
|
|
2258
|
-
router.route('/api/v2/credentials/:id').delete(
|
|
2259
|
-
catchAsyncError(async (req, res) => {
|
|
2260
|
-
const user = await authenticateUser.execute(req);
|
|
2261
|
-
const userId = user.getId();
|
|
2262
|
-
|
|
2263
|
-
await deleteCredentialForUser.execute(req.params.id, userId);
|
|
2264
|
-
|
|
2265
|
-
res.json({ success: true });
|
|
2266
|
-
})
|
|
2267
|
-
);
|
|
2268
|
-
|
|
2269
|
-
// GET /api/v2/credentials/:id/reauthorize - v2: Get reauth requirements
|
|
2270
|
-
router.route('/api/v2/credentials/:id/reauthorize').get(
|
|
2271
|
-
catchAsyncError(async (req, res) => {
|
|
2272
|
-
const user = await authenticateUser.execute(req);
|
|
2273
|
-
const userId = user.getId();
|
|
2274
|
-
|
|
2275
|
-
// Verify ownership
|
|
2276
|
-
const credential = await getCredentialForUser.execute(
|
|
2277
|
-
req.params.id,
|
|
2278
|
-
userId
|
|
2279
|
-
);
|
|
2280
|
-
|
|
2281
|
-
// Get authorization requirements for this credential's type
|
|
2282
|
-
const step = parseInt(req.query.step || '1', 10);
|
|
2283
|
-
const requirements = await getAuthorizationRequirements.execute(
|
|
2284
|
-
credential.type,
|
|
2285
|
-
step
|
|
2286
|
-
);
|
|
2287
|
-
|
|
2288
|
-
res.json(requirements);
|
|
2289
|
-
})
|
|
2290
|
-
);
|
|
2291
|
-
|
|
2292
|
-
// POST /api/v2/credentials/:id/reauthorize - v2: Submit reauth data
|
|
2293
|
-
router.route('/api/v2/credentials/:id/reauthorize').post(
|
|
2294
|
-
catchAsyncError(async (req, res) => {
|
|
2295
|
-
const user = await authenticateUser.execute(req);
|
|
2296
|
-
const userId = user.getId();
|
|
2297
|
-
|
|
2298
|
-
const params = checkRequiredParams(req.body, ['data']);
|
|
2299
|
-
const step = parseInt(req.body.step || '1', 10);
|
|
2300
|
-
const sessionId = req.body.sessionId || null;
|
|
2301
|
-
|
|
2302
|
-
const result = await reauthorizeCredential.execute(
|
|
2303
|
-
req.params.id,
|
|
2304
|
-
userId,
|
|
2305
|
-
params.data,
|
|
2306
|
-
step,
|
|
2307
|
-
sessionId
|
|
2308
|
-
);
|
|
2309
|
-
|
|
2310
|
-
res.json(result);
|
|
2311
|
-
})
|
|
2312
|
-
);
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
/**
|
|
2316
|
-
* Helper function to map HTTP status codes to error codes
|
|
2317
|
-
* @private
|
|
2318
|
-
* @param {number} statusCode - HTTP status code
|
|
2319
|
-
* @returns {string} Error code
|
|
2320
|
-
*/
|
|
2321
|
-
function _getErrorCodeFromStatus(statusCode) {
|
|
2322
|
-
const statusMap = {
|
|
2323
|
-
400: 'INVALID_REQUEST',
|
|
2324
|
-
401: 'INVALID_AUTH',
|
|
2325
|
-
403: 'PERMISSION_DENIED',
|
|
2326
|
-
404: 'NOT_FOUND',
|
|
2327
|
-
429: 'RATE_LIMITED',
|
|
2328
|
-
500: 'INTERNAL_ERROR',
|
|
2329
|
-
502: 'NETWORK_ERROR',
|
|
2330
|
-
503: 'SERVICE_UNAVAILABLE',
|
|
2331
|
-
504: 'TIMEOUT',
|
|
2332
|
-
};
|
|
2333
|
-
|
|
2334
|
-
return statusMap[statusCode] || 'UNKNOWN_ERROR';
|
|
2335
664
|
}
|
|
2336
665
|
|
|
2337
666
|
module.exports = { createIntegrationRouter, checkRequiredParams };
|