@friggframework/core 2.0.0--canary.498.056db99.0 → 2.0.0--canary.498.701403a.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/integrations/integration-router.js +4 -8
- package/modules/use-cases/get-entity-options-by-id.js +17 -5
- package/modules/use-cases/get-module.js +21 -2
- package/modules/use-cases/refresh-entity-options.js +18 -5
- package/modules/use-cases/test-module-auth.js +19 -2
- package/package.json +5 -5
- package/user/repositories/user-repository-documentdb.js +20 -0
- package/user/repositories/user-repository-interface.js +14 -0
- package/user/repositories/user-repository-mongo.js +18 -0
- package/user/user.js +32 -0
|
@@ -601,11 +601,10 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
601
601
|
router.route('/api/entities/:entityId/test-auth').get(
|
|
602
602
|
catchAsyncError(async (req, res) => {
|
|
603
603
|
const user = await authenticateUser.execute(req);
|
|
604
|
-
const userId = user.getId();
|
|
605
604
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
606
605
|
const testAuthResponse = await testModuleAuth.execute(
|
|
607
606
|
params.entityId,
|
|
608
|
-
|
|
607
|
+
user // Pass User object for proper validation
|
|
609
608
|
);
|
|
610
609
|
|
|
611
610
|
if (!testAuthResponse) {
|
|
@@ -628,9 +627,8 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
628
627
|
router.route('/api/entities/:entityId').get(
|
|
629
628
|
catchAsyncError(async (req, res) => {
|
|
630
629
|
const user = await authenticateUser.execute(req);
|
|
631
|
-
const userId = user.getId();
|
|
632
630
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
633
|
-
const module = await getModule.execute(params.entityId,
|
|
631
|
+
const module = await getModule.execute(params.entityId, user); // Pass User object
|
|
634
632
|
|
|
635
633
|
res.json(module);
|
|
636
634
|
})
|
|
@@ -639,12 +637,11 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
639
637
|
router.route('/api/entities/:entityId/options').post(
|
|
640
638
|
catchAsyncError(async (req, res) => {
|
|
641
639
|
const user = await authenticateUser.execute(req);
|
|
642
|
-
const userId = user.getId();
|
|
643
640
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
644
641
|
|
|
645
642
|
const entityOptions = await getEntityOptionsById.execute(
|
|
646
643
|
params.entityId,
|
|
647
|
-
|
|
644
|
+
user // Pass User object
|
|
648
645
|
);
|
|
649
646
|
|
|
650
647
|
res.json(entityOptions);
|
|
@@ -654,11 +651,10 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
654
651
|
router.route('/api/entities/:entityId/options/refresh').post(
|
|
655
652
|
catchAsyncError(async (req, res) => {
|
|
656
653
|
const user = await authenticateUser.execute(req);
|
|
657
|
-
const userId = user.getId();
|
|
658
654
|
const params = checkRequiredParams(req.params, ['entityId']);
|
|
659
655
|
const updatedOptions = await refreshEntityOptions.execute(
|
|
660
656
|
params.entityId,
|
|
661
|
-
|
|
657
|
+
user, // Pass User object
|
|
662
658
|
req.body
|
|
663
659
|
);
|
|
664
660
|
|
|
@@ -12,11 +12,18 @@ class GetEntityOptionsById {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Retrieve
|
|
16
|
-
*
|
|
17
|
-
* @param {string} entityId
|
|
15
|
+
* Retrieve entity options for a given entity
|
|
16
|
+
*
|
|
17
|
+
* @param {string|number} entityId - Entity ID to retrieve options for
|
|
18
|
+
* @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
|
|
19
|
+
* @returns {Promise<Object>} Entity options
|
|
18
20
|
*/
|
|
19
|
-
async execute(entityId,
|
|
21
|
+
async execute(entityId, userIdOrUser) {
|
|
22
|
+
// Support both userId (backward compatible) and User object (new pattern)
|
|
23
|
+
const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
|
|
24
|
+
? userIdOrUser.getId()
|
|
25
|
+
: userIdOrUser;
|
|
26
|
+
|
|
20
27
|
const entity = await this.moduleRepository.findEntityById(
|
|
21
28
|
entityId,
|
|
22
29
|
userId
|
|
@@ -26,7 +33,12 @@ class GetEntityOptionsById {
|
|
|
26
33
|
throw new Error(`Entity ${entityId} not found`);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
// Validate entity ownership
|
|
37
|
+
const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
|
|
38
|
+
? userIdOrUser.ownsUserId(entity.userId)
|
|
39
|
+
: entity.userId?.toString() === userId?.toString();
|
|
40
|
+
|
|
41
|
+
if (!isOwned) {
|
|
30
42
|
throw new Error(
|
|
31
43
|
`Entity ${entityId} does not belong to user ${userId}`
|
|
32
44
|
);
|
|
@@ -6,7 +6,19 @@ class GetModule {
|
|
|
6
6
|
this.moduleDefinitions = moduleDefinitions;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Get module instance for an entity
|
|
11
|
+
*
|
|
12
|
+
* @param {string|number} entityId - Entity ID to retrieve
|
|
13
|
+
* @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
|
|
14
|
+
* @returns {Promise<Object>} Module details
|
|
15
|
+
*/
|
|
16
|
+
async execute(entityId, userIdOrUser) {
|
|
17
|
+
// Support both userId (backward compatible) and User object (new pattern)
|
|
18
|
+
const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
|
|
19
|
+
? userIdOrUser.getId()
|
|
20
|
+
: userIdOrUser;
|
|
21
|
+
|
|
10
22
|
const entity = await this.moduleRepository.findEntityById(
|
|
11
23
|
entityId,
|
|
12
24
|
userId
|
|
@@ -16,7 +28,14 @@ class GetModule {
|
|
|
16
28
|
throw new Error(`Entity ${entityId} not found`);
|
|
17
29
|
}
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
// Validate entity ownership
|
|
32
|
+
// If User object provided, use ownsUserId to check linked users
|
|
33
|
+
// Otherwise fall back to simple equality check
|
|
34
|
+
const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
|
|
35
|
+
? userIdOrUser.ownsUserId(entity.userId)
|
|
36
|
+
: entity.userId?.toString() === userId?.toString();
|
|
37
|
+
|
|
38
|
+
if (!isOwned) {
|
|
20
39
|
throw new Error(
|
|
21
40
|
`Entity ${entityId} does not belong to user ${userId}`
|
|
22
41
|
);
|
|
@@ -12,11 +12,19 @@ class RefreshEntityOptions {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @param {string} entityId
|
|
15
|
+
* Refresh entity options for a given entity
|
|
16
|
+
*
|
|
17
|
+
* @param {string|number} entityId - Entity ID to refresh
|
|
18
|
+
* @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
|
|
19
|
+
* @param {Object} options - Refresh options
|
|
20
|
+
* @returns {Promise<Object>} Updated entity options
|
|
18
21
|
*/
|
|
19
|
-
async execute(entityId,
|
|
22
|
+
async execute(entityId, userIdOrUser, options) {
|
|
23
|
+
// Support both userId (backward compatible) and User object (new pattern)
|
|
24
|
+
const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
|
|
25
|
+
? userIdOrUser.getId()
|
|
26
|
+
: userIdOrUser;
|
|
27
|
+
|
|
20
28
|
const entity = await this.moduleRepository.findEntityById(
|
|
21
29
|
entityId,
|
|
22
30
|
userId
|
|
@@ -26,7 +34,12 @@ class RefreshEntityOptions {
|
|
|
26
34
|
throw new Error(`Entity ${entityId} not found`);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
// Validate entity ownership
|
|
38
|
+
const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
|
|
39
|
+
? userIdOrUser.ownsUserId(entity.userId)
|
|
40
|
+
: entity.userId?.toString() === userId?.toString();
|
|
41
|
+
|
|
42
|
+
if (!isOwned) {
|
|
30
43
|
throw new Error(
|
|
31
44
|
`Entity ${entityId} does not belong to user ${userId}`
|
|
32
45
|
);
|
|
@@ -11,7 +11,19 @@ class TestModuleAuth {
|
|
|
11
11
|
this.moduleDefinitions = moduleDefinitions;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Test authentication for a module entity
|
|
16
|
+
*
|
|
17
|
+
* @param {string|number} entityId - Entity ID to test
|
|
18
|
+
* @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
|
|
19
|
+
* @returns {Promise<boolean>} Authentication test result
|
|
20
|
+
*/
|
|
21
|
+
async execute(entityId, userIdOrUser) {
|
|
22
|
+
// Support both userId (backward compatible) and User object (new pattern)
|
|
23
|
+
const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
|
|
24
|
+
? userIdOrUser.getId()
|
|
25
|
+
: userIdOrUser;
|
|
26
|
+
|
|
15
27
|
const entity = await this.moduleRepository.findEntityById(
|
|
16
28
|
entityId,
|
|
17
29
|
userId
|
|
@@ -21,7 +33,12 @@ class TestModuleAuth {
|
|
|
21
33
|
throw new Error(`Entity ${entityId} not found`);
|
|
22
34
|
}
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
// Validate entity ownership
|
|
37
|
+
const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
|
|
38
|
+
? userIdOrUser.ownsUserId(entity.userId)
|
|
39
|
+
: entity.userId?.toString() === userId?.toString();
|
|
40
|
+
|
|
41
|
+
if (!isOwned) {
|
|
25
42
|
throw new Error(
|
|
26
43
|
`Entity ${entityId} does not belong to user ${userId}`
|
|
27
44
|
);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.498.
|
|
4
|
+
"version": "2.0.0--canary.498.701403a.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0--canary.498.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0--canary.498.
|
|
43
|
-
"@friggframework/test": "2.0.0--canary.498.
|
|
41
|
+
"@friggframework/eslint-config": "2.0.0--canary.498.701403a.0",
|
|
42
|
+
"@friggframework/prettier-config": "2.0.0--canary.498.701403a.0",
|
|
43
|
+
"@friggframework/test": "2.0.0--canary.498.701403a.0",
|
|
44
44
|
"@prisma/client": "^6.17.0",
|
|
45
45
|
"@types/lodash": "4.17.15",
|
|
46
46
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "701403a6bbd42a361784baf5d584d515328e9c7c"
|
|
84
84
|
}
|
|
@@ -427,6 +427,26 @@ class UserRepositoryDocumentDB extends UserRepositoryInterface {
|
|
|
427
427
|
const date = new Date(value);
|
|
428
428
|
return isNaN(date.getTime()) ? undefined : date;
|
|
429
429
|
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Link an individual user to an organization user
|
|
433
|
+
* @param {string} individualUserId - Individual user ID (MongoDB ObjectId string)
|
|
434
|
+
* @param {string} organizationUserId - Organization user ID (MongoDB ObjectId string)
|
|
435
|
+
* @returns {Promise<Object>} Updated individual user object
|
|
436
|
+
*/
|
|
437
|
+
async linkIndividualToOrganization(individualUserId, organizationUserId) {
|
|
438
|
+
const doc = await updateOne(
|
|
439
|
+
this.prisma,
|
|
440
|
+
'User',
|
|
441
|
+
{ _id: toObjectId(individualUserId), type: 'INDIVIDUAL' },
|
|
442
|
+
{ $set: { organizationId: toObjectId(organizationUserId) } }
|
|
443
|
+
);
|
|
444
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
445
|
+
'User',
|
|
446
|
+
doc
|
|
447
|
+
);
|
|
448
|
+
return this._mapUser(decrypted);
|
|
449
|
+
}
|
|
430
450
|
}
|
|
431
451
|
|
|
432
452
|
module.exports = { UserRepositoryDocumentDB };
|
|
@@ -193,6 +193,20 @@ class UserRepositoryInterface {
|
|
|
193
193
|
async deleteUser(userId) {
|
|
194
194
|
throw new Error('Method deleteUser must be implemented by subclass');
|
|
195
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Link an individual user to an organization user
|
|
199
|
+
*
|
|
200
|
+
* @param {string|number} individualUserId - Individual user ID
|
|
201
|
+
* @param {string|number} organizationUserId - Organization user ID
|
|
202
|
+
* @returns {Promise<Object>} Updated individual user object
|
|
203
|
+
* @abstract
|
|
204
|
+
*/
|
|
205
|
+
async linkIndividualToOrganization(individualUserId, organizationUserId) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
'Method linkIndividualToOrganization must be implemented by subclass'
|
|
208
|
+
);
|
|
209
|
+
}
|
|
196
210
|
}
|
|
197
211
|
|
|
198
212
|
module.exports = { UserRepositoryInterface };
|
|
@@ -287,6 +287,24 @@ class UserRepositoryMongo extends UserRepositoryInterface {
|
|
|
287
287
|
throw error;
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Link an individual user to an organization user
|
|
293
|
+
* @param {string} individualUserId - Individual user ID (MongoDB ObjectId string)
|
|
294
|
+
* @param {string} organizationUserId - Organization user ID (MongoDB ObjectId string)
|
|
295
|
+
* @returns {Promise<Object>} Updated individual user object
|
|
296
|
+
*/
|
|
297
|
+
async linkIndividualToOrganization(individualUserId, organizationUserId) {
|
|
298
|
+
return await this.prisma.user.update({
|
|
299
|
+
where: {
|
|
300
|
+
id: individualUserId,
|
|
301
|
+
type: 'INDIVIDUAL',
|
|
302
|
+
},
|
|
303
|
+
data: {
|
|
304
|
+
organizationId: organizationUserId,
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
}
|
|
290
308
|
}
|
|
291
309
|
|
|
292
310
|
module.exports = { UserRepositoryMongo };
|
package/user/user.js
CHANGED
|
@@ -88,6 +88,38 @@ class User {
|
|
|
88
88
|
getAppOrgId() {
|
|
89
89
|
return this.organizationUser?.appOrgId || null;
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Checks if a given userId belongs to this user (either primary or linked).
|
|
94
|
+
* When primary is 'organization', entities owned by the linked individual user
|
|
95
|
+
* should still be accessible to the organization.
|
|
96
|
+
*
|
|
97
|
+
* @param {string|number} userId - The userId to check
|
|
98
|
+
* @returns {boolean} True if the userId belongs to this user or their linked user
|
|
99
|
+
*/
|
|
100
|
+
ownsUserId(userId) {
|
|
101
|
+
const userIdStr = userId?.toString();
|
|
102
|
+
const primaryId = this.getPrimaryUser()?.id?.toString();
|
|
103
|
+
const individualId = this.individualUser?.id?.toString();
|
|
104
|
+
const organizationId = this.organizationUser?.id?.toString();
|
|
105
|
+
|
|
106
|
+
// Check if userId matches primary user
|
|
107
|
+
if (userIdStr === primaryId) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// When primary is 'organization', also check linked individual user
|
|
112
|
+
if (this.config.primary === 'organization' && userIdStr === individualId) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// When primary is 'individual', also check linked organization user if required
|
|
117
|
+
if (this.config.primary === 'individual' && this.config.organizationUserRequired && userIdStr === organizationId) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
91
123
|
}
|
|
92
124
|
|
|
93
125
|
module.exports = { User };
|