@friggframework/core 2.0.0-next.5 → 2.0.0-next.51
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 +693 -0
- package/README.md +959 -50
- package/application/commands/README.md +421 -0
- package/application/commands/credential-commands.js +224 -0
- package/application/commands/entity-commands.js +315 -0
- package/application/commands/integration-commands.js +179 -0
- package/application/commands/user-commands.js +213 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +8 -21
- package/core/create-handler.js +2 -7
- package/credential/repositories/credential-repository-factory.js +47 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +307 -0
- package/credential/repositories/credential-repository-postgres.js +313 -0
- package/credential/repositories/credential-repository.js +302 -0
- package/credential/use-cases/get-credential-for-user.js +21 -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/encryption/README.md +684 -0
- package/database/encryption/encryption-schema-registry.js +141 -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 +25 -12
- package/database/models/WebsocketConnection.js +16 -10
- package/database/models/readme.md +1 -0
- package/database/prisma.js +222 -0
- package/database/repositories/health-check-repository-factory.js +43 -0
- package/database/repositories/health-check-repository-interface.js +87 -0
- package/database/repositories/health-check-repository-mongodb.js +91 -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 +137 -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 +400 -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/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 +22898 -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 +3982 -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 +362 -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 +25072 -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 +3982 -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 +345 -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 +180 -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 +256 -0
- package/handlers/routers/health.js +519 -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 +296 -54
- package/integrations/integration-router.js +381 -182
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-factory.js +50 -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-factory.js +44 -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-factory.js +46 -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/dummy-integration-class.js +83 -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 +93 -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-factory.js +33 -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/{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 +59 -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 +55 -0
- package/modules/use-cases/process-authorization-callback.js +122 -0
- package/modules/use-cases/refresh-entity-options.js +59 -0
- package/modules/use-cases/test-module-auth.js +55 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +82 -50
- package/prisma-mongodb/schema.prisma +362 -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/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +345 -0
- package/queues/queuer-util.js +28 -15
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-factory.js +38 -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-factory.js +33 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +212 -0
- package/token/repositories/token-repository-postgres.js +257 -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-factory.js +46 -0
- package/user/repositories/user-repository-interface.js +198 -0
- package/user/repositories/user-repository-mongo.js +291 -0
- package/user/repositories/user-repository-postgres.js +350 -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 +106 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +93 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +37 -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/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/api-key.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,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 };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { get } = require('../../assertions');
|
|
2
|
+
const { User } = require('../user');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Use case for creating an organization user.
|
|
6
|
+
* @class CreateOrganizationUser
|
|
7
|
+
*/
|
|
8
|
+
class CreateOrganizationUser {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new CreateOrganizationUser instance.
|
|
11
|
+
* @param {Object} params - Configuration parameters.
|
|
12
|
+
* @param {import('../user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
|
|
13
|
+
* @param {Object} params.userConfig - The user properties inside of the app definition.
|
|
14
|
+
*/
|
|
15
|
+
constructor({ userRepository, userConfig }) {
|
|
16
|
+
this.userRepository = userRepository;
|
|
17
|
+
this.userConfig = userConfig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Executes the use case.
|
|
22
|
+
* @async
|
|
23
|
+
* @param {Object} params - The parameters for creating the user.
|
|
24
|
+
* @returns {Promise<import('../user').User>} The newly created user object.
|
|
25
|
+
*/
|
|
26
|
+
async execute(params) {
|
|
27
|
+
const name = get(params, 'name');
|
|
28
|
+
const appOrgId = get(params, 'appOrgId');
|
|
29
|
+
|
|
30
|
+
const organizationUserData =
|
|
31
|
+
await this.userRepository.createOrganizationUser({
|
|
32
|
+
name,
|
|
33
|
+
appOrgId,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return new User(
|
|
37
|
+
null,
|
|
38
|
+
organizationUserData,
|
|
39
|
+
this.userConfig.usePassword,
|
|
40
|
+
this.userConfig.primary,
|
|
41
|
+
this.userConfig.individualUserRequired,
|
|
42
|
+
this.userConfig.organizationUserRequired
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { CreateOrganizationUser };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Use case for creating a token for a user ID.
|
|
5
|
+
* @class CreateTokenForUserId
|
|
6
|
+
*/
|
|
7
|
+
class CreateTokenForUserId {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new CreateTokenForUserId instance.
|
|
10
|
+
* @param {Object} params - Configuration parameters.
|
|
11
|
+
* @param {import('../user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
|
|
12
|
+
*/
|
|
13
|
+
constructor({ userRepository }) {
|
|
14
|
+
this.userRepository = userRepository;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Executes the use case.
|
|
19
|
+
* @async
|
|
20
|
+
* @param {string} userId - The ID of the user to create a token for.
|
|
21
|
+
* @param {number} minutes - The number of minutes until the token expires.
|
|
22
|
+
* @returns {Promise<string>} The user token.
|
|
23
|
+
*/
|
|
24
|
+
async execute(userId, minutes) {
|
|
25
|
+
const rawToken = crypto.randomBytes(20).toString('hex');
|
|
26
|
+
return this.userRepository.createToken(userId, rawToken, minutes);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { CreateTokenForUserId };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* STUB: Use case for retrieving a user from adopter-provided JWT token.
|
|
5
|
+
*
|
|
6
|
+
* This is a stub implementation for future JWT authentication support.
|
|
7
|
+
* When implemented, this will allow adopters to use their own JWT tokens
|
|
8
|
+
* for authentication instead of Frigg's native token system.
|
|
9
|
+
*
|
|
10
|
+
* FUTURE IMPLEMENTATION REQUIREMENTS:
|
|
11
|
+
* - Validate JWT signature using jwtConfig.secret from app definition
|
|
12
|
+
* - Support configurable signing algorithms (HS256, HS384, HS512, RS256, RS384, RS512)
|
|
13
|
+
* - Extract user identifiers from JWT claims based on jwtConfig.userIdClaim and jwtConfig.orgIdClaim
|
|
14
|
+
* - Find or create user based on extracted claim values
|
|
15
|
+
* - Handle token expiration and validation errors
|
|
16
|
+
* - Support refresh tokens (optional)
|
|
17
|
+
* - Validate user ID conflicts if both individual and org IDs present in JWT
|
|
18
|
+
*
|
|
19
|
+
* RECOMMENDED IMPLEMENTATION:
|
|
20
|
+
* - Use 'jsonwebtoken' package for JWT parsing and validation
|
|
21
|
+
* - Cache JWT public keys for RS* algorithms
|
|
22
|
+
* - Add comprehensive error handling for invalid tokens
|
|
23
|
+
* - Log authentication attempts for security auditing
|
|
24
|
+
*
|
|
25
|
+
* @todo Implement JWT validation with jsonwebtoken package
|
|
26
|
+
* @todo Add unit tests for JWT parsing and claim extraction
|
|
27
|
+
* @todo Document adopter JWT integration guide in Frigg docs
|
|
28
|
+
* @todo Add support for JWT refresh tokens
|
|
29
|
+
* @todo Implement JWT public key caching for RS* algorithms
|
|
30
|
+
*
|
|
31
|
+
* @class GetUserFromAdopterJwt
|
|
32
|
+
*/
|
|
33
|
+
class GetUserFromAdopterJwt {
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new GetUserFromAdopterJwt instance.
|
|
36
|
+
* @param {Object} params - Configuration parameters.
|
|
37
|
+
* @param {import('../repositories/user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
|
|
38
|
+
* @param {Object} params.userConfig - The user config in the app definition.
|
|
39
|
+
*/
|
|
40
|
+
constructor({ userRepository, userConfig }) {
|
|
41
|
+
this.userRepository = userRepository;
|
|
42
|
+
this.userConfig = userConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Executes the use case.
|
|
47
|
+
* @async
|
|
48
|
+
* @param {string} jwtToken - The JWT token from the Authorization header.
|
|
49
|
+
* @returns {Promise<import('../user').User>} The authenticated user object.
|
|
50
|
+
* @throws {Boom} 501 Not Implemented - This feature is not yet available.
|
|
51
|
+
*/
|
|
52
|
+
async execute(jwtToken) {
|
|
53
|
+
throw Boom.notImplemented(
|
|
54
|
+
'Adopter JWT authentication is not yet implemented. ' +
|
|
55
|
+
'This feature is planned for a future Frigg release. ' +
|
|
56
|
+
'Please use one of the supported authentication modes instead: ' +
|
|
57
|
+
'friggToken (native bearer token) or xFriggHeaders (backend-to-backend with x-frigg-appUserId/appOrgId headers).'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/* FUTURE IMPLEMENTATION PSEUDOCODE:
|
|
61
|
+
|
|
62
|
+
const jwt = require('jsonwebtoken');
|
|
63
|
+
|
|
64
|
+
// Validate JWT configuration exists
|
|
65
|
+
if (!this.userConfig.jwtConfig || !this.userConfig.jwtConfig.secret) {
|
|
66
|
+
throw Boom.badImplementation('JWT configuration is required when adopterJwt auth mode is enabled');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Verify and decode JWT
|
|
71
|
+
const decoded = jwt.verify(jwtToken, this.userConfig.jwtConfig.secret, {
|
|
72
|
+
algorithms: [this.userConfig.jwtConfig.algorithm || 'HS256']
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Extract user identifiers from claims
|
|
76
|
+
const appUserId = decoded[this.userConfig.jwtConfig.userIdClaim || 'sub'];
|
|
77
|
+
const appOrgId = decoded[this.userConfig.jwtConfig.orgIdClaim || 'org_id'];
|
|
78
|
+
|
|
79
|
+
// At least one identifier required
|
|
80
|
+
if (!appUserId && !appOrgId) {
|
|
81
|
+
throw Boom.badRequest('JWT must contain user or organization identifier claims');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Find existing users
|
|
85
|
+
let individualUserData = null;
|
|
86
|
+
let organizationUserData = null;
|
|
87
|
+
|
|
88
|
+
if (appUserId) {
|
|
89
|
+
individualUserData = await this.userRepository.findIndividualUserByAppUserId(appUserId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (appOrgId) {
|
|
93
|
+
organizationUserData = await this.userRepository.findOrganizationUserByAppOrgId(appOrgId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate no conflicts if both IDs present
|
|
97
|
+
if (appUserId && appOrgId && individualUserData && organizationUserData) {
|
|
98
|
+
const individualOrgId = individualUserData.organizationUser?.toString();
|
|
99
|
+
const expectedOrgId = organizationUserData.id?.toString();
|
|
100
|
+
|
|
101
|
+
if (individualOrgId !== expectedOrgId) {
|
|
102
|
+
throw Boom.badRequest(
|
|
103
|
+
'User ID mismatch: JWT claims refer to different users. ' +
|
|
104
|
+
'Individual and organization IDs must belong to the same user.'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Auto-create if not found
|
|
110
|
+
if (!individualUserData && !organizationUserData) {
|
|
111
|
+
if (appUserId) {
|
|
112
|
+
individualUserData = await this.userRepository.createIndividualUser({
|
|
113
|
+
appUserId,
|
|
114
|
+
username: `jwt-user-${appUserId}`,
|
|
115
|
+
email: decoded.email || `${appUserId}@jwt.local`,
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
organizationUserData = await this.userRepository.createOrganizationUser({
|
|
119
|
+
appOrgId,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return new User(
|
|
125
|
+
individualUserData,
|
|
126
|
+
organizationUserData,
|
|
127
|
+
this.userConfig.usePassword,
|
|
128
|
+
this.userConfig.primary,
|
|
129
|
+
this.userConfig.individualUserRequired,
|
|
130
|
+
this.userConfig.organizationUserRequired
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error.name === 'TokenExpiredError') {
|
|
135
|
+
throw Boom.unauthorized('JWT token has expired');
|
|
136
|
+
} else if (error.name === 'JsonWebTokenError') {
|
|
137
|
+
throw Boom.unauthorized('Invalid JWT token');
|
|
138
|
+
} else if (error.isBoom) {
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
throw Boom.unauthorized('JWT authentication failed');
|
|
142
|
+
}
|
|
143
|
+
*/
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { GetUserFromAdopterJwt };
|
|
148
|
+
|
|
149
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
const { User } = require('../user');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Use case for retrieving a user from a bearer token.
|
|
6
|
+
* @class GetUserFromBearerToken
|
|
7
|
+
*/
|
|
8
|
+
class GetUserFromBearerToken {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new GetUserFromBearerToken instance.
|
|
11
|
+
* @param {Object} params - Configuration parameters.
|
|
12
|
+
* @param {import('../user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
|
|
13
|
+
* @param {Object} params.userConfig - The user config in the app definition.
|
|
14
|
+
*/
|
|
15
|
+
constructor({ userRepository, userConfig }) {
|
|
16
|
+
this.userRepository = userRepository;
|
|
17
|
+
this.userConfig = userConfig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Executes the use case.
|
|
22
|
+
* @async
|
|
23
|
+
* @param {string} bearerToken - The bearer token from the authorization header.
|
|
24
|
+
* @returns {Promise<import('../user').User>} The authenticated user object.
|
|
25
|
+
* @throws {Boom} 401 Unauthorized if the token is missing, malformed, or invalid.
|
|
26
|
+
*/
|
|
27
|
+
async execute(bearerToken) {
|
|
28
|
+
if (!bearerToken) {
|
|
29
|
+
throw Boom.unauthorized('Missing Authorization Header');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const token = bearerToken.split(' ')[1]?.trim();
|
|
33
|
+
if (!token) {
|
|
34
|
+
throw Boom.unauthorized('Invalid Token Format');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sessionToken = await this.userRepository.getSessionToken(token);
|
|
38
|
+
|
|
39
|
+
if (!sessionToken) {
|
|
40
|
+
throw Boom.unauthorized('Session Token Not Found');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (this.userConfig.primary === 'organization') {
|
|
44
|
+
const organizationUserData = await this.userRepository.findOrganizationUserById(sessionToken.user);
|
|
45
|
+
|
|
46
|
+
if (!organizationUserData) {
|
|
47
|
+
throw Boom.unauthorized('Organization User Not Found');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new User(
|
|
51
|
+
null,
|
|
52
|
+
organizationUserData,
|
|
53
|
+
this.userConfig.usePassword,
|
|
54
|
+
this.userConfig.primary,
|
|
55
|
+
this.userConfig.individualUserRequired,
|
|
56
|
+
this.userConfig.organizationUserRequired
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const individualUserData = await this.userRepository.findIndividualUserById(sessionToken.user);
|
|
61
|
+
|
|
62
|
+
if (!individualUserData) {
|
|
63
|
+
throw Boom.unauthorized('Individual User Not Found');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return new User(
|
|
67
|
+
individualUserData,
|
|
68
|
+
null,
|
|
69
|
+
this.userConfig.usePassword,
|
|
70
|
+
this.userConfig.primary,
|
|
71
|
+
this.userConfig.individualUserRequired,
|
|
72
|
+
this.userConfig.organizationUserRequired
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { GetUserFromBearerToken };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
const { User } = require('../user');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Use case for retrieving or creating a user from x-frigg header identifiers.
|
|
6
|
+
* Supports backend-to-backend API communication using application user IDs.
|
|
7
|
+
*
|
|
8
|
+
* @class GetUserFromXFriggHeaders
|
|
9
|
+
*/
|
|
10
|
+
class GetUserFromXFriggHeaders {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new GetUserFromXFriggHeaders instance.
|
|
13
|
+
* @param {Object} params - Configuration parameters.
|
|
14
|
+
* @param {import('../repositories/user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
|
|
15
|
+
* @param {Object} params.userConfig - The user config in the app definition.
|
|
16
|
+
*/
|
|
17
|
+
constructor({ userRepository, userConfig }) {
|
|
18
|
+
this.userRepository = userRepository;
|
|
19
|
+
this.userConfig = userConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Executes the use case.
|
|
24
|
+
* @async
|
|
25
|
+
* @param {string} [appUserId] - The app user ID from x-frigg-appUserId header.
|
|
26
|
+
* @param {string} [appOrgId] - The app organization ID from x-frigg-appOrgId header.
|
|
27
|
+
* @returns {Promise<import('../user').User>} The authenticated user object.
|
|
28
|
+
* @throws {Boom} 400 Bad Request if neither ID is provided or if both IDs are provided but belong to different users.
|
|
29
|
+
*/
|
|
30
|
+
async execute(appUserId, appOrgId) {
|
|
31
|
+
// At least one header must be provided
|
|
32
|
+
if (!appUserId && !appOrgId) {
|
|
33
|
+
throw Boom.badRequest(
|
|
34
|
+
'At least one of x-frigg-appUserId or x-frigg-appOrgId headers is required for backend-to-backend authentication'
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Find users by both IDs if both are provided
|
|
39
|
+
let individualUserData = null;
|
|
40
|
+
let organizationUserData = null;
|
|
41
|
+
|
|
42
|
+
if (appUserId && this.userConfig.individualUserRequired !== false) {
|
|
43
|
+
individualUserData =
|
|
44
|
+
await this.userRepository.findIndividualUserByAppUserId(
|
|
45
|
+
appUserId
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (appOrgId && this.userConfig.organizationUserRequired) {
|
|
50
|
+
organizationUserData =
|
|
51
|
+
await this.userRepository.findOrganizationUserByAppOrgId(
|
|
52
|
+
appOrgId
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// VALIDATION: If both IDs provided and both users exist, verify they match
|
|
57
|
+
if (
|
|
58
|
+
appUserId &&
|
|
59
|
+
appOrgId &&
|
|
60
|
+
individualUserData &&
|
|
61
|
+
organizationUserData
|
|
62
|
+
) {
|
|
63
|
+
// Check if individual user is linked to the org user
|
|
64
|
+
const individualOrgId =
|
|
65
|
+
individualUserData.organizationUser?.toString();
|
|
66
|
+
const expectedOrgId = organizationUserData.id?.toString();
|
|
67
|
+
|
|
68
|
+
if (individualOrgId !== expectedOrgId) {
|
|
69
|
+
throw Boom.badRequest(
|
|
70
|
+
'User ID mismatch: x-frigg-appUserId and x-frigg-appOrgId refer to different users. ' +
|
|
71
|
+
'Provide only one identifier or ensure they belong to the same user.'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Auto-create user if not found
|
|
77
|
+
if (!individualUserData && !organizationUserData) {
|
|
78
|
+
if (appUserId) {
|
|
79
|
+
individualUserData =
|
|
80
|
+
await this.userRepository.createIndividualUser({
|
|
81
|
+
appUserId,
|
|
82
|
+
username: `app-user-${appUserId}`,
|
|
83
|
+
email: `${appUserId}@app.local`,
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
organizationUserData =
|
|
87
|
+
await this.userRepository.createOrganizationUser({
|
|
88
|
+
appOrgId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return new User(
|
|
94
|
+
individualUserData,
|
|
95
|
+
organizationUserData,
|
|
96
|
+
this.userConfig.usePassword,
|
|
97
|
+
this.userConfig.primary,
|
|
98
|
+
this.userConfig.individualUserRequired,
|
|
99
|
+
this.userConfig.organizationUserRequired
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { GetUserFromXFriggHeaders };
|
|
105
|
+
|
|
106
|
+
|