@izara_project/izara-core-generate-service-code 1.0.85 → 1.0.87

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 (43) 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 +2 -1
  5. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +32 -14
  6. package/src/emit/crud/crudActionPlan.js +1 -1
  7. package/src/emit/crud/emitCrudApplicationArtifacts.js +10 -4
  8. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +16 -8
  9. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +12 -3
  10. package/src/emit/crud/templates/CreateLogicalLib.ejs +85 -20
  11. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +48 -7
  12. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +47 -6
  13. package/src/emit/crud/templates/UpdateLogicalLib.ejs +754 -353
  14. package/src/emit/function/functionYamlArtifact.js +4 -2
  15. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +5 -5
  16. package/src/emit/rbac/emitRbacApplicationArtifacts.js +70 -89
  17. package/src/emit/rbac/templates/CancelUsage_Main.ejs +14 -10
  18. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +14 -10
  19. package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +56 -16
  20. package/src/emit/rbac/templates/DeleteTargetRole_Main.ejs +96 -0
  21. package/src/emit/rbac/templates/GetMyPermissions_Main.ejs +110 -0
  22. package/src/emit/rbac/templates/GetTargetRole_Main.ejs +62 -0
  23. package/src/emit/rbac/templates/ReservedDynamicUsage_Main.ejs +76 -34
  24. package/src/emit/rbac/templates/UpdateTargetRole_Main.ejs +107 -0
  25. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +24 -21
  26. package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +12 -0
  27. package/src/emit/resource/emitResourceArtifacts.js +75 -8
  28. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +4 -2
  29. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +14 -18
  30. package/src/emit/system/templates/WebSocketComplete_Yaml.ejs +4 -2
  31. package/src/emit/systemText/emitSystemTextSchemas.js +5 -2
  32. package/src/generate.js +18 -12
  33. package/src/generateResources/IntTest/generateResources/generateResources.js +1 -1
  34. package/src/generateResources/IntTest/generateTests/generateTemplate.js +1 -1
  35. package/src/generateResources/IntTest/upload/uploadIntTest.js +8 -2
  36. package/test/createTargetRoleHandler.test.js +42 -15
  37. package/test/crudApplicationArtifacts.test.js +62 -3
  38. package/test/customFlowApplicationArtifacts.test.js +3 -5
  39. package/test/dynamicCrudFlowSchemas.test.js +5 -17
  40. package/test/dynamicRbacFlowSchemas.test.js +5 -5
  41. package/test/functionYamlArtifact.test.js +4 -2
  42. package/test/publicApi.test.js +18 -14
  43. package/test/resourceArtifacts.test.js +7 -3
@@ -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
+ }
@@ -35,21 +35,25 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
35
35
  } = requestParams;
36
36
 
37
37
  const targetIdentifiers = requestParams.targetIdentifiers;
38
- let identifiersConcat = requestParams.identifiersConcat;
38
+ let identifiersConcat =
39
+ requestParams.identifiersConcat || requestParams.identifierConcat;
39
40
  if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
