@friggframework/core 2.0.0-next.6 → 2.0.0-next.61
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/CLAUDE.md +694 -0
- package/README.md +959 -50
- package/application/commands/README.md +451 -0
- package/application/commands/credential-commands.js +245 -0
- package/application/commands/entity-commands.js +336 -0
- package/application/commands/integration-commands.js +210 -0
- package/application/commands/user-commands.js +283 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +8 -21
- package/core/create-handler.js +14 -7
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +269 -0
- package/credential/repositories/credential-repository-postgres.js +291 -0
- package/credential/repositories/credential-repository.js +302 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +154 -0
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +268 -0
- package/database/encryption/field-encryption-service.js +226 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/prisma-encryption-extension.js +222 -0
- package/database/index.js +61 -21
- package/database/models/WebsocketConnection.js +16 -10
- package/database/models/readme.md +1 -0
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +134 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/health-check-repository.js +108 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +91 -0
- package/database/utils/mongodb-schema-init.js +106 -0
- package/database/utils/prisma-runner.js +477 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/encrypt/test-encrypt.js +0 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- package/errors/index.js +2 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22903 -0
- package/generated/prisma-mongodb/index.js +359 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
- package/generated/prisma-mongodb/runtime/binary.js +289 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +360 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +341 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +356 -0
- package/generated/prisma-postgresql/index-browser.js +338 -0
- package/generated/prisma-postgresql/index.d.ts +25077 -0
- package/generated/prisma-postgresql/index.js +381 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
- package/generated/prisma-postgresql/runtime/binary.js +289 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +343 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +363 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +56 -0
- package/handlers/backend-utils.js +186 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +15 -0
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +326 -0
- package/handlers/routers/health.js +516 -0
- package/handlers/routers/integration-defined-routers.js +45 -0
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/routers/user.js +63 -0
- package/handlers/routers/websocket.js +57 -0
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +352 -0
- package/handlers/workers/integration-defined-workers.js +27 -0
- package/index.js +77 -22
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +326 -55
- package/integrations/integration-router.js +374 -179
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-documentdb.js +210 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +127 -0
- package/integrations/repositories/integration-repository-mongo.js +303 -0
- package/integrations/repositories/integration-repository-postgres.js +352 -0
- package/integrations/repositories/process-repository-documentdb.js +243 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +90 -0
- package/integrations/repositories/process-repository-mongo.js +190 -0
- package/integrations/repositories/process-repository-postgres.js +217 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +105 -0
- package/integrations/tests/doubles/test-integration-repository.js +99 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/create-process.js +128 -0
- package/integrations/use-cases/delete-integration-for-user.js +101 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +87 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +201 -0
- package/integrations/use-cases/update-process-state.js +119 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/logs/logger.js +0 -4
- package/{module-plugin → modules}/entity.js +1 -1
- package/{module-plugin → modules}/index.js +0 -8
- package/modules/module-factory.js +56 -0
- package/modules/module.js +221 -0
- package/modules/repositories/module-repository-documentdb.js +307 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +377 -0
- package/modules/repositories/module-repository-postgres.js +426 -0
- package/modules/repositories/module-repository.js +316 -0
- package/modules/requester/api-key.js +52 -0
- package/{module-plugin → modules}/requester/requester.js +1 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +31 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +133 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +82 -50
- package/prisma-mongodb/schema.prisma +360 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +343 -0
- package/queues/queuer-util.js +27 -22
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/core/index.d.ts +2 -2
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +5 -59
- package/types/syncs/index.d.ts +0 -2
- package/user/repositories/user-repository-documentdb.js +441 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +201 -0
- package/user/repositories/user-repository-mongo.js +308 -0
- package/user/repositories/user-repository-postgres.js +360 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/mongo.js +0 -45
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
const bcrypt = require('bcryptjs');
|
|
2
|
+
const { prisma } = require('../../database/prisma');
|
|
3
|
+
const {
|
|
4
|
+
createTokenRepository,
|
|
5
|
+
} = require('../../token/repositories/token-repository-factory');
|
|
6
|
+
const { UserRepositoryInterface } = require('./user-repository-interface');
|
|
7
|
+
const { ClientSafeError } = require('../../errors');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PostgreSQL User Repository Adapter
|
|
11
|
+
* Handles user operations with discriminator pattern support
|
|
12
|
+
*
|
|
13
|
+
* PostgreSQL-specific characteristics:
|
|
14
|
+
* - Uses Int IDs with autoincrement
|
|
15
|
+
* - Requires ID conversion: String (app layer) ↔ Int (database)
|
|
16
|
+
* - All returned IDs are converted to strings for application layer consistency
|
|
17
|
+
*/
|
|
18
|
+
class UserRepositoryPostgres extends UserRepositoryInterface {
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
this.prisma = prisma;
|
|
22
|
+
this.tokenRepository = createTokenRepository();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert string ID to integer for PostgreSQL queries
|
|
27
|
+
* @private
|
|
28
|
+
* @param {string|number|null|undefined} id - ID to convert
|
|
29
|
+
* @returns {number|null|undefined} Integer ID or null/undefined
|
|
30
|
+
* @throws {Error} If ID cannot be converted to integer
|
|
31
|
+
*/
|
|
32
|
+
_convertId(id) {
|
|
33
|
+
if (id === null || id === undefined) return id;
|
|
34
|
+
const parsed = parseInt(id, 10);
|
|
35
|
+
if (isNaN(parsed)) {
|
|
36
|
+
throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Convert user object IDs to strings
|
|
43
|
+
* @private
|
|
44
|
+
* @param {Object|null} user - User object from database
|
|
45
|
+
* @returns {Object|null} User with string IDs
|
|
46
|
+
*/
|
|
47
|
+
_convertUserIds(user) {
|
|
48
|
+
if (!user) return user;
|
|
49
|
+
return {
|
|
50
|
+
...user,
|
|
51
|
+
id: user.id?.toString(),
|
|
52
|
+
organizationId: user.organizationId?.toString(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get session token from base64 buffer token
|
|
58
|
+
* Delegates to TokenRepository
|
|
59
|
+
*
|
|
60
|
+
* @param {string} token - Base64 buffer token
|
|
61
|
+
* @returns {Promise<Object>} Session token object with string IDs
|
|
62
|
+
*/
|
|
63
|
+
async getSessionToken(token) {
|
|
64
|
+
const jsonToken =
|
|
65
|
+
this.tokenRepository.getJSONTokenFromBase64BufferToken(token);
|
|
66
|
+
const sessionToken = await this.tokenRepository.validateAndGetToken(
|
|
67
|
+
jsonToken
|
|
68
|
+
);
|
|
69
|
+
return sessionToken;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Find organization user by ID
|
|
74
|
+
* Replaces: OrganizationUser.findById(userId)
|
|
75
|
+
*
|
|
76
|
+
* @param {string} userId - User ID (string from application layer)
|
|
77
|
+
* @returns {Promise<Object|null>} User object with string IDs or null
|
|
78
|
+
*/
|
|
79
|
+
async findOrganizationUserById(userId) {
|
|
80
|
+
const intId = this._convertId(userId);
|
|
81
|
+
const user = await this.prisma.user.findFirst({
|
|
82
|
+
where: {
|
|
83
|
+
id: intId,
|
|
84
|
+
type: 'ORGANIZATION',
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return this._convertUserIds(user);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find individual user by ID
|
|
92
|
+
* Replaces: IndividualUser.findById(userId)
|
|
93
|
+
*
|
|
94
|
+
* @param {string} userId - User ID (string from application layer)
|
|
95
|
+
* @returns {Promise<Object|null>} User object with string IDs or null
|
|
96
|
+
*/
|
|
97
|
+
async findIndividualUserById(userId) {
|
|
98
|
+
const intId = this._convertId(userId);
|
|
99
|
+
const user = await this.prisma.user.findFirst({
|
|
100
|
+
where: {
|
|
101
|
+
id: intId,
|
|
102
|
+
type: 'INDIVIDUAL',
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
return this._convertUserIds(user);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create token with expiration
|
|
110
|
+
* Delegates to TokenRepository
|
|
111
|
+
*
|
|
112
|
+
* @param {string} userId - User ID (string from application layer)
|
|
113
|
+
* @param {string} rawToken - Raw unhashed token
|
|
114
|
+
* @param {number} minutes - Minutes until expiration (default 120)
|
|
115
|
+
* @returns {Promise<string>} Base64 buffer token
|
|
116
|
+
*/
|
|
117
|
+
async createToken(userId, rawToken, minutes = 120) {
|
|
118
|
+
const createdToken = await this.tokenRepository.createTokenWithExpire(
|
|
119
|
+
userId,
|
|
120
|
+
rawToken,
|
|
121
|
+
minutes
|
|
122
|
+
);
|
|
123
|
+
return this.tokenRepository.createBase64BufferToken(
|
|
124
|
+
createdToken,
|
|
125
|
+
rawToken
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create individual user
|
|
131
|
+
* Replaces: IndividualUser.create(params)
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} params - User creation parameters (with string IDs from application layer)
|
|
134
|
+
* @param {string} [params.hashword] - Plain text password (will be bcrypt hashed automatically)
|
|
135
|
+
* @returns {Promise<Object>} Created user object with string IDs
|
|
136
|
+
*/
|
|
137
|
+
async createIndividualUser(params) {
|
|
138
|
+
const data = {
|
|
139
|
+
type: 'INDIVIDUAL',
|
|
140
|
+
email: params.email,
|
|
141
|
+
username: params.username,
|
|
142
|
+
appUserId: params.appUserId,
|
|
143
|
+
organizationId: this._convertId(
|
|
144
|
+
params.organization || params.organizationId
|
|
145
|
+
),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
params.hashword !== undefined &&
|
|
150
|
+
params.hashword !== null &&
|
|
151
|
+
params.hashword !== ''
|
|
152
|
+
) {
|
|
153
|
+
if (typeof params.hashword !== 'string') {
|
|
154
|
+
throw new ClientSafeError('Password must be a string', 400);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Prevent double-hashing: bcrypt hashes start with $2a$ or $2b$
|
|
158
|
+
if (params.hashword.startsWith('$2')) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
'Password appears to be already hashed. Pass plain text password only.'
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
data.hashword = await bcrypt.hash(params.hashword, 10);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const user = await this.prisma.user.create({ data });
|
|
168
|
+
return this._convertUserIds(user);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create organization user
|
|
173
|
+
* Replaces: OrganizationUser.create(params)
|
|
174
|
+
*
|
|
175
|
+
* @param {Object} params - Organization creation parameters
|
|
176
|
+
* @returns {Promise<Object>} Created organization object with string IDs
|
|
177
|
+
*/
|
|
178
|
+
async createOrganizationUser(params) {
|
|
179
|
+
const user = await this.prisma.user.create({
|
|
180
|
+
data: {
|
|
181
|
+
type: 'ORGANIZATION',
|
|
182
|
+
appOrgId: params.appOrgId,
|
|
183
|
+
name: params.name,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
return this._convertUserIds(user);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Find individual user by username
|
|
191
|
+
* Replaces: IndividualUser.findOne({ username })
|
|
192
|
+
*
|
|
193
|
+
* @param {string} username - Username to search for
|
|
194
|
+
* @returns {Promise<Object|null>} User object with string IDs or null
|
|
195
|
+
*/
|
|
196
|
+
async findIndividualUserByUsername(username) {
|
|
197
|
+
const user = await this.prisma.user.findFirst({
|
|
198
|
+
where: {
|
|
199
|
+
type: 'INDIVIDUAL',
|
|
200
|
+
username,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
return this._convertUserIds(user);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Find individual user by app user ID
|
|
208
|
+
* Replaces: IndividualUser.getUserByAppUserId(appUserId)
|
|
209
|
+
*
|
|
210
|
+
* @param {string} appUserId - App user ID to search for
|
|
211
|
+
* @returns {Promise<Object|null>} User object with string IDs or null
|
|
212
|
+
*/
|
|
213
|
+
async findIndividualUserByAppUserId(appUserId) {
|
|
214
|
+
const user = await this.prisma.user.findFirst({
|
|
215
|
+
where: {
|
|
216
|
+
type: 'INDIVIDUAL',
|
|
217
|
+
appUserId,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
return this._convertUserIds(user);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Find organization user by app org ID
|
|
225
|
+
* Replaces: OrganizationUser.getUserByAppOrgId(appOrgId)
|
|
226
|
+
*
|
|
227
|
+
* @param {string} appOrgId - App organization ID to search for
|
|
228
|
+
* @returns {Promise<Object|null>} User object with string IDs or null
|
|
229
|
+
*/
|
|
230
|
+
async findOrganizationUserByAppOrgId(appOrgId) {
|
|
231
|
+
const user = await this.prisma.user.findFirst({
|
|
232
|
+
where: {
|
|
233
|
+
type: 'ORGANIZATION',
|
|
234
|
+
appOrgId,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
return this._convertUserIds(user);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Find individual user by email
|
|
242
|
+
* @param {string} email - Email to search for
|
|
243
|
+
* @returns {Promise<Object|null>} User object with string IDs or null
|
|
244
|
+
*/
|
|
245
|
+
async findIndividualUserByEmail(email) {
|
|
246
|
+
const user = await this.prisma.user.findFirst({
|
|
247
|
+
where: {
|
|
248
|
+
type: 'INDIVIDUAL',
|
|
249
|
+
email,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
return this._convertUserIds(user);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Update individual user
|
|
257
|
+
* @param {string} userId - User ID (string from application layer)
|
|
258
|
+
* @param {Object} updates - Fields to update (with string IDs from application layer)
|
|
259
|
+
* @param {string} [updates.hashword] - Plain text password (will be bcrypt hashed automatically)
|
|
260
|
+
* @returns {Promise<Object>} Updated user object with string IDs
|
|
261
|
+
*/
|
|
262
|
+
async updateIndividualUser(userId, updates) {
|
|
263
|
+
const intId = this._convertId(userId);
|
|
264
|
+
|
|
265
|
+
const data = { ...updates };
|
|
266
|
+
|
|
267
|
+
if (data.organizationId !== undefined) {
|
|
268
|
+
data.organizationId = this._convertId(data.organizationId);
|
|
269
|
+
}
|
|
270
|
+
if (data.organization !== undefined) {
|
|
271
|
+
data.organizationId = this._convertId(data.organization);
|
|
272
|
+
delete data.organization;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (
|
|
276
|
+
data.hashword !== undefined &&
|
|
277
|
+
data.hashword !== null &&
|
|
278
|
+
data.hashword !== ''
|
|
279
|
+
) {
|
|
280
|
+
if (typeof data.hashword !== 'string') {
|
|
281
|
+
throw new ClientSafeError('Password must be a string', 400);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Prevent double-hashing: bcrypt hashes start with $2a$ or $2b$
|
|
285
|
+
if (data.hashword.startsWith('$2')) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
'Password appears to be already hashed. Pass plain text password only.'
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
data.hashword = await bcrypt.hash(data.hashword, 10);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const user = await this.prisma.user.update({
|
|
295
|
+
where: { id: intId },
|
|
296
|
+
data,
|
|
297
|
+
});
|
|
298
|
+
return this._convertUserIds(user);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Update organization user
|
|
303
|
+
* @param {string} userId - User ID (string from application layer)
|
|
304
|
+
* @param {Object} updates - Fields to update
|
|
305
|
+
* @returns {Promise<Object>} Updated user object with string IDs
|
|
306
|
+
*/
|
|
307
|
+
async updateOrganizationUser(userId, updates) {
|
|
308
|
+
const intId = this._convertId(userId);
|
|
309
|
+
const user = await this.prisma.user.update({
|
|
310
|
+
where: { id: intId },
|
|
311
|
+
data: updates,
|
|
312
|
+
});
|
|
313
|
+
return this._convertUserIds(user);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Delete user by ID
|
|
318
|
+
* @param {string} userId - User ID to delete (string from application layer)
|
|
319
|
+
* @returns {Promise<boolean>} True if deleted successfully
|
|
320
|
+
*/
|
|
321
|
+
async deleteUser(userId) {
|
|
322
|
+
try {
|
|
323
|
+
const intId = this._convertId(userId);
|
|
324
|
+
await this.prisma.user.delete({
|
|
325
|
+
where: { id: intId },
|
|
326
|
+
});
|
|
327
|
+
return true;
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (error.code === 'P2025') {
|
|
330
|
+
// Record not found
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Link an individual user to an organization user
|
|
339
|
+
* @param {string} individualUserId - Individual user ID (string from application layer)
|
|
340
|
+
* @param {string} organizationUserId - Organization user ID (string from application layer)
|
|
341
|
+
* @returns {Promise<Object>} Updated individual user with string IDs
|
|
342
|
+
*/
|
|
343
|
+
async linkIndividualToOrganization(individualUserId, organizationUserId) {
|
|
344
|
+
const intIndividualId = this._convertId(individualUserId);
|
|
345
|
+
const intOrganizationId = this._convertId(organizationUserId);
|
|
346
|
+
|
|
347
|
+
const user = await this.prisma.user.update({
|
|
348
|
+
where: {
|
|
349
|
+
id: intIndividualId,
|
|
350
|
+
type: 'INDIVIDUAL',
|
|
351
|
+
},
|
|
352
|
+
data: {
|
|
353
|
+
organizationId: intOrganizationId,
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
return this._convertUserIds(user);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
module.exports = { UserRepositoryPostgres };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
const { User } = require('../../user');
|
|
3
|
+
|
|
4
|
+
class TestUserRepository {
|
|
5
|
+
constructor({ userConfig }) {
|
|
6
|
+
this.individualUsers = new Map();
|
|
7
|
+
this.organizationUsers = new Map();
|
|
8
|
+
this.tokens = new Map();
|
|
9
|
+
this.userConfig = userConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async getSessionToken(token) {
|
|
13
|
+
return this.tokens.get(token);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async findOrganizationUserById(userId) {
|
|
17
|
+
return this.organizationUsers.get(userId);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async findIndividualUserById(userId) {
|
|
21
|
+
return this.individualUsers.get(userId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async createToken(userId, rawToken, minutes = 120) {
|
|
25
|
+
const token = `token-for-${userId}-for-${minutes}-mins`;
|
|
26
|
+
this.tokens.set(token, { user: userId, rawToken });
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async createIndividualUser(params) {
|
|
31
|
+
const individualUserData = { id: `individual-${Date.now()}`, ...params };
|
|
32
|
+
this.individualUsers.set(individualUserData.id, individualUserData);
|
|
33
|
+
return individualUserData;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async createOrganizationUser(params) {
|
|
37
|
+
const orgUserData = { ...params, id: `org-${Date.now()}` };
|
|
38
|
+
this.organizationUsers.set(orgUserData.id, orgUserData);
|
|
39
|
+
return orgUserData;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async findIndividualUserByUsername(username) {
|
|
43
|
+
for (const userDoc of this.individualUsers.values()) {
|
|
44
|
+
if (userDoc.username === username) {
|
|
45
|
+
return userDoc;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async findIndividualUserByAppUserId(appUserId) {
|
|
52
|
+
if (!appUserId) return null;
|
|
53
|
+
for (const userDoc of this.individualUsers.values()) {
|
|
54
|
+
if (userDoc.appUserId === appUserId) {
|
|
55
|
+
return userDoc;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async findOrganizationUserByAppOrgId(appOrgId) {
|
|
62
|
+
if (!appOrgId) return null;
|
|
63
|
+
for (const userDoc of this.organizationUsers.values()) {
|
|
64
|
+
if (userDoc.appOrgId === appOrgId) {
|
|
65
|
+
return userDoc;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { TestUserRepository };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Use case for authenticating a user using multiple authentication strategies.
|
|
5
|
+
*
|
|
6
|
+
* Supports three authentication modes in priority order:
|
|
7
|
+
* 1. Shared Secret (backend-to-backend with x-frigg-api-key + x-frigg headers)
|
|
8
|
+
* 2. Adopter JWT (custom JWT authentication)
|
|
9
|
+
* 3. Frigg Native Token (bearer token from /user/login)
|
|
10
|
+
*
|
|
11
|
+
* x-frigg-appUserId and x-frigg-appOrgId headers are automatically supported
|
|
12
|
+
* for user identification with any auth mode. When present with JWT or Frigg
|
|
13
|
+
* tokens, they are validated to match the authenticated user.
|
|
14
|
+
*
|
|
15
|
+
* @class AuthenticateUser
|
|
16
|
+
*/
|
|
17
|
+
class AuthenticateUser {
|
|
18
|
+
/**
|
|
19
|
+
* Creates a new AuthenticateUser instance.
|
|
20
|
+
* @param {Object} params - Configuration parameters.
|
|
21
|
+
* @param {import('./get-user-from-bearer-token').GetUserFromBearerToken} params.getUserFromBearerToken - Use case for bearer token auth.
|
|
22
|
+
* @param {import('./get-user-from-x-frigg-headers').GetUserFromXFriggHeaders} params.getUserFromXFriggHeaders - Use case for x-frigg header auth.
|
|
23
|
+
* @param {import('./get-user-from-adopter-jwt').GetUserFromAdopterJwt} params.getUserFromAdopterJwt - Use case for adopter JWT auth.
|
|
24
|
+
* @param {import('./authenticate-with-shared-secret').AuthenticateWithSharedSecret} params.authenticateWithSharedSecret - Use case for validating shared secret.
|
|
25
|
+
* @param {Object} params.userConfig - The user config in the app definition.
|
|
26
|
+
*/
|
|
27
|
+
constructor({
|
|
28
|
+
getUserFromBearerToken,
|
|
29
|
+
getUserFromXFriggHeaders,
|
|
30
|
+
getUserFromAdopterJwt,
|
|
31
|
+
authenticateWithSharedSecret,
|
|
32
|
+
userConfig,
|
|
33
|
+
}) {
|
|
34
|
+
this.getUserFromBearerToken = getUserFromBearerToken;
|
|
35
|
+
this.getUserFromXFriggHeaders = getUserFromXFriggHeaders;
|
|
36
|
+
this.getUserFromAdopterJwt = getUserFromAdopterJwt;
|
|
37
|
+
this.authenticateWithSharedSecret = authenticateWithSharedSecret;
|
|
38
|
+
this.userConfig = userConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Executes the use case.
|
|
43
|
+
* @async
|
|
44
|
+
* @param {Object} req - Express request object with headers.
|
|
45
|
+
* @returns {Promise<import('../user').User>} The authenticated user object.
|
|
46
|
+
* @throws {Boom} Unauthorized if no valid authentication provided.
|
|
47
|
+
* @throws {Boom} Forbidden if x-frigg headers don't match authenticated user.
|
|
48
|
+
*/
|
|
49
|
+
async execute(req) {
|
|
50
|
+
const authModes = this.userConfig.authModes || { friggToken: true };
|
|
51
|
+
const appUserId = req.headers['x-frigg-appuserid'];
|
|
52
|
+
const appOrgId = req.headers['x-frigg-apporgid'];
|
|
53
|
+
let user = null;
|
|
54
|
+
|
|
55
|
+
// Priority 1: Shared Secret (backend-to-backend with API key)
|
|
56
|
+
if (authModes.sharedSecret !== false) {
|
|
57
|
+
const apiKey = req.headers['x-frigg-api-key'];
|
|
58
|
+
if (apiKey) {
|
|
59
|
+
// Validate the API key (authentication)
|
|
60
|
+
await this.authenticateWithSharedSecret.execute(apiKey);
|
|
61
|
+
// Get user from x-frigg headers (authorization)
|
|
62
|
+
return await this.getUserFromXFriggHeaders.execute(
|
|
63
|
+
appUserId,
|
|
64
|
+
appOrgId
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Priority 2: Adopter JWT (if enabled)
|
|
70
|
+
if (
|
|
71
|
+
authModes.adopterJwt === true &&
|
|
72
|
+
req.headers.authorization?.startsWith('Bearer ')
|
|
73
|
+
) {
|
|
74
|
+
const token = req.headers.authorization.split(' ')[1];
|
|
75
|
+
// Detect JWT format (3 parts separated by dots)
|
|
76
|
+
if (token && token.split('.').length === 3) {
|
|
77
|
+
user = await this.getUserFromAdopterJwt.execute(token);
|
|
78
|
+
// Validate x-frigg headers match JWT claims if present
|
|
79
|
+
if (appUserId || appOrgId) {
|
|
80
|
+
this.validateUserMatch(user, appUserId, appOrgId);
|
|
81
|
+
}
|
|
82
|
+
return user;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Priority 3: Frigg native token (default)
|
|
87
|
+
if (authModes.friggToken !== false && req.headers.authorization) {
|
|
88
|
+
user = await this.getUserFromBearerToken.execute(
|
|
89
|
+
req.headers.authorization
|
|
90
|
+
);
|
|
91
|
+
// Validate x-frigg headers match token user if present
|
|
92
|
+
if (appUserId || appOrgId) {
|
|
93
|
+
this.validateUserMatch(user, appUserId, appOrgId);
|
|
94
|
+
}
|
|
95
|
+
return user;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw Boom.unauthorized('No valid authentication provided');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validates that x-frigg headers match authenticated user if provided.
|
|
103
|
+
* This ensures that when both authentication (via token/JWT) and
|
|
104
|
+
* x-frigg headers are present, they refer to the same user.
|
|
105
|
+
*
|
|
106
|
+
* @param {import('../user').User} user - The authenticated user
|
|
107
|
+
* @param {string} [appUserId] - The x-frigg-appuserid header value
|
|
108
|
+
* @param {string} [appOrgId] - The x-frigg-apporgid header value
|
|
109
|
+
* @throws {Boom} 403 Forbidden if headers don't match user
|
|
110
|
+
*/
|
|
111
|
+
validateUserMatch(user, appUserId, appOrgId) {
|
|
112
|
+
if (appUserId && user.getAppUserId() !== appUserId) {
|
|
113
|
+
throw Boom.forbidden(
|
|
114
|
+
'x-frigg-appuserid header does not match authenticated user'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (appOrgId && user.getAppOrgId() !== appOrgId) {
|
|
118
|
+
throw Boom.forbidden(
|
|
119
|
+
'x-frigg-apporgid header does not match authenticated user'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { AuthenticateUser };
|
|
126
|
+
|
|
127
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Use case for authenticating requests with shared secret API key.
|
|
5
|
+
* This use case ONLY validates the authenticity of the request via API key.
|
|
6
|
+
* It does NOT retrieve user data - that's handled by GetUserFromXFriggHeaders.
|
|
7
|
+
*
|
|
8
|
+
* Used for backend-to-backend communication where the secret proves
|
|
9
|
+
* the request is legitimate, but user identification comes from x-frigg headers.
|
|
10
|
+
*
|
|
11
|
+
* @class AuthenticateWithSharedSecret
|
|
12
|
+
*/
|
|
13
|
+
class AuthenticateWithSharedSecret {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new AuthenticateWithSharedSecret instance.
|
|
16
|
+
* @param {Object} params - Configuration parameters (none needed currently, but kept for consistency).
|
|
17
|
+
*/
|
|
18
|
+
constructor() {
|
|
19
|
+
// No dependencies needed - just validates against env var
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validates the provided shared secret against FRIGG_API_KEY.
|
|
24
|
+
* @async
|
|
25
|
+
* @param {string} providedSecret - Secret from x-frigg-api-key header
|
|
26
|
+
* @returns {Promise<boolean>} True if valid (or throws error if invalid)
|
|
27
|
+
* @throws {Boom} 500 if FRIGG_API_KEY not configured
|
|
28
|
+
* @throws {Boom} 401 if provided secret doesn't match
|
|
29
|
+
*/
|
|
30
|
+
async execute(providedSecret) {
|
|
31
|
+
// Validate secret
|
|
32
|
+
const expectedSecret = process.env.FRIGG_API_KEY;
|
|
33
|
+
if (!expectedSecret) {
|
|
34
|
+
throw Boom.badImplementation(
|
|
35
|
+
'FRIGG_API_KEY environment variable is not configured. ' +
|
|
36
|
+
'Set FRIGG_API_KEY to enable shared secret authentication.'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!providedSecret || providedSecret !== expectedSecret) {
|
|
41
|
+
throw Boom.unauthorized('Invalid API key');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { AuthenticateWithSharedSecret };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { get } = require('../../assertions');
|
|
2
|
+
const Boom = require('@hapi/boom');
|
|
3
|
+
const { User } = require('../user');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Use case for creating an individual user.
|
|
7
|
+
* @class CreateIndividualUser
|
|
8
|
+
*/
|
|
9
|
+
class CreateIndividualUser {
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new CreateIndividualUser instance.
|
|
12
|
+
* @param {Object} params - Configuration parameters.
|
|
13
|
+
* @param {import('../user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
|
|
14
|
+
* @param {Object} params.userConfig - The user properties inside of the app definition.
|
|
15
|
+
*/
|
|
16
|
+
constructor({ userRepository, userConfig }) {
|
|
17
|
+
this.userRepository = userRepository;
|
|
18
|
+
this.userConfig = userConfig;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Executes the use case.
|
|
23
|
+
* @async
|
|
24
|
+
* @param {Object} params - The parameters for creating the user.
|
|
25
|
+
* @returns {Promise<import('../user').User>} The newly created user object.
|
|
26
|
+
*/
|
|
27
|
+
async execute(params) {
|
|
28
|
+
let hashword;
|
|
29
|
+
if (this.userConfig.usePassword) {
|
|
30
|
+
hashword = get(params, 'password');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const email = get(params, 'email', null);
|
|
34
|
+
const username = get(params, 'username', null);
|
|
35
|
+
if (!email && !username) {
|
|
36
|
+
throw Boom.badRequest('email or username is required');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const appUserId = get(params, 'appUserId', null);
|
|
40
|
+
const organizationUserId = get(params, 'organizationUserId', null);
|
|
41
|
+
|
|
42
|
+
const individualUserData = await this.userRepository.createIndividualUser({
|
|
43
|
+
email,
|
|
44
|
+
username,
|
|
45
|
+
hashword,
|
|
46
|
+
appUserId,
|
|
47
|
+
organizationUser: organizationUserId,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return new User(
|
|
51
|
+
individualUserData,
|
|
52
|
+
null,
|
|
53
|
+
this.userConfig.usePassword,
|
|
54
|
+
this.userConfig.primary,
|
|
55
|
+
this.userConfig.individualUserRequired,
|
|
56
|
+
this.userConfig.organizationUserRequired
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { CreateIndividualUser };
|