@izara_project/izara-core-generate-service-code 1.0.84 → 1.0.86

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.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/src/compile/compileProject.js +1 -2
  3. package/src/compile/loadSchemas.js +29 -17
  4. package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +6 -0
  5. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +26 -6
  6. package/src/emit/crud/crudActionPlan.js +3 -1
  7. package/src/emit/crud/emitCrudApplicationArtifacts.js +47 -8
  8. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +136 -25
  9. package/src/emit/crud/templates/CreateConfirmComplete_Main.ejs +54 -0
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +248 -181
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +1155 -293
  12. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +221 -0
  13. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +36 -4
  14. package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +20 -120
  15. package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +76 -14
  16. package/src/emit/crud/templates/FlowMain_Wbs.ejs +31 -35
  17. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +35 -3
  18. package/src/emit/crud/templates/UpdateLogicalLib.ejs +754 -353
  19. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +5 -2
  20. package/src/emit/rbac/emitRbacApplicationArtifacts.js +102 -6
  21. package/src/emit/rbac/templates/CancelUsage_Main.ejs +82 -22
  22. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +130 -14
  23. package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +61 -12
  24. package/src/emit/rbac/templates/DeleteTargetRole_Main.ejs +96 -0
  25. package/src/emit/rbac/templates/GetMyPermissions_Main.ejs +106 -0
  26. package/src/emit/rbac/templates/GetTargetRole_Main.ejs +62 -0
  27. package/src/emit/rbac/templates/ReservedDynamicUsage_Main.ejs +30 -11
  28. package/src/emit/rbac/templates/UpdateTargetRole_Main.ejs +107 -0
  29. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +40 -15
  30. package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +22 -7
  31. package/src/emit/resource/emitResourceArtifacts.js +52 -2
  32. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +6 -2
  33. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +2 -0
  34. package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +1 -1
  35. package/src/emit/system/templates/WebSocketConnect_Yaml.ejs +0 -1
  36. package/src/emit/systemText/emitSystemTextSchemas.js +5 -2
  37. package/src/generate.js +18 -12
  38. package/src/generateResources/IntTest/generateResources/generateResources.js +1 -1
  39. package/src/generateResources/IntTest/generateTests/generateTemplate.js +1 -1
  40. package/src/generateResources/IntTest/upload/uploadIntTest.js +8 -2
  41. package/test/createTargetRoleHandler.test.js +42 -1
  42. package/test/crudApplicationArtifacts.test.js +53 -0
  43. package/test/dynamicCrudFlowSchemas.test.js +16 -2
  44. package/test/dynamicRbacFlowSchemas.test.js +5 -2
  45. package/test/publicApi.test.js +18 -14
  46. package/test/resourceArtifacts.test.js +24 -0