40
- if (targetIdentifiers.identifierConcat) {
41
- identifiersConcat = targetIdentifiers.identifierConcat;
41
+ if (targetIdentifiers.identifiersConcat || targetIdentifiers.identifierConcat) {
42
+ identifiersConcat =
43
+ targetIdentifiers.identifiersConcat || targetIdentifiers.identifierConcat;
42
44
  } else {
43
- const sortedKeys = Object.keys(targetIdentifiers).sort();
45
+ const sortedKeys = Object.keys(targetIdentifiers)
46
+ .filter(
47
+ (k) =>
48
+ k !== 'submittedByUserId' &&
49
+ k !== 'identifierConcat' &&
50
+ k !== 'identifiersConcat'
51
+ )
52
+ .sort();
44
53
  const parts = [];
45
54
  for (const key of sortedKeys) {
46
55
  const value = targetIdentifiers[key];
47
- if (
48
- key !== 'submittedByUserId' &&
49
- key !== 'identifierConcat' &&
50
- value !== undefined &&
51
- value !== null
52
- ) {
56
+ if (value !== undefined && value !== null) {
53
57
  parts.push(`${key}#${value}`);
54
58
  }
55
59
  }
@@ -70,41 +74,79 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
70
74
  }
71
75
 
72
76
  const uniqueIdentify = `${usedTimestamp}_${_izContext.uniqueRequestId || Date.now()}`;
73
- const roleLimitKeys = Array.isArray(requestParams.roleLimitIds)
74
- ? requestParams.roleLimitIds
75
- : [roleLimitId || `${identifiersConcat}_${flowTypeConcat}`];
76
77
 
77
- let maxLimit = Infinity;
78
- const limitRecords = [];
79
-
80
- for (const key of roleLimitKeys) {
81
- const record = await dynamodbSharedLib.getItem(
78
+ let targetPlanTier = requestParams.planTier || null;
79
+ if (!targetPlanTier && identifiersConcat) {
80
+ const targetRolesResult = await dynamodbSharedLib.query(
82
81
  _izContext,
83
- dynamodbSharedLib.tableName(_izContext, 'RoleLimit'),
84
- { roleLimitId: key, limitType: 'runningTotal' }
82
+ dynamodbSharedLib.tableName(_izContext, 'TargetRoles'),
83
+ { identifiersConcat }
85
84
  );
86
- if (record && typeof record.limitValue === 'number') {
87
- limitRecords.push(record);
85
+ let targetRoleItems = [];
86
+ if (Array.isArray(targetRolesResult)) {
87
+ targetRoleItems = targetRolesResult;
88
+ } else if (targetRolesResult && targetRolesResult.Items) {
89
+ targetRoleItems = targetRolesResult.Items;
90
+ }
91
+ for (const roleItem of targetRoleItems) {
92
+ if (roleItem.roleType === 'plan_tier') {
93
+ targetPlanTier = roleItem.roleIdKey;
94
+ break;
95
+ }
96
+ }
97
+ }
98
+ if (!targetPlanTier) {
99
+ targetPlanTier = 'tier-free';
100
+ }
101
+
102
+ const limitRecords = [];
103
+ const tierPermission = await dynamodbSharedLib.getItem(
104
+ _izContext,
105
+ dynamodbSharedLib.tableName(_izContext, 'RolePermissions'),
106
+ { roleIdKey: targetPlanTier, flowTypeConcat }
107
+ );
108
+ if (tierPermission && typeof tierPermission.limitValue === 'number') {
109
+ limitRecords.push(tierPermission);
110
+ }
111
+
112
+ if (Array.isArray(requestParams.roleIdKeys)) {
113
+ for (const rKey of requestParams.roleIdKeys) {
114
+ const perm = await dynamodbSharedLib.getItem(
115
+ _izContext,
116
+ dynamodbSharedLib.tableName(_izContext, 'RolePermissions'),
117
+ { roleIdKey: rKey, flowTypeConcat }
118
+ );
119
+ if (perm && typeof perm.limitValue === 'number') {
120
+ limitRecords.push(perm);
121
+ }
88
122
  }
89
123
  }
90
124
 
125
+ let maxLimit = Infinity;
91
126
  if (limitRecords.length > 0) {
92
- const isDecrease = (rec) =>
93
- rec.effect === 'decrease' ||
94
- rec.actionType === 'decrease' ||
95
- rec.action === 'decrease';
127
+ const isDecrease = function (rec) {
128
+ if (
129
+ rec.effect === 'decrease' ||
130
+ rec.actionType === 'decrease' ||
131
+ rec.action === 'decrease'
132
+ ) {
133
+ return true;
134
+ }
135
+ return false;
136
+ };
96
137
 
97
138
  const increaseLimits = limitRecords.filter((rec) => !isDecrease(rec));
98
139
  const decreaseLimits = limitRecords.filter((rec) => isDecrease(rec));
99
140
 
100
- const maxIncrease =
101
- increaseLimits.length > 0
102
- ? Math.max(...increaseLimits.map((r) => r.limitValue))
103
- : Infinity;
104
- const minDecrease =
105
- decreaseLimits.length > 0
106
- ? Math.min(...decreaseLimits.map((r) => r.limitValue))
107
- : Infinity;
141
+ let maxIncrease = Infinity;
142
+ if (increaseLimits.length > 0) {
143
+ maxIncrease = Math.max(...increaseLimits.map((r) => r.limitValue));
144
+ }
145
+
146
+ let minDecrease = Infinity;
147
+ if (decreaseLimits.length > 0) {
148
+ minDecrease = Math.min(...decreaseLimits.map((r) => r.limitValue));
149
+ }
108
150
 
109
151
  maxLimit = Math.min(maxIncrease, minDecrease);
110
152
  }
@@ -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,
@@ -101,19 +101,22 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
101
101
  if (typeof targetIdentifiers === 'string') {
102
102
  targetId = targetIdentifiers;
103
103
  } else if (typeof targetIdentifiers === 'object') {
104
- if (targetIdentifiers.identifierConcat) {
105
- targetId = targetIdentifiers.identifierConcat;
104
+ if (targetIdentifiers.identifiersConcat || targetIdentifiers.identifierConcat) {
105
+ targetId =
106
+ targetIdentifiers.identifiersConcat || targetIdentifiers.identifierConcat;
106
107
  } else {
107
- const sortedKeys = Object.keys(targetIdentifiers).sort();
108
+ const sortedKeys = Object.keys(targetIdentifiers)
109
+ .filter(
110
+ (k) =>
111
+ k !== 'submittedByUserId' &&
112
+ k !== 'identifierConcat' &&
113
+ k !== 'identifiersConcat'
114
+ )
115
+ .sort();
108
116
  const parts = [];
109
117
  for (const key of sortedKeys) {
110
118
  const value = targetIdentifiers[key];
111
- if (
112
- key !== 'submittedByUserId' &&
113
- key !== 'identifierConcat' &&
114
- value !== undefined &&
115
- value !== null
116
- ) {
119
+ if (value !== undefined && value !== null) {
117
120
  parts.push(`${key}#${value}`);
118
121
  }
119
122
  }
@@ -133,7 +136,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
133
136
  throw new Error('Missing flowTypeConcat in requestParams');
134
137
  }
135
138
 
136
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] requestParams:', {
139
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] requestParams:`, {
137
140
  submittedByUserId,
138
141
  targetId,
139
142
  flowTypeConcat,
@@ -175,7 +178,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
175
178
  // 1. Check L1 In-Memory Cache
176
179
  const l1Hit = _getL1Cache(fullCacheKey);
177
180
  if (l1Hit) {
178
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L1 Cache hit:', l1Hit);
181
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L1 Cache hit:`, l1Hit);
179
182
  await _publicMessageOut(_izContext, { ...l1Hit, parentFlowId });
180
183
  return;
181
184
  }
@@ -183,19 +186,19 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
183
186
  // 2. Check L2 Shared DynamoDB Cache
184
187
  const l2Hit = await _getL2Cache(_izContext, submittedByUserId, cacheSubKey);
185
188
  if (l2Hit) {
186
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L2 Cache hit:', l2Hit);
189
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] L2 Cache hit:`, l2Hit);
187
190
  _setL1Cache(fullCacheKey, l2Hit);
188
191
  await _publicMessageOut(_izContext, { ...l2Hit, parentFlowId });
189
192
  return;
190
193
  }
191
194
 
192
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] Cache miss — querying DynamoDB');
195
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] Cache miss — querying DynamoDB`);
193
196
 
194
197
  // 3. Fallback to DB Query
195
198
  const userRolesResult = await dynamodbSharedLib.query(
196
199
  _izContext,
197
200
  dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
198
- { submittedByUserId }
201
+ { userId: submittedByUserId }
199
202
  );
200
203
 
201
204
  let userRoles = [];
@@ -205,7 +208,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
205
208
  userRoles = userRolesResult.Items;
206
209
  }
207
210
 
208
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] userRoles fetched:', { userRoles });
211
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] userRoles fetched:`, { userRoles });
209
212
 
210
213
  const targetIdPrefix = `${targetId}_`;
211
214
  const targetRoles = [];
@@ -215,13 +218,13 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
215
218
  }
216
219
  }
217
220
 
218
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] targetRoles filtered:', {
221
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] targetRoles filtered:`, {
219
222
  targetIdPrefix,
220
223
  targetRoles
221
224
  });
