@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
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GetProcess Use Case
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Retrieves a process by ID with proper error handling.
|
|
5
5
|
* Simple use case that delegates to repository.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Design Philosophy:
|
|
8
8
|
* - Use cases provide consistent error handling
|
|
9
9
|
* - Business logic layer between controllers and repositories
|
|
10
10
|
* - Return null for not found vs throwing error (configurable)
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* @example
|
|
13
13
|
* const getProcess = new GetProcess({ processRepository });
|
|
14
14
|
* const process = await getProcess.execute(processId);
|
|
@@ -75,13 +75,12 @@ class GetProcess {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const processes = await Promise.all(
|
|
78
|
-
processIds.map(id => this.execute(id))
|
|
78
|
+
processIds.map((id) => this.execute(id))
|
|
79
79
|
);
|
|
80
80
|
|
|
81
81
|
// Filter out nulls (not found)
|
|
82
|
-
return processes.filter(p => p !== null);
|
|
82
|
+
return processes.filter((p) => p !== null);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
module.exports = { GetProcess };
|
|
87
|
-
|
|
@@ -6,6 +6,7 @@ const { CreateProcess } = require('./create-process');
|
|
|
6
6
|
const { UpdateProcessState } = require('./update-process-state');
|
|
7
7
|
const { UpdateProcessMetrics } = require('./update-process-metrics');
|
|
8
8
|
const { GetProcess } = require('./get-process');
|
|
9
|
+
const { ExecuteProxyRequest } = require('./execute-proxy-request');
|
|
9
10
|
|
|
10
11
|
module.exports = {
|
|
11
12
|
GetIntegrationsForUser,
|
|
@@ -16,4 +17,5 @@ module.exports = {
|
|
|
16
17
|
UpdateProcessState,
|
|
17
18
|
UpdateProcessMetrics,
|
|
18
19
|
GetProcess,
|
|
19
|
-
|
|
20
|
+
ExecuteProxyRequest,
|
|
21
|
+
};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
class LoadIntegrationContextUseCase {
|
|
2
|
-
constructor({
|
|
3
|
-
integrationRepository,
|
|
4
|
-
moduleRepository,
|
|
5
|
-
moduleFactory,
|
|
6
|
-
}) {
|
|
2
|
+
constructor({ integrationRepository, moduleRepository, moduleFactory }) {
|
|
7
3
|
if (!integrationRepository) {
|
|
8
4
|
throw new Error('integrationRepository is required');
|
|
9
5
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UpdateProcessState Use Case
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Updates the state of a process and optionally merges context updates.
|
|
5
5
|
* Handles state transitions in the process state machine.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Design Philosophy:
|
|
8
8
|
* - State transitions are explicit and tracked
|
|
9
9
|
* - Context updates are merged (not replaced) to preserve data
|
|
10
10
|
* - Repository handles persistence, use case handles business logic
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* State Machine (CRM Sync Example):
|
|
13
|
-
* INITIALIZING → FETCHING_TOTAL → QUEUING_PAGES → PROCESSING_BATCHES →
|
|
13
|
+
* INITIALIZING → FETCHING_TOTAL → QUEUING_PAGES → PROCESSING_BATCHES →
|
|
14
14
|
* COMPLETING → COMPLETED
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* Any state can transition to ERROR on failure.
|
|
17
|
-
*
|
|
17
|
+
*
|
|
18
18
|
* @example
|
|
19
19
|
* const updateProcessState = new UpdateProcessState({ processRepository });
|
|
20
20
|
* await updateProcessState.execute(processId, 'FETCHING_TOTAL', {
|
|
@@ -75,7 +75,10 @@ class UpdateProcessState {
|
|
|
75
75
|
|
|
76
76
|
// Persist updates
|
|
77
77
|
try {
|
|
78
|
-
const updatedProcess = await this.processRepository.update(
|
|
78
|
+
const updatedProcess = await this.processRepository.update(
|
|
79
|
+
processId,
|
|
80
|
+
updates
|
|
81
|
+
);
|
|
79
82
|
return updatedProcess;
|
|
80
83
|
} catch (error) {
|
|
81
84
|
throw new Error(`Failed to update process state: ${error.message}`);
|
|
@@ -116,4 +119,3 @@ class UpdateProcessState {
|
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
module.exports = { UpdateProcessState };
|
|
119
|
-
|
|
@@ -15,11 +15,14 @@ function mapIntegrationClassToIntegrationDTO(integration) {
|
|
|
15
15
|
version: integration.version,
|
|
16
16
|
messages: integration.messages,
|
|
17
17
|
userActions: integration.userActions,
|
|
18
|
-
options:
|
|
18
|
+
options:
|
|
19
|
+
integration.options ||
|
|
20
|
+
(typeof integration.getOptionDetails === 'function'
|
|
21
|
+
? integration.getOptionDetails()
|
|
22
|
+
: null),
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
|
|
23
26
|
const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
|
|
24
27
|
return [
|
|
25
28
|
...new Set(
|
|
@@ -34,4 +37,7 @@ const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
|
|
|
34
37
|
];
|
|
35
38
|
};
|
|
36
39
|
|
|
37
|
-
module.exports = {
|
|
40
|
+
module.exports = {
|
|
41
|
+
mapIntegrationClassToIntegrationDTO,
|
|
42
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
43
|
+
};
|
package/jest-setup.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const {globalSetup} = require('@friggframework/test');
|
|
1
|
+
const { globalSetup } = require('@friggframework/test');
|
|
2
2
|
module.exports = globalSetup;
|
package/jest-teardown.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const {globalTeardown} = require('@friggframework/test');
|
|
1
|
+
const { globalTeardown } = require('@friggframework/test');
|
|
2
2
|
module.exports = globalTeardown;
|
package/lambda/TimeoutCatcher.js
CHANGED
|
@@ -2,42 +2,44 @@ const isPositive = (n) => Number.isFinite(n) && n > 0;
|
|
|
2
2
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
3
|
|
|
4
4
|
class TimeoutCatcher {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
async watch() {
|
|
16
|
-
try {
|
|
17
|
-
await Promise.race([this.doWork(), this.exitBeforeTimeout()]);
|
|
18
|
-
return true;
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if (error.isSentinelTimeout) return false;
|
|
21
|
-
throw error;
|
|
5
|
+
constructor({ work, timeout, cleanUp = () => {}, cleanUpTime = 2_000 }) {
|
|
6
|
+
this.isFinished = false;
|
|
7
|
+
this.work = work;
|
|
8
|
+
this.cleanUp = cleanUp;
|
|
9
|
+
this.waitTime = timeout - cleanUpTime;
|
|
10
|
+
|
|
11
|
+
if (!isPositive(this.waitTime))
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Wait time was not a positive number of milliseconds'
|
|
14
|
+
);
|
|
22
15
|
}
|
|
23
|
-
}
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
async watch() {
|
|
18
|
+
try {
|
|
19
|
+
await Promise.race([this.doWork(), this.exitBeforeTimeout()]);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error.isSentinelTimeout) return false;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async doWork() {
|
|
28
|
+
await this.work();
|
|
29
|
+
this.isFinished = true;
|
|
30
|
+
}
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
async exitBeforeTimeout() {
|
|
33
|
+
await sleep(this.waitTime);
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if (!this.isFinished) {
|
|
36
|
+
await this.cleanUp();
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const error = new Error('Sentinel Timed Out');
|
|
39
|
+
error.isSentinelTimeout = true;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
|
-
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
module.exports = { TimeoutCatcher };
|
package/lambda/index.js
CHANGED
package/logs/index.js
CHANGED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthorizationSession Entity
|
|
3
|
+
* Domain entity for multi-step authorization workflows
|
|
4
|
+
*
|
|
5
|
+
* Manages state for authentication flows that require multiple steps,
|
|
6
|
+
* such as OTP verification, multi-factor authentication, or progressive
|
|
7
|
+
* credential collection.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const session = new AuthorizationSession({
|
|
11
|
+
* sessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
12
|
+
* userId: 'user123',
|
|
13
|
+
* entityType: 'nagaris',
|
|
14
|
+
* currentStep: 1,
|
|
15
|
+
* maxSteps: 2,
|
|
16
|
+
* stepData: { email: 'user@example.com' },
|
|
17
|
+
* expiresAt: new Date(Date.now() + 15 * 60 * 1000)
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
class AuthorizationSession {
|
|
21
|
+
/**
|
|
22
|
+
* Create an authorization session
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} params - Session parameters
|
|
25
|
+
* @param {string} params.sessionId - Unique session identifier (UUID)
|
|
26
|
+
* @param {string} params.userId - User ID who initiated the auth flow
|
|
27
|
+
* @param {string} params.entityType - Type of entity being authorized (module name)
|
|
28
|
+
* @param {number} [params.currentStep=1] - Current step in the auth flow
|
|
29
|
+
* @param {number} params.maxSteps - Total number of steps in the flow
|
|
30
|
+
* @param {Object} [params.stepData={}] - Accumulated data from previous steps
|
|
31
|
+
* @param {Date} params.expiresAt - Session expiration timestamp
|
|
32
|
+
* @param {boolean} [params.completed=false] - Whether auth flow is complete
|
|
33
|
+
* @param {Date} [params.createdAt=new Date()] - Session creation timestamp
|
|
34
|
+
* @param {Date} [params.updatedAt=new Date()] - Last update timestamp
|
|
35
|
+
*/
|
|
36
|
+
constructor({
|
|
37
|
+
sessionId,
|
|
38
|
+
userId,
|
|
39
|
+
entityType,
|
|
40
|
+
currentStep = 1,
|
|
41
|
+
maxSteps,
|
|
42
|
+
stepData = {},
|
|
43
|
+
expiresAt,
|
|
44
|
+
completed = false,
|
|
45
|
+
createdAt = new Date(),
|
|
46
|
+
updatedAt = new Date(),
|
|
47
|
+
}) {
|
|
48
|
+
this.sessionId = sessionId;
|
|
49
|
+
this.userId = userId;
|
|
50
|
+
this.entityType = entityType;
|
|
51
|
+
this.currentStep = currentStep;
|
|
52
|
+
this.maxSteps = maxSteps;
|
|
53
|
+
this.stepData = stepData;
|
|
54
|
+
this.expiresAt = expiresAt;
|
|
55
|
+
this.completed = completed;
|
|
56
|
+
this.createdAt = createdAt;
|
|
57
|
+
this.updatedAt = updatedAt;
|
|
58
|
+
|
|
59
|
+
this.validate();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate session state
|
|
64
|
+
*
|
|
65
|
+
* @throws {Error} If validation fails
|
|
66
|
+
*/
|
|
67
|
+
validate() {
|
|
68
|
+
if (!this.sessionId) {
|
|
69
|
+
throw new Error('Session ID is required');
|
|
70
|
+
}
|
|
71
|
+
if (!this.userId) {
|
|
72
|
+
throw new Error('User ID is required');
|
|
73
|
+
}
|
|
74
|
+
if (!this.entityType) {
|
|
75
|
+
throw new Error('Entity type is required');
|
|
76
|
+
}
|
|
77
|
+
if (this.currentStep < 1) {
|
|
78
|
+
throw new Error('Step must be >= 1');
|
|
79
|
+
}
|
|
80
|
+
if (this.currentStep > this.maxSteps) {
|
|
81
|
+
throw new Error('Current step cannot exceed max steps');
|
|
82
|
+
}
|
|
83
|
+
if (this.expiresAt < new Date()) {
|
|
84
|
+
throw new Error('Session has expired');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Advance to next step with new data
|
|
90
|
+
*
|
|
91
|
+
* @param {Object} newStepData - Data collected from current step
|
|
92
|
+
* @throws {Error} If session is already completed
|
|
93
|
+
*/
|
|
94
|
+
advanceStep(newStepData) {
|
|
95
|
+
if (this.completed) {
|
|
96
|
+
throw new Error('Cannot advance completed session');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.currentStep += 1;
|
|
100
|
+
this.stepData = { ...this.stepData, ...newStepData };
|
|
101
|
+
this.updatedAt = new Date();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Mark session as complete
|
|
106
|
+
*/
|
|
107
|
+
markComplete() {
|
|
108
|
+
this.completed = true;
|
|
109
|
+
this.updatedAt = new Date();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if session has expired
|
|
114
|
+
*
|
|
115
|
+
* @returns {boolean} True if session is expired
|
|
116
|
+
*/
|
|
117
|
+
isExpired() {
|
|
118
|
+
return this.expiresAt < new Date();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if session can advance to next step
|
|
123
|
+
*
|
|
124
|
+
* @returns {boolean} True if session can advance
|
|
125
|
+
*/
|
|
126
|
+
canAdvance() {
|
|
127
|
+
return !this.completed && this.currentStep < this.maxSteps;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = { AuthorizationSession };
|
package/modules/module.js
CHANGED
|
@@ -102,15 +102,10 @@ class Module extends Delegate {
|
|
|
102
102
|
this.api,
|
|
103
103
|
this.userId
|
|
104
104
|
);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
`[Frigg] No refresh_token in apiParams for module ${this.name}.`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
Object.assign(credentialDetails.details, apiParams);
|
|
105
|
+
Object.assign(
|
|
106
|
+
credentialDetails.details,
|
|
107
|
+
this.apiParamsFromCredential(this.api)
|
|
108
|
+
);
|
|
114
109
|
credentialDetails.details.authIsValid = true;
|
|
115
110
|
|
|
116
111
|
const persisted = await this.credentialRepository.upsertCredential(
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const {
|
|
2
|
+
AuthorizationSessionRepositoryPostgres,
|
|
3
|
+
} = require('./authorization-session-repository-postgres');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Authorization Session Repository Factory
|
|
7
|
+
* Creates the appropriate repository adapter based on database type
|
|
8
|
+
*
|
|
9
|
+
* Database-specific implementations:
|
|
10
|
+
* - MongoDB: Uses String IDs (ObjectId), TTL index for auto-cleanup
|
|
11
|
+
* - PostgreSQL: Uses Int IDs, manual cleanup via deleteExpired()
|
|
12
|
+
*
|
|
13
|
+
* All repository methods return AuthorizationSession domain entities,
|
|
14
|
+
* ensuring application layer consistency regardless of database type.
|
|
15
|
+
*
|
|
16
|
+
* Environment Configuration:
|
|
17
|
+
* - DB_TYPE=mongodb (default) - Uses MongoDB adapter
|
|
18
|
+
* - DB_TYPE=postgresql - Uses PostgreSQL adapter
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```javascript
|
|
22
|
+
* const repository = createAuthorizationSessionRepository();
|
|
23
|
+
* const session = await repository.findBySessionId(sessionId);
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} [prismaClient] - Optional Prisma client for testing
|
|
27
|
+
* @returns {AuthorizationSessionRepositoryInterface} Configured repository adapter
|
|
28
|
+
* @throws {Error} If DB_TYPE is not supported
|
|
29
|
+
*/
|
|
30
|
+
function createAuthorizationSessionRepository(prismaClient) {
|
|
31
|
+
const dbType = process.env.DB_TYPE || 'mongodb';
|
|
32
|
+
|
|
33
|
+
switch (dbType) {
|
|
34
|
+
case 'mongodb':
|
|
35
|
+
const { AuthorizationSessionRepositoryMongo } = require('./authorization-session-repository-mongo');
|
|
36
|
+
return new AuthorizationSessionRepositoryMongo(prismaClient);
|
|
37
|
+
|
|
38
|
+
case 'postgresql':
|
|
39
|
+
return new AuthorizationSessionRepositoryPostgres(prismaClient);
|
|
40
|
+
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Unsupported DB_TYPE: ${dbType}. Supported values: 'mongodb', 'postgresql'`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
createAuthorizationSessionRepository,
|
|
50
|
+
// Export adapters for direct testing
|
|
51
|
+
get AuthorizationSessionRepositoryMongo() { return require('./authorization-session-repository-mongo').AuthorizationSessionRepositoryMongo; },
|
|
52
|
+
AuthorizationSessionRepositoryPostgres,
|
|
53
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization Session Repository Interface
|
|
3
|
+
* Abstract base class defining the contract for AuthorizationSession persistence adapters
|
|
4
|
+
*
|
|
5
|
+
* This follows the Port in Hexagonal Architecture:
|
|
6
|
+
* - Domain layer depends on this abstraction
|
|
7
|
+
* - Concrete adapters (MongoDB, PostgreSQL) implement this interface
|
|
8
|
+
* - Use cases receive repositories via dependency injection
|
|
9
|
+
*
|
|
10
|
+
* @abstract
|
|
11
|
+
*/
|
|
12
|
+
class AuthorizationSessionRepositoryInterface {
|
|
13
|
+
/**
|
|
14
|
+
* Create a new authorization session
|
|
15
|
+
*
|
|
16
|
+
* @param {import('../domain/entities/AuthorizationSession').AuthorizationSession} session - Session entity to create
|
|
17
|
+
* @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession>} Created session
|
|
18
|
+
* @abstract
|
|
19
|
+
*/
|
|
20
|
+
async create(session) {
|
|
21
|
+
throw new Error('Method create must be implemented by subclass');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Find session by session ID
|
|
26
|
+
*
|
|
27
|
+
* @param {string} sessionId - Unique session identifier
|
|
28
|
+
* @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession|null>} Session entity or null if not found/expired
|
|
29
|
+
* @abstract
|
|
30
|
+
*/
|
|
31
|
+
async findBySessionId(sessionId) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'Method findBySessionId must be implemented by subclass'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find active session for user and entity type
|
|
39
|
+
* Returns the most recent active (non-completed, non-expired) session
|
|
40
|
+
*
|
|
41
|
+
* @param {string} userId - User ID
|
|
42
|
+
* @param {string} entityType - Entity type (module name)
|
|
43
|
+
* @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession|null>} Session entity or null if not found
|
|
44
|
+
* @abstract
|
|
45
|
+
*/
|
|
46
|
+
async findActiveSession(userId, entityType) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'Method findActiveSession must be implemented by subclass'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update existing session
|
|
54
|
+
*
|
|
55
|
+
* @param {import('../domain/entities/AuthorizationSession').AuthorizationSession} session - Session entity with updated data
|
|
56
|
+
* @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession>} Updated session
|
|
57
|
+
* @abstract
|
|
58
|
+
*/
|
|
59
|
+
async update(session) {
|
|
60
|
+
throw new Error('Method update must be implemented by subclass');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Delete expired sessions (cleanup operation)
|
|
65
|
+
* Should be called periodically to remove old sessions
|
|
66
|
+
*
|
|
67
|
+
* @returns {Promise<number>} Number of deleted sessions
|
|
68
|
+
* @abstract
|
|
69
|
+
*/
|
|
70
|
+
async deleteExpired() {
|
|
71
|
+
throw new Error('Method deleteExpired must be implemented by subclass');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { AuthorizationSessionRepositoryInterface };
|