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