222
225
 
223
226
  if (targetRoles.length === 0) {
224
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] no targetRoles found, permission denied');
227
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] no targetRoles found, permission denied`);
225
228
  const result = {
226
229
  returnValue: {
227
230
  permission: false,
@@ -250,7 +253,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
250
253
  })
251
254
  );
252
255
 
253
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] rolePermissions fetched:', {
256
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] rolePermissions fetched:`, {
254
257
  rolePermissions
255
258
  });
256
259
 
@@ -278,7 +281,7 @@ export default async function <%- flowTag %>(_izContext, requestParams = {}) {
278
281
  status: 'Completed'
279
282
  };
280
283
 
281
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] validation result:', cacheResult);
284
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:<%- flowTag %>] validation result:`, cacheResult);
282
285
 
283
286
  // Store in L1 & L2 Cache
284
287
  _setL1Cache(fullCacheKey, cacheResult);
@@ -26,6 +26,18 @@ import { sns } from '@izara_project/izara-core-library-external-request';
26
26
  export default async function <%- flowTag %>(_izContext, requestParams) {
27
27
  try {
28
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
+ <% } -%>
29
41
  await dynamodbSharedLib.transactWriteItems(_izContext, [
30
42
  {
31
43
  action: '<%- action %>',
@@ -31,18 +31,18 @@ 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
- 'CreateRoleLimit',
44
- 'ListRoleLimits',
45
- 'DeleteRoleLimit',
46
46
  'ReservedDynamicUsage',
47
47
  'ConfirmUsage',
48
48
  'CancelUsage'
@@ -59,12 +59,12 @@ const SYSTEM_TABLES = [
59
59
  'AwaitingStep'
60
60
  ];
61
61
  const RBAC_TABLES = [
62
+ 'Roles',
62
63
  'TargetRoles',
63
64
  'RolePermissions',
64
65
  'RoleUsers',
65
66
  'UserRoles',
66
67
  'RbacPermissionCaches',
67
- 'RoleLimit',
68
68
  'RunningTotalUsage',
69
69
  'RunningTotalReservedUsage',
70
70
  'WebSocketTask'
@@ -414,12 +414,12 @@ function createResourceState({ objects, relationships, flows }) {
414
414
  const systemTables = [...SYSTEM_TABLES];
415
415
  if (rbac) {
416
416
  systemTables.push(
417
+ 'Roles',
417
418
  'TargetRoles',
418
419
  'RolePermissions',
419
420
  'RoleUsers',
420
421
  'UserRoles',
421
422
  'RbacPermissionCaches',
422
- 'RoleLimit',
423
423
  'RunningTotalUsage',
424
424
  'RunningTotalReservedUsage'
425
425
  );
@@ -728,7 +728,15 @@ function roleStatements(state, roleName) {
728
728
  addStatement(
729
729
  statements,
730
730
  ['sns:Publish'],
731
- topicArns(roleTopicNames(state, roleName))
731
+ [
732
+ ...topicArns(roleTopicNames(state, roleName)),
733
+ ...roleExternalTopicArns(state, roleName)
734
+ ]
735
+ );
736
+ addStatement(
737
+ statements,
738
+ ['lambda:InvokeFunction'],
739
+ roleExternalLambdaArns(state, roleName)
732
740
  );
733
741
  if (state.webSocketRoles.has(roleName)) {
734
742
  statements.push({
@@ -765,10 +773,11 @@ function roleTableNames(state, roleName) {
765
773
  'WebSocketTask',
766
774
  ...(state.rbac
767
775
  ? [
776
+ 'Roles',
768
777
  'TargetRoles',
778
+ 'RolePermissions',
769
779
  'RoleUsers',
770
780
  'UserRoles',
771
- 'RoleLimit',
772
781
  'RunningTotalUsage',
773
782
  'RunningTotalReservedUsage'
774
783
  ]
@@ -925,6 +934,9 @@ function roleTopicNames(state, roleName) {
925
934
  topics.push(`${action}_In`, `${action}_Out`);
926
935
  }
927
936
  }
937
+ if (state.rbac) {
938
+ topics.push('ValidatorRbac_In');
939
+ }
928
940
  return topics;
929
941
  }
930
942
  if (roleName === 'FlowRelationshipRole') {
@@ -937,6 +949,9 @@ function roleTopicNames(state, roleName) {
937
949
  topics.push(`${action}_In`, `${action}_Out`);
938
950
  }
939
951
  }
952
+ if (state.rbac) {
953
+ topics.push('ValidatorRbac_In');
954
+ }
940
955
  return topics;
941
956
  }
942
957
  if (roleName === 'FlowRbacRole') {
@@ -1023,6 +1038,58 @@ function roleExternalDynamoTables(state, roleName) {
1023
1038
  return [];
1024
1039
  }
1025
1040
 
1041
+ function roleExternalTopicArns(state, roleName) {
1042
+ if (!state.rbac) {
1043
+ return [];
1044
+ }
1045
+ const extraTags = getRbacValidatorServiceTags(state).filter(
1046
+ (tag) => tag && tag !== 'Rbac'
1047
+ );
1048
+ const extraTopics = extraTags.map(
1049
+ (tag) =>
1050
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:${tag}_\${self:custom.iz_stage}_ValidatorRbac_In`
1051
+ );
1052
+ if (roleName === 'FlowObjectsRole') {
1053
+ return [
1054
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ValidatorRbac_In`,
1055
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ConfirmUsage_In`,
1056
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_CancelUsage_In`,
1057
+ ...extraTopics
1058
+ ];
1059
+ }
1060
+ if (roleName === 'FlowRbacRole' || roleName === 'FlowRelationshipRole') {
1061
+ return [
1062
+ `arn:aws:sns:\${self:custom.iz_region}:\${self:custom.iz_accountId}:Rbac_\${self:custom.iz_stage}_ValidatorRbac_In`,
1063
+ ...extraTopics
1064
+ ];
1065
+ }
1066
+ return [];
1067
+ }
1068
+
1069
+ function roleExternalLambdaArns(state, roleName) {
1070
+ if (!state.rbac) {
1071
+ return [];
1072
+ }
1073
+ const extraTags = getRbacValidatorServiceTags(state).filter(
1074
+ (tag) => tag && tag !== 'Rbac'
1075
+ );
1076
+ const extraLambdas = extraTags.map(
1077
+ (tag) =>
1078
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:${tag}\${self:custom.iz_stage}ValidatorRbacHdrInv`
1079
+ );
1080
+ if (
1081
+ roleName === 'FlowObjectsRole' ||
1082
+ roleName === 'FlowRbacRole' ||
1083
+ roleName === 'FlowRelationshipRole'
1084
+ ) {
1085
+ return [
1086
+ `arn:aws:lambda:\${self:custom.iz_region}:\${self:custom.iz_accountId}:function:Rbac\${self:custom.iz_stage}ValidatorRbacHdrInv`,
1087
+ ...extraLambdas
1088
+ ];
1089
+ }
1090
+ return [];
1091
+ }
1092
+
1026
1093
  function queueArns(queueNames) {
1027
1094
  return [...new Set(queueNames)]
1028
1095
  .sort()
@@ -58,7 +58,8 @@ export function generatedLambdaRole() {
58
58
  Resource: [
59
59
  `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In', 'Rbac')}`,
60
60
  `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ConfirmUsage_In', 'Rbac')}`,
61
- `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'CancelUsage_In', 'Rbac')}`<% (rbacValidatorServiceTags || []).forEach(function(tag) { %>,
61
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'CancelUsage_In', 'Rbac')}`<% if (rbac) { %>,
62
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In')}`<% } %><% (rbacValidatorServiceTags || []).forEach(function(tag) { %>,
62
63
  `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In', '<%= tag %>')}`<% }); %>
63
64
  ]
64
65
  }<% } %>
@@ -77,7 +78,8 @@ export function generatedLambdaRole() {
77
78
  Effect: 'Allow',
78
79
  Action: ['sns:Publish'],
79
80
  Resource: [
80
- `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In', 'Rbac')}`<% (rbacValidatorServiceTags || []).forEach(function(tag) { %>,
81
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In', 'Rbac')}`,
82
+ `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In')}`<% (rbacValidatorServiceTags || []).forEach(function(tag) { %>,
81
83
  `arn:aws:sns:${process.env.iz_region}:${process.env.iz_accountId}:${snsSharedLib.snsTopicNameByFlowSchema(_izContext, 'ValidatorRbac_In', '<%= tag %>')}`<% }); %>
82
84
  ]
83
85
  }