@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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
AuthorizationSession,
|
|
4
|
+
} = require('../domain/entities/AuthorizationSession');
|
|
5
|
+
const {
|
|
6
|
+
AuthorizationSessionRepositoryInterface,
|
|
7
|
+
} = require('./authorization-session-repository-interface');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MongoDB Authorization Session Repository Adapter
|
|
11
|
+
* Handles AuthorizationSession persistence operations for MongoDB via Prisma
|
|
12
|
+
*
|
|
13
|
+
* MongoDB-specific characteristics:
|
|
14
|
+
* - Uses String IDs (Prisma's default for MongoDB)
|
|
15
|
+
* - TTL index on expiresAt for automatic cleanup
|
|
16
|
+
* - No ID conversion needed (IDs are already strings)
|
|
17
|
+
*
|
|
18
|
+
* Schema Requirements (Prisma):
|
|
19
|
+
* ```prisma
|
|
20
|
+
* model AuthorizationSession {
|
|
21
|
+
* id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
22
|
+
* sessionId String @unique
|
|
23
|
+
* userId String
|
|
24
|
+
* entityType String
|
|
25
|
+
* currentStep Int @default(1)
|
|
26
|
+
* maxSteps Int
|
|
27
|
+
* stepData Json @default("{}")
|
|
28
|
+
* expiresAt DateTime
|
|
29
|
+
* completed Boolean @default(false)
|
|
30
|
+
* createdAt DateTime @default(now())
|
|
31
|
+
* updatedAt DateTime @updatedAt
|
|
32
|
+
*
|
|
33
|
+
* @@index([sessionId])
|
|
34
|
+
* @@index([userId, entityType])
|
|
35
|
+
* @@index([expiresAt])
|
|
36
|
+
* @@index([completed])
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
class AuthorizationSessionRepositoryMongo extends AuthorizationSessionRepositoryInterface {
|
|
41
|
+
constructor(prismaClient = prisma) {
|
|
42
|
+
super();
|
|
43
|
+
this.prisma = prismaClient; // Allow injection for testing
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a new authorization session
|
|
48
|
+
*
|
|
49
|
+
* @param {AuthorizationSession} session - Session entity to create
|
|
50
|
+
* @returns {Promise<AuthorizationSession>} Created session entity
|
|
51
|
+
*/
|
|
52
|
+
async create(session) {
|
|
53
|
+
const doc = await this.prisma.authorizationSession.create({
|
|
54
|
+
data: {
|
|
55
|
+
sessionId: session.sessionId,
|
|
56
|
+
userId: session.userId,
|
|
57
|
+
entityType: session.entityType,
|
|
58
|
+
currentStep: session.currentStep,
|
|
59
|
+
maxSteps: session.maxSteps,
|
|
60
|
+
stepData: session.stepData,
|
|
61
|
+
expiresAt: session.expiresAt,
|
|
62
|
+
completed: session.completed,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return this._toEntity(doc);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Find session by session ID
|
|
71
|
+
* Excludes expired sessions
|
|
72
|
+
*
|
|
73
|
+
* @param {string} sessionId - Unique session identifier
|
|
74
|
+
* @returns {Promise<AuthorizationSession|null>} Session entity or null
|
|
75
|
+
*/
|
|
76
|
+
async findBySessionId(sessionId) {
|
|
77
|
+
const doc = await this.prisma.authorizationSession.findFirst({
|
|
78
|
+
where: {
|
|
79
|
+
sessionId,
|
|
80
|
+
expiresAt: { gt: new Date() },
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return doc ? this._toEntity(doc) : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Find active session for user and entity type
|
|
89
|
+
* Returns most recent active session
|
|
90
|
+
*
|
|
91
|
+
* @param {string} userId - User ID
|
|
92
|
+
* @param {string} entityType - Entity type (module name)
|
|
93
|
+
* @returns {Promise<AuthorizationSession|null>} Session entity or null
|
|
94
|
+
*/
|
|
95
|
+
async findActiveSession(userId, entityType) {
|
|
96
|
+
const doc = await this.prisma.authorizationSession.findFirst({
|
|
97
|
+
where: {
|
|
98
|
+
userId,
|
|
99
|
+
entityType,
|
|
100
|
+
completed: false,
|
|
101
|
+
expiresAt: { gt: new Date() },
|
|
102
|
+
},
|
|
103
|
+
orderBy: { createdAt: 'desc' },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return doc ? this._toEntity(doc) : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Update existing session
|
|
111
|
+
*
|
|
112
|
+
* @param {AuthorizationSession} session - Session entity with updated data
|
|
113
|
+
* @returns {Promise<AuthorizationSession>} Updated session entity
|
|
114
|
+
*/
|
|
115
|
+
async update(session) {
|
|
116
|
+
const updated = await this.prisma.authorizationSession.update({
|
|
117
|
+
where: { sessionId: session.sessionId },
|
|
118
|
+
data: {
|
|
119
|
+
currentStep: session.currentStep,
|
|
120
|
+
stepData: session.stepData,
|
|
121
|
+
completed: session.completed,
|
|
122
|
+
updatedAt: new Date(),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return this._toEntity(updated);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Delete expired sessions (cleanup operation)
|
|
131
|
+
* Note: MongoDB TTL index handles automatic deletion, but this provides
|
|
132
|
+
* manual cleanup capability
|
|
133
|
+
*
|
|
134
|
+
* @returns {Promise<number>} Number of deleted sessions
|
|
135
|
+
*/
|
|
136
|
+
async deleteExpired() {
|
|
137
|
+
const result = await this.prisma.authorizationSession.deleteMany({
|
|
138
|
+
where: {
|
|
139
|
+
expiresAt: { lt: new Date() },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return result.count;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convert Prisma document to domain entity
|
|
148
|
+
*
|
|
149
|
+
* @private
|
|
150
|
+
* @param {Object} doc - Prisma document
|
|
151
|
+
* @returns {AuthorizationSession} Domain entity
|
|
152
|
+
*/
|
|
153
|
+
_toEntity(doc) {
|
|
154
|
+
return new AuthorizationSession({
|
|
155
|
+
sessionId: doc.sessionId,
|
|
156
|
+
userId: doc.userId,
|
|
157
|
+
entityType: doc.entityType,
|
|
158
|
+
currentStep: doc.currentStep,
|
|
159
|
+
maxSteps: doc.maxSteps,
|
|
160
|
+
stepData: doc.stepData,
|
|
161
|
+
expiresAt: doc.expiresAt,
|
|
162
|
+
completed: doc.completed,
|
|
163
|
+
createdAt: doc.createdAt,
|
|
164
|
+
updatedAt: doc.updatedAt,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { AuthorizationSessionRepositoryMongo };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
AuthorizationSession,
|
|
4
|
+
} = require('../domain/entities/AuthorizationSession');
|
|
5
|
+
const {
|
|
6
|
+
AuthorizationSessionRepositoryInterface,
|
|
7
|
+
} = require('./authorization-session-repository-interface');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PostgreSQL Authorization Session Repository Adapter
|
|
11
|
+
* Handles AuthorizationSession persistence operations for PostgreSQL via Prisma
|
|
12
|
+
*
|
|
13
|
+
* PostgreSQL-specific characteristics:
|
|
14
|
+
* - Uses Int IDs (auto-incrementing)
|
|
15
|
+
* - Indexes on sessionId, userId+entityType, expiresAt, completed
|
|
16
|
+
* - JSON/JSONB support for stepData
|
|
17
|
+
* - No automatic TTL (manual cleanup via deleteExpired)
|
|
18
|
+
*
|
|
19
|
+
* Schema Requirements (Prisma):
|
|
20
|
+
* ```prisma
|
|
21
|
+
* model AuthorizationSession {
|
|
22
|
+
* id Int @id @default(autoincrement())
|
|
23
|
+
* sessionId String @unique
|
|
24
|
+
* userId String
|
|
25
|
+
* entityType String
|
|
26
|
+
* currentStep Int @default(1)
|
|
27
|
+
* maxSteps Int
|
|
28
|
+
* stepData Json @default("{}")
|
|
29
|
+
* expiresAt DateTime
|
|
30
|
+
* completed Boolean @default(false)
|
|
31
|
+
* createdAt DateTime @default(now())
|
|
32
|
+
* updatedAt DateTime @updatedAt
|
|
33
|
+
*
|
|
34
|
+
* @@index([sessionId])
|
|
35
|
+
* @@index([userId, entityType])
|
|
36
|
+
* @@index([expiresAt])
|
|
37
|
+
* @@index([completed])
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
class AuthorizationSessionRepositoryPostgres extends AuthorizationSessionRepositoryInterface {
|
|
42
|
+
constructor(prismaClient = prisma) {
|
|
43
|
+
super();
|
|
44
|
+
this.prisma = prismaClient; // Allow injection for testing
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a new authorization session
|
|
49
|
+
*
|
|
50
|
+
* @param {AuthorizationSession} session - Session entity to create
|
|
51
|
+
* @returns {Promise<AuthorizationSession>} Created session entity
|
|
52
|
+
*/
|
|
53
|
+
async create(session) {
|
|
54
|
+
const created = await this.prisma.authorizationSession.create({
|
|
55
|
+
data: {
|
|
56
|
+
sessionId: session.sessionId,
|
|
57
|
+
userId: session.userId,
|
|
58
|
+
entityType: session.entityType,
|
|
59
|
+
currentStep: session.currentStep,
|
|
60
|
+
maxSteps: session.maxSteps,
|
|
61
|
+
stepData: session.stepData,
|
|
62
|
+
expiresAt: session.expiresAt,
|
|
63
|
+
completed: session.completed,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return this._toEntity(created);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find session by session ID
|
|
72
|
+
* Excludes expired sessions
|
|
73
|
+
*
|
|
74
|
+
* @param {string} sessionId - Unique session identifier
|
|
75
|
+
* @returns {Promise<AuthorizationSession|null>} Session entity or null
|
|
76
|
+
*/
|
|
77
|
+
async findBySessionId(sessionId) {
|
|
78
|
+
const record = await this.prisma.authorizationSession.findFirst({
|
|
79
|
+
where: {
|
|
80
|
+
sessionId,
|
|
81
|
+
expiresAt: { gt: new Date() },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return record ? this._toEntity(record) : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Find active session for user and entity type
|
|
90
|
+
* Returns most recent active session
|
|
91
|
+
*
|
|
92
|
+
* @param {string} userId - User ID
|
|
93
|
+
* @param {string} entityType - Entity type (module name)
|
|
94
|
+
* @returns {Promise<AuthorizationSession|null>} Session entity or null
|
|
95
|
+
*/
|
|
96
|
+
async findActiveSession(userId, entityType) {
|
|
97
|
+
const record = await this.prisma.authorizationSession.findFirst({
|
|
98
|
+
where: {
|
|
99
|
+
userId,
|
|
100
|
+
entityType,
|
|
101
|
+
completed: false,
|
|
102
|
+
expiresAt: { gt: new Date() },
|
|
103
|
+
},
|
|
104
|
+
orderBy: { createdAt: 'desc' },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return record ? this._toEntity(record) : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Update existing session
|
|
112
|
+
*
|
|
113
|
+
* @param {AuthorizationSession} session - Session entity with updated data
|
|
114
|
+
* @returns {Promise<AuthorizationSession>} Updated session entity
|
|
115
|
+
*/
|
|
116
|
+
async update(session) {
|
|
117
|
+
const updated = await this.prisma.authorizationSession.update({
|
|
118
|
+
where: { sessionId: session.sessionId },
|
|
119
|
+
data: {
|
|
120
|
+
currentStep: session.currentStep,
|
|
121
|
+
stepData: session.stepData,
|
|
122
|
+
completed: session.completed,
|
|
123
|
+
updatedAt: new Date(),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return this._toEntity(updated);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Delete expired sessions (cleanup operation)
|
|
132
|
+
* PostgreSQL doesn't have TTL indexes, so this must be called periodically
|
|
133
|
+
* Recommend running as cron job or scheduled task
|
|
134
|
+
*
|
|
135
|
+
* @returns {Promise<number>} Number of deleted sessions
|
|
136
|
+
*/
|
|
137
|
+
async deleteExpired() {
|
|
138
|
+
const result = await this.prisma.authorizationSession.deleteMany({
|
|
139
|
+
where: {
|
|
140
|
+
expiresAt: { lt: new Date() },
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return result.count;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Convert Prisma record to domain entity
|
|
149
|
+
*
|
|
150
|
+
* @private
|
|
151
|
+
* @param {Object} record - Prisma record
|
|
152
|
+
* @returns {AuthorizationSession} Domain entity
|
|
153
|
+
*/
|
|
154
|
+
_toEntity(record) {
|
|
155
|
+
return new AuthorizationSession({
|
|
156
|
+
sessionId: record.sessionId,
|
|
157
|
+
userId: record.userId,
|
|
158
|
+
entityType: record.entityType,
|
|
159
|
+
currentStep: record.currentStep,
|
|
160
|
+
maxSteps: record.maxSteps,
|
|
161
|
+
stepData: record.stepData,
|
|
162
|
+
expiresAt: record.expiresAt,
|
|
163
|
+
completed: record.completed,
|
|
164
|
+
createdAt: record.createdAt,
|
|
165
|
+
updatedAt: record.updatedAt,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { AuthorizationSessionRepositoryPostgres };
|
|
@@ -9,7 +9,9 @@ const {
|
|
|
9
9
|
deleteOne,
|
|
10
10
|
} = require('../../database/documentdb-utils');
|
|
11
11
|
const { ModuleRepositoryInterface } = require('./module-repository-interface');
|
|
12
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
DocumentDBEncryptionService,
|
|
14
|
+
} = require('../../database/documentdb-encryption-service');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Module/Entity repository for DocumentDB.
|
|
@@ -50,16 +52,34 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
50
52
|
}
|
|
51
53
|
const filter = { userId: objectId };
|
|
52
54
|
const docs = await findMany(this.prisma, 'Entity', filter);
|
|
53
|
-
const credentialMap = await this._fetchCredentialsBulk(
|
|
54
|
-
|
|
55
|
+
const credentialMap = await this._fetchCredentialsBulk(
|
|
56
|
+
docs.map((doc) => doc.credentialId)
|
|
57
|
+
);
|
|
58
|
+
return docs.map((doc) =>
|
|
59
|
+
this._mapEntity(
|
|
60
|
+
doc,
|
|
61
|
+
credentialMap.get(fromObjectId(doc.credentialId)) || null
|
|
62
|
+
)
|
|
63
|
+
);
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
async findEntitiesByIds(entitiesIds) {
|
|
58
|
-
const ids = (entitiesIds || [])
|
|
67
|
+
const ids = (entitiesIds || [])
|
|
68
|
+
.map((id) => toObjectId(id))
|
|
69
|
+
.filter(Boolean);
|
|
59
70
|
if (ids.length === 0) return [];
|
|
60
|
-
const docs = await findMany(this.prisma, 'Entity', {
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
const docs = await findMany(this.prisma, 'Entity', {
|
|
72
|
+
_id: { $in: ids },
|
|
73
|
+
});
|
|
74
|
+
const credentialMap = await this._fetchCredentialsBulk(
|
|
75
|
+
docs.map((doc) => doc.credentialId)
|
|
76
|
+
);
|
|
77
|
+
return docs.map((doc) =>
|
|
78
|
+
this._mapEntity(
|
|
79
|
+
doc,
|
|
80
|
+
credentialMap.get(fromObjectId(doc.credentialId)) || null
|
|
81
|
+
)
|
|
82
|
+
);
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
async findEntitiesByUserIdAndModuleName(userId, moduleName) {
|
|
@@ -72,8 +92,15 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
72
92
|
moduleName,
|
|
73
93
|
};
|
|
74
94
|
const docs = await findMany(this.prisma, 'Entity', filter);
|
|
75
|
-
const credentialMap = await this._fetchCredentialsBulk(
|
|
76
|
-
|
|
95
|
+
const credentialMap = await this._fetchCredentialsBulk(
|
|
96
|
+
docs.map((doc) => doc.credentialId)
|
|
97
|
+
);
|
|
98
|
+
return docs.map((doc) =>
|
|
99
|
+
this._mapEntity(
|
|
100
|
+
doc,
|
|
101
|
+
credentialMap.get(fromObjectId(doc.credentialId)) || null
|
|
102
|
+
)
|
|
103
|
+
);
|
|
77
104
|
}
|
|
78
105
|
|
|
79
106
|
async unsetCredential(entityId) {
|
|
@@ -174,7 +201,9 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
174
201
|
async deleteEntity(entityId) {
|
|
175
202
|
const objectId = toObjectId(entityId);
|
|
176
203
|
if (!objectId) return false;
|
|
177
|
-
const result = await deleteOne(this.prisma, 'Entity', {
|
|
204
|
+
const result = await deleteOne(this.prisma, 'Entity', {
|
|
205
|
+
_id: objectId,
|
|
206
|
+
});
|
|
178
207
|
const deleted = result?.n ?? 0;
|
|
179
208
|
return deleted > 0;
|
|
180
209
|
}
|
|
@@ -190,13 +219,17 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
190
219
|
|
|
191
220
|
// Use raw findOne to bypass Prisma encryption extension
|
|
192
221
|
const rawCredential = await findOne(this.prisma, 'Credential', {
|
|
193
|
-
_id: objectId
|
|
222
|
+
_id: objectId,
|
|
194
223
|
});
|
|
195
224
|
|
|
196
225
|
if (!rawCredential) return null;
|
|
197
226
|
|
|
198
227
|
// Decrypt sensitive fields using service
|
|
199
|
-
const decryptedCredential =
|
|
228
|
+
const decryptedCredential =
|
|
229
|
+
await this.encryptionService.decryptFields(
|
|
230
|
+
'Credential',
|
|
231
|
+
rawCredential
|
|
232
|
+
);
|
|
200
233
|
|
|
201
234
|
// Return in same format
|
|
202
235
|
const credential = {
|
|
@@ -206,12 +239,15 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
206
239
|
authIsValid: decryptedCredential.authIsValid ?? null,
|
|
207
240
|
createdAt: decryptedCredential.createdAt,
|
|
208
241
|
updatedAt: decryptedCredential.updatedAt,
|
|
209
|
-
data: decryptedCredential.data
|
|
242
|
+
data: decryptedCredential.data,
|
|
210
243
|
};
|
|
211
244
|
|
|
212
245
|
return this._convertCredentialIds(credential);
|
|
213
246
|
} catch (error) {
|
|
214
|
-
console.error(
|
|
247
|
+
console.error(
|
|
248
|
+
`Failed to fetch/decrypt credential ${id}:`,
|
|
249
|
+
error.message
|
|
250
|
+
);
|
|
215
251
|
// Return null instead of throwing to allow graceful degradation
|
|
216
252
|
// This repository is read-only (doesn't create/update credentials)
|
|
217
253
|
// Entities can still be loaded even if their credential is corrupted/unreadable
|
|
@@ -229,45 +265,55 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
229
265
|
|
|
230
266
|
try {
|
|
231
267
|
// Convert string IDs to ObjectIds for bulk query
|
|
232
|
-
const objectIds = ids.map(id => toObjectId(id)).filter(Boolean);
|
|
268
|
+
const objectIds = ids.map((id) => toObjectId(id)).filter(Boolean);
|
|
233
269
|
if (objectIds.length === 0) return new Map();
|
|
234
270
|
|
|
235
271
|
// Use raw findMany to bypass Prisma encryption extension
|
|
236
272
|
const rawCredentials = await findMany(this.prisma, 'Credential', {
|
|
237
|
-
_id: { $in: objectIds }
|
|
273
|
+
_id: { $in: objectIds },
|
|
238
274
|
});
|
|
239
275
|
|
|
240
276
|
// Decrypt all credentials in parallel
|
|
241
|
-
const decryptionPromises = rawCredentials.map(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
277
|
+
const decryptionPromises = rawCredentials.map(
|
|
278
|
+
async (rawCredential) => {
|
|
279
|
+
try {
|
|
280
|
+
// Decrypt sensitive fields using service
|
|
281
|
+
const decryptedCredential =
|
|
282
|
+
await this.encryptionService.decryptFields(
|
|
283
|
+
'Credential',
|
|
284
|
+
rawCredential
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Build credential object in same format as Prisma would return
|
|
288
|
+
const credential = {
|
|
289
|
+
id: fromObjectId(decryptedCredential._id),
|
|
290
|
+
userId: fromObjectId(decryptedCredential.userId),
|
|
291
|
+
externalId: decryptedCredential.externalId ?? null,
|
|
292
|
+
authIsValid:
|
|
293
|
+
decryptedCredential.authIsValid ?? null,
|
|
294
|
+
createdAt: decryptedCredential.createdAt,
|
|
295
|
+
updatedAt: decryptedCredential.updatedAt,
|
|
296
|
+
data: decryptedCredential.data,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
return this._convertCredentialIds(credential);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const credId = fromObjectId(rawCredential._id);
|
|
302
|
+
console.error(
|
|
303
|
+
`Failed to decrypt credential ${credId}:`,
|
|
304
|
+
error.message
|
|
305
|
+
);
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
262
308
|
}
|
|
263
|
-
|
|
309
|
+
);
|
|
264
310
|
|
|
265
311
|
// Wait for all decryptions to complete
|
|
266
312
|
const decryptedCredentials = await Promise.all(decryptionPromises);
|
|
267
313
|
|
|
268
314
|
// Build Map from results, filtering out nulls
|
|
269
315
|
const map = new Map();
|
|
270
|
-
decryptedCredentials.forEach(credential => {
|
|
316
|
+
decryptedCredentials.forEach((credential) => {
|
|
271
317
|
if (credential) {
|
|
272
318
|
map.set(credential.id, credential);
|
|
273
319
|
}
|
|
@@ -308,12 +354,15 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
308
354
|
if (userObj) query.userId = userObj;
|
|
309
355
|
}
|
|
310
356
|
if (filter.credential || filter.credentialId) {
|
|
311
|
-
const credObj = toObjectId(
|
|
357
|
+
const credObj = toObjectId(
|
|
358
|
+
filter.credential || filter.credentialId
|
|
359
|
+
);
|
|
312
360
|
if (credObj) query.credentialId = credObj;
|
|
313
361
|
}
|
|
314
362
|
if (filter.name) query.name = filter.name;
|
|
315
363
|
if (filter.moduleName) query.moduleName = filter.moduleName;
|
|
316
364
|
if (filter.externalId) query.externalId = filter.externalId;
|
|
365
|
+
if (filter.isGlobal !== undefined) query.isGlobal = filter.isGlobal;
|
|
317
366
|
return query;
|
|
318
367
|
}
|
|
319
368
|
|
|
@@ -332,4 +381,3 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
|
|
|
332
381
|
}
|
|
333
382
|
|
|
334
383
|
module.exports = { ModuleRepositoryDocumentDB };
|
|
335
|
-
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
const { ModuleRepositoryMongo } = require('./module-repository-mongo');
|
|
2
1
|
const { ModuleRepositoryPostgres } = require('./module-repository-postgres');
|
|
3
|
-
const {
|
|
4
|
-
ModuleRepositoryDocumentDB,
|
|
5
|
-
} = require('./module-repository-documentdb');
|
|
6
2
|
const config = require('../../database/config');
|
|
7
3
|
|
|
8
4
|
/**
|
|
@@ -16,12 +12,14 @@ function createModuleRepository() {
|
|
|
16
12
|
|
|
17
13
|
switch (dbType) {
|
|
18
14
|
case 'mongodb':
|
|
15
|
+
const { ModuleRepositoryMongo } = require('./module-repository-mongo');
|
|
19
16
|
return new ModuleRepositoryMongo();
|
|
20
17
|
|
|
21
18
|
case 'postgresql':
|
|
22
19
|
return new ModuleRepositoryPostgres();
|
|
23
20
|
|
|
24
21
|
case 'documentdb':
|
|
22
|
+
const { ModuleRepositoryDocumentDB } = require('./module-repository-documentdb');
|
|
25
23
|
return new ModuleRepositoryDocumentDB();
|
|
26
24
|
|
|
27
25
|
default:
|
|
@@ -34,7 +32,7 @@ function createModuleRepository() {
|
|
|
34
32
|
module.exports = {
|
|
35
33
|
createModuleRepository,
|
|
36
34
|
// Export adapters for direct testing
|
|
37
|
-
ModuleRepositoryMongo,
|
|
35
|
+
get ModuleRepositoryMongo() { return require('./module-repository-mongo').ModuleRepositoryMongo; },
|
|
38
36
|
ModuleRepositoryPostgres,
|
|
39
|
-
ModuleRepositoryDocumentDB,
|
|
37
|
+
get ModuleRepositoryDocumentDB() { return require('./module-repository-documentdb').ModuleRepositoryDocumentDB; },
|
|
40
38
|
};
|
|
@@ -91,6 +91,19 @@ class ModuleRepositoryInterface {
|
|
|
91
91
|
throw new Error('Method findEntity must be implemented by subclass');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Find entities matching filter criteria
|
|
96
|
+
*
|
|
97
|
+
* @param {Object} filter - Filter criteria
|
|
98
|
+
* @returns {Promise<Array>} Array of entity objects
|
|
99
|
+
* @abstract
|
|
100
|
+
*/
|
|
101
|
+
async findEntitiesBy(filter) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
'Method findEntitiesBy must be implemented by subclass'
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
/**
|
|
95
108
|
* Create a new entity
|
|
96
109
|
*
|