@@ -0,0 +1,106 @@
1
+ /*
2
+ Copyright (C) 2020 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
19
+ import snsSharedLib from '@izara_project/izara-core-library-sns';
20
+ import { sns } from '@izara_project/izara-core-library-external-request';
21
+ import { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
+
23
+ export default async function GetMyPermissions(_izContext, requestParams) {
24
+ try {
25
+ const submittedByUserId =
26
+ requestParams.submittedByUserId ||
27
+ _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
28
+
29
+ if (!submittedByUserId) {
30
+ throw new NoRetryError('User identity not found in context.');
31
+ }
32
+
33
+ const targetIdentifiers = requestParams.targetIdentifiers;
34
+ let identifiersConcat = requestParams.identifiersConcat;
35
+ if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
36
+ if (targetIdentifiers.identifierConcat) {
37
+ identifiersConcat = targetIdentifiers.identifierConcat;
38
+ } else {
39
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
40
+ const parts = [];
41
+ for (const key of sortedKeys) {
42
+ const value = targetIdentifiers[key];
43
+ if (
44
+ key !== 'submittedByUserId' &&
45
+ key !== 'identifierConcat' &&
46
+ value !== undefined &&
47
+ value !== null
48
+ ) {
49
+ parts.push(`${key}#${value}`);
50
+ }
51
+ }
52
+ if (parts.length > 0) {
53
+ identifiersConcat = parts.join('|');
54
+ }
55
+ }
56
+ }
57
+
58
+ const userRolesTable = dynamodbSharedLib.tableName(_izContext, 'UserRoles');
59
+ const rolePermissionsTable = dynamodbSharedLib.tableName(_izContext, 'RolePermissions');
60
+
61
+ const userRolesResult = await dynamodbSharedLib.query(_izContext, userRolesTable, {
62
+ userId: submittedByUserId
63
+ });
64
+ const userRoleItems = userRolesResult?.Items || [];
65
+
66
+ const matchingRoles = identifiersConcat
67
+ ? userRoleItems.filter((item) => item.roleIdKey?.startsWith(identifiersConcat))
68
+ : userRoleItems;
69
+
70
+ const permissionsMap = {};
71
+ const rolesList = [];
72
+
73
+ for (const roleItem of matchingRoles) {
74
+ rolesList.push(roleItem.roleIdKey);
75
+ const permsResult = await dynamodbSharedLib.query(_izContext, rolePermissionsTable, {
76
+ roleIdKey: roleItem.roleIdKey
77
+ });
78
+ for (const p of (permsResult?.Items || [])) {
79
+ if (p.permission === true) {
80
+ permissionsMap[p.flowTypeConcat] = true;
81
+ } else if (permissionsMap[p.flowTypeConcat] === undefined) {
82
+ permissionsMap[p.flowTypeConcat] = false;
83
+ }
84
+ }
85
+ }
86
+
87
+ const result = {
88
+ userId: submittedByUserId,
89
+ identifiersConcat: identifiersConcat || null,
90
+ roles: rolesList,
91
+ permissions: permissionsMap
92
+ };
93
+
94
+ await sns.publishMessage(_izContext, {
95
+ message: { returnResult: result },
96
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
97
+ _izContext,
98
+ 'GetMyPermissions_Out'
99
+ )
100
+ });
101
+ return result;
102
+ } catch (error) {
103
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:GetMyPermissions] error:`, error);
104
+ throw error;
105
+ }
106
+ }
@@ -0,0 +1,62 @@
1
+ /*
2
+ Copyright (C) 2020 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
19
+ import snsSharedLib from '@izara_project/izara-core-library-sns';
20
+ import { sns } from '@izara_project/izara-core-library-external-request';
21
+ import { NoRetryError } from '@izara_project/izara-core-library-core';
22
+
23
+ export default async function GetTargetRole(_izContext, requestParams) {
24
+ try {
25
+ const { identifiersConcat, roleIdKey } = requestParams;
26
+ if (!roleIdKey) {
27
+ throw new NoRetryError('roleIdKey is required');
28
+ }
29
+
30
+ const targetRolesTable = dynamodbSharedLib.tableName(_izContext, 'TargetRoles');
31
+ const rolePermissionsTable = dynamodbSharedLib.tableName(_izContext, 'RolePermissions');
32
+ const roleUsersTable = dynamodbSharedLib.tableName(_izContext, 'RoleUsers');
33
+
34
+ const role = identifiersConcat
35
+ ? await dynamodbSharedLib.getItem(_izContext, targetRolesTable, { identifiersConcat, roleIdKey })
36
+ : null;
37
+
38
+ const [permsResult, usersResult] = await Promise.all([
39
+ dynamodbSharedLib.query(_izContext, rolePermissionsTable, { roleIdKey }),
40
+ dynamodbSharedLib.query(_izContext, roleUsersTable, { roleIdKey })
41
+ ]);
42
+
43
+ const result = {
44
+ role,
45
+ roleIdKey,
46
+ permissions: permsResult?.Items || [],
47
+ users: usersResult?.Items || []
48
+ };
49
+
50
+ await sns.publishMessage(_izContext, {
51
+ message: { returnResult: result },
52
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
53
+ _izContext,
54
+ 'GetTargetRole_Out'
55
+ )
56
+ });
57
+ return result;
58
+ } catch (error) {
59
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:GetTargetRole] error:`, error);
60
+ throw error;
61
+ }
62
+ }
@@ -25,8 +25,10 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
25
25
  const submittedByUserId =
26
26
  requestParams.submittedByUserId ||
27
27
  _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
28
+ const flowTypeConcat =
29
+ requestParams.flowTypeConcat ||
30
+ requestParams.limitTag;
28
31
  const {
29
- limitTag,
30
32
  value = 1,
31
33
  usedTimestamp = new Date().toISOString(),
32
34
  roleLimitId
@@ -35,9 +37,26 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
35
37
  const targetIdentifiers = requestParams.targetIdentifiers;
36
38
  let identifiersConcat = requestParams.identifiersConcat;
37
39
  if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
38
- identifiersConcat = Object.entries(targetIdentifiers)
39
- .map(([k, v]) => `${k}#${v}`)
40
- .join('_');
40
+ if (targetIdentifiers.identifierConcat) {
41
+ identifiersConcat = targetIdentifiers.identifierConcat;
42
+ } else {
43
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
44
+ const parts = [];
45
+ for (const key of sortedKeys) {
46
+ const value = targetIdentifiers[key];
47
+ if (
48
+ key !== 'submittedByUserId' &&
49
+ key !== 'identifierConcat' &&
50
+ value !== undefined &&
51
+ value !== null
52
+ ) {
53
+ parts.push(`${key}#${value}`);
54
+ }
55
+ }
56
+ if (parts.length > 0) {
57
+ identifiersConcat = parts.join('|');
58
+ }
59
+ }
41
60
  }
42
61
  if (!identifiersConcat) {
43
62
  identifiersConcat = submittedByUserId;
@@ -46,14 +65,14 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
46
65
  if (!identifiersConcat) {
47
66
  throw new NoRetryError('Identifier not found.');
48
67
  }
49
- if (!limitTag) {
50
- throw new NoRetryError('limitTag is required.');
68
+ if (!flowTypeConcat) {
69
+ throw new NoRetryError('flowTypeConcat (or limitTag) is required.');
51
70
  }
52
71
 
53
72
  const uniqueIdentify = `${usedTimestamp}_${_izContext.uniqueRequestId || Date.now()}`;
54
73
  const roleLimitKeys = Array.isArray(requestParams.roleLimitIds)
55
74
  ? requestParams.roleLimitIds
56
- : [roleLimitId || `${identifiersConcat}_${limitTag}`];
75
+ : [roleLimitId || `${identifiersConcat}_${flowTypeConcat}`];
57
76
 
58
77
  let maxLimit = Infinity;
59
78
  const limitRecords = [];
@@ -93,7 +112,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
93
112
  const usageRecord = await dynamodbSharedLib.getItem(
94
113
  _izContext,
95
114
  dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
96
- { identifiersConcat, limitTag }
115
+ { identifiersConcat, flowTypeConcat }
97
116
  );
98
117
  const currentTotal = usageRecord?.totalCount || 0;
99
118
 
@@ -106,7 +125,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
106
125
  _izContext,
107
126
  dynamodbSharedLib.tableName(_izContext, 'RunningTotalReservedUsage'),
108
127
  {
109
- runningTotalReservedId: `${identifiersConcat}_${limitTag}`,
128
+ runningTotalReservedId: `${identifiersConcat}_${flowTypeConcat}`,
110
129
  usedTimestampId: uniqueIdentify,
111
130
  usageCount: value,
112
131
  runningTotalUsageStatus: 'reserved',
@@ -119,7 +138,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
119
138
  dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
120
139
  {
121
140
  identifiersConcat,
122
- limitTag,
141
+ flowTypeConcat,
123
142
  totalCount: currentTotal + value,
124
143
  updateUuid: _izContext.uniqueRequestId || `${Date.now()}`
125
144
  }
@@ -129,7 +148,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
129
148
  const returnResult = {
130
149
  passStatus,
131
150
  uniqueIdentify,
132
- limitTag,
151
+ flowTypeConcat,
133
152
  identifiersConcat
134
153
  };
135
154
 
@@ -0,0 +1,107 @@
1
+ /*
2
+ Copyright (C) 2020 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
19
+ import snsSharedLib from '@izara_project/izara-core-library-sns';
20
+ import { sns } from '@izara_project/izara-core-library-external-request';
21
+ import { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
+
23
+ export default async function UpdateTargetRole(_izContext, requestParams) {
24
+ try {
25
+ const submittedByUserId =
26
+ requestParams.submittedByUserId ||
27
+ _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
28
+
29
+ const {
30
+ identifiersConcat,
31
+ roleIdKey,
32
+ roleName,
33
+ permissions
34
+ } = requestParams;
35
+
36
+ if (!roleIdKey) {
37
+ throw new NoRetryError('roleIdKey is required');
38
+ }
39
+
40
+ if (roleIdKey.endsWith('_owner')) {
41
+ throw new NoRetryError('Cannot modify system owner role');
42
+ }
43
+
44
+ const targetRolesTable = dynamodbSharedLib.tableName(_izContext, 'TargetRoles');
45
+ const rolePermissionsTable = dynamodbSharedLib.tableName(_izContext, 'RolePermissions');
46
+
47
+ if (roleName && identifiersConcat) {
48
+ const existingRole = await dynamodbSharedLib.getItem(_izContext, targetRolesTable, {
49
+ identifiersConcat,
50
+ roleIdKey
51
+ });
52
+ if (existingRole) {
53
+ await dynamodbSharedLib.putItem(_izContext, targetRolesTable, {
54
+ ...existingRole,
55
+ roleName
56
+ });
57
+ }
58
+ }
59
+
60
+ if (Array.isArray(permissions)) {
61
+ const currentPermsQuery = await dynamodbSharedLib.query(_izContext, rolePermissionsTable, {
62
+ roleIdKey
63
+ });
64
+ const currentPerms = currentPermsQuery?.Items || [];
65
+ const currentFlowMap = new Map(currentPerms.map((p) => [p.flowTypeConcat, p]));
66
+
67
+ const newFlowSet = new Set();
68
+ for (const perm of permissions) {
69
+ const flowTypeConcat = typeof perm === 'string' ? perm : perm.flowTypeConcat;
70
+ if (flowTypeConcat) {
71
+ newFlowSet.add(flowTypeConcat);
72
+ const permission =
73
+ typeof perm === 'object' && perm.permission !== undefined
74
+ ? perm.permission
75
+ : true;
76
+ const parameterGroups = (typeof perm === 'object' && perm.parameterGroups) || {};
77
+ await dynamodbSharedLib.putItem(_izContext, rolePermissionsTable, {
78
+ roleIdKey,
79
+ flowTypeConcat,
80
+ permission,
81
+ parameterGroups
82
+ });
83
+ }
84
+ }
85
+
86
+ for (const [oldFlow] of currentFlowMap.entries()) {
87
+ if (!newFlowSet.has(oldFlow)) {
88
+ await dynamodbSharedLib.deleteItem(_izContext, rolePermissionsTable, {
89
+ roleIdKey,
90
+ flowTypeConcat: oldFlow
91
+ });
92
+ }
93
+ }
94
+ }
95
+
96
+ await sns.publishMessage(_izContext, {
97
+ message: { returnResult: { roleIdKey, status: 'updated' } },
98
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
99
+ _izContext,
100
+ 'UpdateTargetRole_Out'
101
+ )
102
+ });
103
+ } catch (error) {
104
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:UpdateTargetRole] error:`, error);
105
+ throw error;
106
+ }
107
+ }
@@ -52,7 +52,7 @@ async function _getL2Cache(_izContext, submittedByUserId, cacheSubKey) {
52
52
  try {
53
53
  const tableName = dynamodbSharedLib.tableName(_izContext, 'RbacPermissionCaches');
54
54
  const result = await dynamodbSharedLib.getItem(_izContext, tableName, {
55
- submittedByUserId,
55
+ userId: submittedByUserId,
56
56
  cacheKey: cacheSubKey
57
57
  });
58
58
  if (result && result.ttlTimestamp && result.ttlTimestamp > Math.floor(Date.now() / 1000)) {
@@ -77,7 +77,7 @@ async function _setL2Cache(_izContext, submittedByUserId, cacheSubKey, value) {
77
77
  const tableName = dynamodbSharedLib.tableName(_izContext, 'RbacPermissionCaches');
78
78
  const ttlTimestamp = Math.floor(Date.now() / 1000) + L2_TTL_SECONDS;
79
79
  await dynamodbSharedLib.putItem(_izContext, tableName, {
80
- submittedByUserId,
80
+ userId: submittedByUserId,
81
81
  cacheKey: cacheSubKey,
82
82
  permission: value.returnValue ? value.returnValue.permission : value.permission,
83
83
  roleIdKey: value.returnValue ? value.returnValue.roleIdKey : value.roleIdKey,
@@ -92,12 +92,37 @@ async function _setL2Cache(_izContext, submittedByUserId, cacheSubKey, value) {
92
92
  export default async function <%- flowTag %>(_izContext, requestParams = {}) {
93
93
  const submittedByUserId =
94
94
  requestParams.submittedByUserId ||
95
+ requestParams.targetIdentifiers?.submittedByUserId ||
95
96
  _izContext.correlationIds.get(consts.SUBMITTED_BY_USER_ID);
96
97
 
97
98
  const targetIdentifiers = requestParams.targetIdentifiers;
98
- let targetId = targetIdentifiers && typeof targetIdentifiers === 'object'
99
- ? Object.entries(targetIdentifiers).map(([k, v]) => `${k}#${v}`).join('_')
100
- : undefined;
99
+ let targetId = '';
100
+ if (targetIdentifiers) {
101
+ if (typeof targetIdentifiers === 'string') {
102
+ targetId = targetIdentifiers;
103
+ } else if (typeof targetIdentifiers === 'object') {
104
+ if (targetIdentifiers.identifierConcat) {
105
+ targetId = targetIdentifiers.identifierConcat;
106
+ } else {
107
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
108
+ const parts = [];
109
+ for (const key of sortedKeys) {
110
+ const value = targetIdentifiers[key];
111
+ if (
112
+ key !== 'submittedByUserId' &&
113
+ key !== 'identifierConcat' &&
114
+ value !== undefined &&
115
+ value !== null
116
+ ) {
117
+ parts.push(`${key}#${value}`);
118
+ }
119
+ }
120
+ if (parts.length > 0) {
121
+ targetId = parts.join('|');
122
+ }
123
+ }
124
+ }
125
+ }
101
126
 
102
127
  if (!submittedByUserId || !targetIdentifiers) {
103
128
  throw new Error('Missing required request parameters: submittedByUserId or targetIdentifiers');
@@ -108,7 +133,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
108
133
  throw new Error('Missing flowTypeConcat in requestParams');
109
134
  }
110
135
 
111
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] requestParams:', {
136
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] requestParams:`, {
112
137
  submittedByUserId,
113
138
  targetId,
114
139
  flowTypeConcat,
@@ -150,7 +175,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
150
175
  // 1. Check L1 In-Memory Cache
151
176
  const l1Hit = _getL1Cache(fullCacheKey);
152
177
  if (l1Hit) {
153
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L1 Cache hit:', l1Hit);
178
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L1 Cache hit:`, l1Hit);
154
179
  await _publicMessageOut(_izContext, { ...l1Hit, parentFlowId });
155
180
  return;
156
181
  }
@@ -158,19 +183,19 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
158
183
  // 2. Check L2 Shared DynamoDB Cache
159
184
  const l2Hit = await _getL2Cache(_izContext, submittedByUserId, cacheSubKey);
160
185
  if (l2Hit) {
161
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L2 Cache hit:', l2Hit);
186
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L2 Cache hit:`, l2Hit);
162
187
  _setL1Cache(fullCacheKey, l2Hit);
163
188
  await _publicMessageOut(_izContext, { ...l2Hit, parentFlowId });
164
189
  return;
165
190
  }
166
191
 
167
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] Cache miss — querying DynamoDB');
192
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] Cache miss — querying DynamoDB`);
168
193
 
169
194
  // 3. Fallback to DB Query
170
195
  const userRolesResult = await dynamodbSharedLib.query(
171
196
  _izContext,
172
197
  dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
173
- { submittedByUserId }
198
+ { userId: submittedByUserId }
174
199
  );
175
200
 
176
201
  let userRoles = [];
@@ -180,7 +205,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
180
205
  userRoles = userRolesResult.Items;
181
206
  }
182
207
 
183
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] userRoles fetched:', { userRoles });
208
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] userRoles fetched:`, { userRoles });
184
209
 
185
210
  const targetIdPrefix = `${targetId}_`;
186
211
  const targetRoles = [];
@@ -190,13 +215,13 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
190
215
  }
191
216
  }
192
217
 
193
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] targetRoles filtered:', {
218
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] targetRoles filtered:`, {
194
219
  targetIdPrefix,
195
220
  targetRoles
196
221
  });
197
222
 
198
223
  if (targetRoles.length === 0) {
199
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] no targetRoles found, permission denied');
224
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] no targetRoles found, permission denied`);
200
225
  const result = {
201
226
  returnValue: {
202
227
  permission: false,
@@ -225,7 +250,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
225
250
  })
226
251
  );
227
252
 
228
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] rolePermissions fetched:', {
253
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] rolePermissions fetched:`, {
229
254
  rolePermissions
230
255
  });
231
256
 
@@ -253,7 +278,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
253
278
  status: 'Completed'
254
279
  };
255
280
 
256
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] validation result:', cacheResult);
281
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] validation result:`, cacheResult);
257
282
 
258
283
  // Store in L1 & L2 Cache
259
284
  _setL1Cache(fullCacheKey, cacheResult);
@@ -16,7 +16,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
18
  <%
19
- const itemType = operation === 'Put' ? 'Item' : 'Key';
19
+ const action = operation === 'Put' ? 'put' : 'delete';
20
+ const propName = operation === 'Put' ? 'attributes' : 'keyValues';
20
21
  const roleUsersKey = operation === 'Put' ? 'userId, roleIdKey' : 'roleIdKey, userId';
21
22
  -%> import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
22
23
  import snsSharedLib from '@izara_project/izara-core-library-sns';
@@ -25,17 +26,31 @@ import { sns } from '@izara_project/izara-core-library-external-request';
25
26
  export default async function <%- flowTag %>(_izContext, requestParams) {
26
27
  try {
27
28
  const { roleIdKey, userId } = requestParams;
29
+ <% if (operation === 'Delete') { -%>
30
+ if (roleIdKey && roleIdKey.endsWith('_owner')) {
31
+ const remainingUsers = await dynamodbSharedLib.query(
32
+ _izContext,
33
+ dynamodbSharedLib.tableName(_izContext, 'RoleUsers'),
34
+ { roleIdKey }
35
+ );
36
+ if (remainingUsers?.Items?.length <= 1) {
37
+ throw new Error('Cannot remove the last remaining owner');
38
+ }
39
+ }
40
+ <% } -%>
28
41
  await dynamodbSharedLib.transactWriteItems(_izContext, [
29
42
  {
30
- <%- operation %>: {
31
- TableName: dynamodbSharedLib.tableName(_izContext, 'RoleUsers'),
32
- <%- itemType %>: { <%- roleUsersKey %> }
43
+ action: '<%- action %>',
44
+ params: {
45
+ tableName: dynamodbSharedLib.tableName(_izContext, 'RoleUsers'),
46
+ <%- propName %>: { <%- roleUsersKey %> }
33
47
  }
34
48
  },
35
49
  {
36
- <%- operation %>: {
37
- TableName: dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
38
- <%- itemType %>: { userId, roleIdKey }
50
+ action: '<%- action %>',
51
+ params: {
52
+ tableName: dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
53
+ <%- propName %>: { userId, roleIdKey }
39
54
  }
40
55
  }
41
56
  ]);
@@ -31,14 +31,17 @@ const RELATIONSHIP_ACTIONS = [
31
31
  ];
32
32
  const RBAC_ACTIONS = [
33
33
  'CreateTargetRole',
34
- 'ListTargetRole',
34
+ 'UpdateTargetRole',
35
35
  'DeleteTargetRole',
36
+ 'GetTargetRole',
37
+ 'ListTargetRole',
36
38
  'CreateRolePermissions',
37
39
  'ListRolePermissions',
38
40
  'DeleteRolePermissions',
39
41
  'CreateUserRole',
40
42
  'ListUserInRoles',
41
43
  'DeleteUserFromRole',
44
+ 'GetMyPermissions',
42
45
  'ValidatorRbac',
43
46
  'CreateRoleLimit',
44
47
  'ListRoleLimits',
@@ -215,6 +218,9 @@ function createResourceState({ objects, relationships, flows }) {
215
218
  if (crud) {
216
219
  for (const action of ['Create', 'Update', 'Delete']) {
217
220
  const plan = crudActionPlans[action];
221
+ if (plan.hasCreateNodeCallback) {
222
+ crudSqsQueues.push(`${action}NodeCallbackHdrSqs`);
223
+ }
218
224
  if (plan.hasComplete) {
219
225
  crudSqsQueues.push(`${action}CompleteHdrSqs`);
220
226
  }
@@ -474,6 +480,9 @@ function createResourceState({ objects, relationships, flows }) {
474
480
  flow.initiateBy === 'systemWithUser'))) &&
475
481
  upperFirst(flow.flowTag) === topic
476
482
  ) ||
483
+ (objects.length > 0 &&
484
+ topic !== 'Get' &&
485
+ CRUD_ACTIONS.includes(topic)) ||
477
486
  (relationships.length > 0 &&
478
487
  topic !== 'GetRelationship' &&
479
488
  RELATIONSHIP_ACTIONS.includes(topic))
@@ -722,7 +731,10 @@ function roleStatements(state, roleName) {
722
731
  addStatement(
723
732
  statements,
724
733
  ['sns:Publish'],
725
- topicArns(roleTopicNames(state, roleName))
734
+ [
735
+ ...topicArns(roleTopicNames(state, roleName)),
736
+ ...roleExternalTopicArns(state, roleName)
737
+ ]
726
738
  );
727
739
  if (state.webSocketRoles.has(roleName)) {
728
740
  statements.push({
@@ -760,6 +772,7 @@ function roleTableNames(state, roleName) {
760
772
  ...(state.rbac
761
773
  ? [
762
774
  'TargetRoles',
775
+ 'RolePermissions',
763
776
  'RoleUsers',
764
777
  'UserRoles',
765
778
  'RoleLimit',
@@ -832,6 +845,9 @@ function roleQueueNames(state, roleName) {
832
845
  if (!plan.outputTopic) return [];
833
846
  return [
834
847
  `${action}HdrSqs`,
848
+ ...(plan.hasCreateNodeCallback
849
+ ? [`${action}NodeCallbackHdrSqs`]
850
+ : []),
835
851
  ...(plan.hasRbacCallback
836
852
  ? [`${action}RbacCallbackHdrSqs`]
837
853
  : []),
@@ -916,6 +932,9 @@ function roleTopicNames(state, roleName) {
916
932
  topics.push(`${action}_In`, `${action}_Out`);
917
933
  }
918
934
  }
935
+ if (state.rbac) {
936
+ topics.push('ValidatorRbac_In');
937
+ }
919
938
  return topics;
920
939
  }
921
940
  if (roleName === 'FlowRelationshipRole') {
@@ -928,6 +947,9 @@ function roleTopicNames(state, roleName) {
928
947
  topics.push(`${action}_In`, `${action}_Out`);
929
948
  }
930
949
  }
950
+ if (state.rbac) {
951
+ topics.push('ValidatorRbac_In');
952
+ }
931
953
  return topics;
932
954
  }
933
955
  if (roleName === 'FlowRbacRole') {
@@ -1014,6 +1036,34 @@ function roleExternalDynamoTables(state, roleName) {
1014
1036
  return [];
1015
1037
  }
1016
1038
 
1039
+ function roleExternalTopicArns(state, roleName) {
1040
+ if (!state.rbac) {
1041
+ return [];
1042
+ }
1043
+ const extraTags = getRbacValidatorServiceTags(state).filter(
1044
+ (tag) => tag && tag !== 'Rbac'
1045
+ );
1046
+ const extraTopics = extraTags.map(
1047
+ (tag) =>
1048
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:${tag}_\${self:custom.iz_stage}_ValidatorRbac_In`
1049
+ );
1050
+ if (roleName === 'FlowObjectsRole') {
1051
+ return [
1052
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ValidatorRbac_In`,
1053
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ConfirmUsage_In`,
1054
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_CancelUsage_In`,
1055
+ ...extraTopics
1056
+ ];
1057
+ }
1058
+ if (roleName === 'FlowRbacRole' || roleName === 'FlowRelationshipRole') {
1059
+ return [
1060
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ValidatorRbac_In`,
1061
+ ...extraTopics
1062
+ ];
1063
+ }
1064
+ return [];
1065
+ }
1066
+
1017
1067
  function queueArns(queueNames) {
1018
1068
  return [...new Set(queueNames)]
1019
1069
  .sort()