@cxbuilder/flow-config 1.0.1 → 1.1.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/.jsii CHANGED
@@ -20,7 +20,6 @@
20
20
  "yaml": "^2.8.0"
21
21
  },
22
22
  "dependencies": {
23
- "@aws-solutions-constructs/aws-openapigateway-lambda": "^2.85.2",
24
23
  "aws-cdk-lib": "2.194.0",
25
24
  "constructs": "^10.0.0"
26
25
  },
@@ -107,78 +106,6 @@
107
106
  }
108
107
  }
109
108
  },
110
- "@aws-solutions-constructs/aws-openapigateway-lambda": {
111
- "targets": {
112
- "dotnet": {
113
- "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png",
114
- "namespace": "Amazon.SolutionsConstructs.AWS.OpenApiGatewayLambda",
115
- "packageId": "Amazon.SolutionsConstructs.AWS.OpenApiGatewayLambda",
116
- "signAssembly": true
117
- },
118
- "java": {
119
- "maven": {
120
- "artifactId": "openapigatewaylambda",
121
- "groupId": "software.amazon.awsconstructs"
122
- },
123
- "package": "software.amazon.awsconstructs.services.openapigatewaylambda"
124
- },
125
- "js": {
126
- "npm": "@aws-solutions-constructs/aws-openapigateway-lambda"
127
- },
128
- "python": {
129
- "distName": "aws-solutions-constructs.aws-openapigateway-lambda",
130
- "module": "aws_solutions_constructs.aws_openapigateway_lambda"
131
- }
132
- }
133
- },
134
- "@aws-solutions-constructs/core": {
135
- "targets": {
136
- "dotnet": {
137
- "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png",
138
- "namespace": "Amazon.SolutionsConstructs.AWS.Core",
139
- "packageId": "Amazon.SolutionsConstructs.AWS.Core",
140
- "signAssembly": true
141
- },
142
- "java": {
143
- "maven": {
144
- "artifactId": "core",
145
- "groupId": "software.amazon.awsconstructs"
146
- },
147
- "package": "software.amazon.awsconstructs.services.core"
148
- },
149
- "js": {
150
- "npm": "@aws-solutions-constructs/core"
151
- },
152
- "python": {
153
- "distName": "aws-solutions-constructs.core",
154
- "module": "aws_solutions_constructs.core"
155
- }
156
- }
157
- },
158
- "@aws-solutions-constructs/resources": {
159
- "targets": {
160
- "dotnet": {
161
- "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png",
162
- "namespace": "Amazon.SolutionsConstructs.AWS.Resources",
163
- "packageId": "Amazon.SolutionsConstructs.AWS.Resources",
164
- "signAssembly": true
165
- },
166
- "java": {
167
- "maven": {
168
- "artifactId": "resources",
169
- "groupId": "software.amazon.awsconstructs"
170
- },
171
- "package": "software.amazon.awsconstructs.services.resources"
172
- },
173
- "js": {
174
- "npm": "@aws-solutions-constructs/resources"
175
- },
176
- "python": {
177
- "distName": "aws-solutions-constructs.resources",
178
- "module": "aws_solutions_constructs.resources"
179
- }
180
- }
181
- },
182
109
  "aws-cdk-lib": {
183
110
  "submodules": {
184
111
  "aws-cdk-lib.alexa_ask": {
@@ -4089,7 +4016,7 @@
4089
4016
  "kind": "interface",
4090
4017
  "locationInModule": {
4091
4018
  "filename": "infrastructure/FlowConfigStack.ts",
4092
- "line": 23
4019
+ "line": 24
4093
4020
  },
4094
4021
  "name": "CognitoConfig",
4095
4022
  "properties": [
@@ -4102,7 +4029,7 @@
4102
4029
  "immutable": true,
4103
4030
  "locationInModule": {
4104
4031
  "filename": "infrastructure/FlowConfigStack.ts",
4105
- "line": 29
4032
+ "line": 30
4106
4033
  },
4107
4034
  "name": "domain",
4108
4035
  "type": {
@@ -4117,7 +4044,7 @@
4117
4044
  "immutable": true,
4118
4045
  "locationInModule": {
4119
4046
  "filename": "infrastructure/FlowConfigStack.ts",
4120
- "line": 24
4047
+ "line": 25
4121
4048
  },
4122
4049
  "name": "userPoolId",
4123
4050
  "type": {
@@ -4134,7 +4061,7 @@
4134
4061
  "immutable": true,
4135
4062
  "locationInModule": {
4136
4063
  "filename": "infrastructure/FlowConfigStack.ts",
4137
- "line": 34
4064
+ "line": 35
4138
4065
  },
4139
4066
  "name": "ssoProviderName",
4140
4067
  "optional": true,
@@ -4158,7 +4085,7 @@
4158
4085
  },
4159
4086
  "locationInModule": {
4160
4087
  "filename": "infrastructure/FlowConfigStack.ts",
4161
- "line": 142
4088
+ "line": 143
4162
4089
  },
4163
4090
  "parameters": [
4164
4091
  {
@@ -4184,7 +4111,7 @@
4184
4111
  "kind": "class",
4185
4112
  "locationInModule": {
4186
4113
  "filename": "infrastructure/FlowConfigStack.ts",
4187
- "line": 117
4114
+ "line": 118
4188
4115
  },
4189
4116
  "methods": [
4190
4117
  {
@@ -4194,7 +4121,7 @@
4194
4121
  },
4195
4122
  "locationInModule": {
4196
4123
  "filename": "infrastructure/FlowConfigStack.ts",
4197
- "line": 233
4124
+ "line": 267
4198
4125
  },
4199
4126
  "name": "associate3pApp"
4200
4127
  },
@@ -4204,7 +4131,7 @@
4204
4131
  },
4205
4132
  "locationInModule": {
4206
4133
  "filename": "infrastructure/FlowConfigStack.ts",
4207
- "line": 205
4134
+ "line": 207
4208
4135
  },
4209
4136
  "name": "createUserPoolClient",
4210
4137
  "returns": {
@@ -4212,6 +4139,17 @@
4212
4139
  "fqn": "aws-cdk-lib.aws_cognito.UserPoolClient"
4213
4140
  }
4214
4141
  }
4142
+ },
4143
+ {
4144
+ "docs": {
4145
+ "stability": "stable",
4146
+ "summary": "Create Cognito User Groups for role-based access control."
4147
+ },
4148
+ "locationInModule": {
4149
+ "filename": "infrastructure/FlowConfigStack.ts",
4150
+ "line": 235
4151
+ },
4152
+ "name": "createUserPoolGroups"
4215
4153
  }
4216
4154
  ],
4217
4155
  "name": "FlowConfigStack",
@@ -4223,7 +4161,7 @@
4223
4161
  "immutable": true,
4224
4162
  "locationInModule": {
4225
4163
  "filename": "infrastructure/FlowConfigStack.ts",
4226
- "line": 137
4164
+ "line": 138
4227
4165
  },
4228
4166
  "name": "appUrl",
4229
4167
  "type": {
@@ -4236,7 +4174,7 @@
4236
4174
  },
4237
4175
  "locationInModule": {
4238
4176
  "filename": "infrastructure/FlowConfigStack.ts",
4239
- "line": 121
4177
+ "line": 122
4240
4178
  },
4241
4179
  "name": "alertTopic",
4242
4180
  "type": {
@@ -4249,7 +4187,7 @@
4249
4187
  },
4250
4188
  "locationInModule": {
4251
4189
  "filename": "infrastructure/FlowConfigStack.ts",
4252
- "line": 145
4190
+ "line": 146
4253
4191
  },
4254
4192
  "name": "props",
4255
4193
  "type": {
@@ -4262,7 +4200,7 @@
4262
4200
  },
4263
4201
  "locationInModule": {
4264
4202
  "filename": "infrastructure/FlowConfigStack.ts",
4265
- "line": 122
4203
+ "line": 123
4266
4204
  },
4267
4205
  "name": "table",
4268
4206
  "type": {
@@ -4275,7 +4213,7 @@
4275
4213
  },
4276
4214
  "locationInModule": {
4277
4215
  "filename": "infrastructure/FlowConfigStack.ts",
4278
- "line": 118
4216
+ "line": 119
4279
4217
  },
4280
4218
  "name": "userPool",
4281
4219
  "type": {
@@ -4288,7 +4226,7 @@
4288
4226
  },
4289
4227
  "locationInModule": {
4290
4228
  "filename": "infrastructure/FlowConfigStack.ts",
4291
- "line": 120
4229
+ "line": 121
4292
4230
  },
4293
4231
  "name": "userPoolClient",
4294
4232
  "type": {
@@ -4311,7 +4249,7 @@
4311
4249
  "kind": "interface",
4312
4250
  "locationInModule": {
4313
4251
  "filename": "infrastructure/FlowConfigStack.ts",
4314
- "line": 88
4252
+ "line": 89
4315
4253
  },
4316
4254
  "name": "FlowConfigStackProps",
4317
4255
  "properties": [
@@ -4324,7 +4262,7 @@
4324
4262
  "immutable": true,
4325
4263
  "locationInModule": {
4326
4264
  "filename": "infrastructure/FlowConfigStack.ts",
4327
- "line": 99
4265
+ "line": 100
4328
4266
  },
4329
4267
  "name": "alertEmails",
4330
4268
  "type": {
@@ -4344,7 +4282,7 @@
4344
4282
  "immutable": true,
4345
4283
  "locationInModule": {
4346
4284
  "filename": "infrastructure/FlowConfigStack.ts",
4347
- "line": 93
4285
+ "line": 94
4348
4286
  },
4349
4287
  "name": "cognito",
4350
4288
  "type": {
@@ -4359,7 +4297,7 @@
4359
4297
  "immutable": true,
4360
4298
  "locationInModule": {
4361
4299
  "filename": "infrastructure/FlowConfigStack.ts",
4362
- "line": 94
4300
+ "line": 95
4363
4301
  },
4364
4302
  "name": "connectInstanceArn",
4365
4303
  "type": {
@@ -4375,7 +4313,7 @@
4375
4313
  "immutable": true,
4376
4314
  "locationInModule": {
4377
4315
  "filename": "infrastructure/FlowConfigStack.ts",
4378
- "line": 92
4316
+ "line": 93
4379
4317
  },
4380
4318
  "name": "prefix",
4381
4319
  "type": {
@@ -4392,7 +4330,7 @@
4392
4330
  "immutable": true,
4393
4331
  "locationInModule": {
4394
4332
  "filename": "infrastructure/FlowConfigStack.ts",
4395
- "line": 114
4333
+ "line": 115
4396
4334
  },
4397
4335
  "name": "globalTable",
4398
4336
  "optional": true,
@@ -4408,7 +4346,7 @@
4408
4346
  "immutable": true,
4409
4347
  "locationInModule": {
4410
4348
  "filename": "infrastructure/FlowConfigStack.ts",
4411
- "line": 100
4349
+ "line": 101
4412
4350
  },
4413
4351
  "name": "prod",
4414
4352
  "optional": true,
@@ -4426,7 +4364,7 @@
4426
4364
  "immutable": true,
4427
4365
  "locationInModule": {
4428
4366
  "filename": "infrastructure/FlowConfigStack.ts",
4429
- "line": 107
4367
+ "line": 108
4430
4368
  },
4431
4369
  "name": "vpc",
4432
4370
  "optional": true,
@@ -4448,7 +4386,7 @@
4448
4386
  "kind": "interface",
4449
4387
  "locationInModule": {
4450
4388
  "filename": "infrastructure/FlowConfigStack.ts",
4451
- "line": 65
4389
+ "line": 66
4452
4390
  },
4453
4391
  "name": "GlobalTableConfig",
4454
4392
  "properties": [
@@ -4461,7 +4399,7 @@
4461
4399
  "immutable": true,
4462
4400
  "locationInModule": {
4463
4401
  "filename": "infrastructure/FlowConfigStack.ts",
4464
- "line": 69
4402
+ "line": 70
4465
4403
  },
4466
4404
  "name": "isPrimaryRegion",
4467
4405
  "type": {
@@ -4477,7 +4415,7 @@
4477
4415
  "immutable": true,
4478
4416
  "locationInModule": {
4479
4417
  "filename": "infrastructure/FlowConfigStack.ts",
4480
- "line": 75
4418
+ "line": 76
4481
4419
  },
4482
4420
  "name": "replicaRegions",
4483
4421
  "optional": true,
@@ -4504,7 +4442,7 @@
4504
4442
  "kind": "interface",
4505
4443
  "locationInModule": {
4506
4444
  "filename": "infrastructure/FlowConfigStack.ts",
4507
- "line": 40
4445
+ "line": 41
4508
4446
  },
4509
4447
  "name": "VpcConfig",
4510
4448
  "properties": [
@@ -4517,7 +4455,7 @@
4517
4455
  "immutable": true,
4518
4456
  "locationInModule": {
4519
4457
  "filename": "infrastructure/FlowConfigStack.ts",
4520
- "line": 49
4458
+ "line": 50
4521
4459
  },
4522
4460
  "name": "lambdaSecurityGroupIds",
4523
4461
  "type": {
@@ -4538,7 +4476,7 @@
4538
4476
  "immutable": true,
4539
4477
  "locationInModule": {
4540
4478
  "filename": "infrastructure/FlowConfigStack.ts",
4541
- "line": 54
4479
+ "line": 55
4542
4480
  },
4543
4481
  "name": "privateSubnetIds",
4544
4482
  "type": {
@@ -4559,7 +4497,7 @@
4559
4497
  "immutable": true,
4560
4498
  "locationInModule": {
4561
4499
  "filename": "infrastructure/FlowConfigStack.ts",
4562
- "line": 59
4500
+ "line": 60
4563
4501
  },
4564
4502
  "name": "vpcEndpointSecurityGroupIds",
4565
4503
  "type": {
@@ -4580,7 +4518,7 @@
4580
4518
  "immutable": true,
4581
4519
  "locationInModule": {
4582
4520
  "filename": "infrastructure/FlowConfigStack.ts",
4583
- "line": 44
4521
+ "line": 45
4584
4522
  },
4585
4523
  "name": "vpcId",
4586
4524
  "type": {
@@ -4591,6 +4529,6 @@
4591
4529
  "symbolId": "infrastructure/FlowConfigStack:VpcConfig"
4592
4530
  }
4593
4531
  },
4594
- "version": "1.0.1",
4595
- "fingerprint": "V7ICP5NiiXfM53BnBQnXOFRX/nYJTyaFcStmhn/v5jo="
4532
+ "version": "1.1.0",
4533
+ "fingerprint": "HFZB1viUfyeGHk8/FgKUYwz6TaxogYZ+PsdAxRdPVdA="
4596
4534
  }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] - 2025-06-23
9
+
10
+ ### Added
11
+
12
+ - Role-based access control (RBAC) using Amazon Cognito User Groups
13
+ - Three permission levels: FlowConfigAdmin, FlowConfigEdit, and FlowConfigRead
14
+ - Backend permission validation for all API endpoints
15
+ - Frontend UI adapts based on user permissions
16
+ - Read-only mode for users without edit access
17
+ - Access denied screen for users without any FlowConfig permissions
18
+
19
+ ### Changed
20
+
21
+ - Replaced placeholder permission system with full Cognito Groups implementation
22
+ - FlowConfigEdit users can add languages to prompts but cannot remove existing ones
23
+ - FlowConfigEdit users can add/remove channels but cannot modify structure
24
+ - Preview functionality remains available to all permission levels
25
+
26
+ ## [1.0.2] - 2025-06-20
27
+
28
+ - Converted to `SpecRestApi` because `@aws-solutions-constructs/aws-openapigateway-lambda` is not compatible with `Role.customizeRoles`
29
+
8
30
  ## [1.0.0] - 2025-06-18
9
31
 
10
32
  ### Added
@@ -151,6 +151,78 @@ function stripSSML(text) {
151
151
  }
152
152
  __name(stripSSML, "stripSSML");
153
153
 
154
+ // backend/shared/permissions.ts
155
+ var COGNITO_GROUPS = {
156
+ ADMIN: "FlowConfigAdmin",
157
+ EDIT: "FlowConfigEdit",
158
+ READ: "FlowConfigRead"
159
+ };
160
+ function extractCognitoGroups(claims) {
161
+ const groupsClaim = claims["cognito:groups"];
162
+ if (!groupsClaim) {
163
+ return [];
164
+ }
165
+ if (typeof groupsClaim === "string") {
166
+ return groupsClaim.split(",").map((group) => group.trim());
167
+ }
168
+ if (Array.isArray(groupsClaim)) {
169
+ return groupsClaim;
170
+ }
171
+ return [];
172
+ }
173
+ __name(extractCognitoGroups, "extractCognitoGroups");
174
+ function hasFlowConfigAccess(claims) {
175
+ const userGroups = extractCognitoGroups(claims);
176
+ const flowConfigGroups = Object.values(COGNITO_GROUPS);
177
+ return userGroups.some((group) => flowConfigGroups.includes(group));
178
+ }
179
+ __name(hasFlowConfigAccess, "hasFlowConfigAccess");
180
+ function getAccessLevel(claims) {
181
+ const userGroups = extractCognitoGroups(claims);
182
+ if (userGroups.includes(COGNITO_GROUPS.ADMIN)) {
183
+ return "Full";
184
+ }
185
+ if (userGroups.includes(COGNITO_GROUPS.EDIT)) {
186
+ return "Edit";
187
+ }
188
+ if (userGroups.includes(COGNITO_GROUPS.READ)) {
189
+ return "Read";
190
+ }
191
+ return null;
192
+ }
193
+ __name(getAccessLevel, "getAccessLevel");
194
+ function checkActionPermission(claims, action) {
195
+ const accessLevel = getAccessLevel(claims);
196
+ if (!accessLevel) {
197
+ return null;
198
+ }
199
+ switch (action) {
200
+ case "Read":
201
+ return accessLevel;
202
+ case "Edit":
203
+ if (accessLevel === "Edit" || accessLevel === "Full") {
204
+ return accessLevel;
205
+ }
206
+ return null;
207
+ case "Create":
208
+ case "Delete":
209
+ if (accessLevel === "Full") {
210
+ return accessLevel;
211
+ }
212
+ return null;
213
+ default:
214
+ return null;
215
+ }
216
+ }
217
+ __name(checkActionPermission, "checkActionPermission");
218
+ function validateFlowConfigPermission(claims, _flowConfigId, action) {
219
+ if (!hasFlowConfigAccess(claims)) {
220
+ return null;
221
+ }
222
+ return checkActionPermission(claims, action);
223
+ }
224
+ __name(validateFlowConfigPermission, "validateFlowConfigPermission");
225
+
154
226
  // backend/FlowConfig.ts
155
227
  var env = process.env;
156
228
  var client2 = new import_client_dynamodb.DynamoDBClient();
@@ -198,7 +270,7 @@ async function listFlowConfigs(event, claims) {
198
270
  }
199
271
  const resultItems = [];
200
272
  for (const config of filteredConfigs) {
201
- const accessLevel = await checkPermissions(claims, config.id, "Read");
273
+ const accessLevel = validateFlowConfigPermission(claims, config.id, "Read");
202
274
  if (accessLevel) {
203
275
  resultItems.push({
204
276
  id: config.id,
@@ -216,7 +288,7 @@ async function listFlowConfigs(event, claims) {
216
288
  __name(listFlowConfigs, "listFlowConfigs");
217
289
  async function getFlowConfig(flowConfigId, claims) {
218
290
  try {
219
- const accessLevel = await checkPermissions(claims, flowConfigId, "Read");
291
+ const accessLevel = validateFlowConfigPermission(claims, flowConfigId, "Read");
220
292
  if (!accessLevel) {
221
293
  return respondMessage(403, "Access denied");
222
294
  }
@@ -278,10 +350,16 @@ async function saveFlowConfig(flowConfigId, event, claims) {
278
350
  const response = await docClient.send(getCommand);
279
351
  const existingConfig = response.Item;
280
352
  const action = existingConfig ? "Edit" : "Create";
281
- const accessLevel = await checkPermissions(claims, flowConfigId, action);
353
+ const accessLevel = validateFlowConfigPermission(claims, flowConfigId, action);
282
354
  if (!accessLevel) {
283
355
  return respondMessage(403, "Access denied");
284
356
  }
357
+ if (accessLevel === "Edit" && existingConfig) {
358
+ const structuralChangeError = validateEditOnlyChanges(existingConfig, body);
359
+ if (structuralChangeError) {
360
+ return respondMessage(403, `FlowConfigEdit users cannot make structural changes: ${structuralChangeError}`);
361
+ }
362
+ }
285
363
  const putCommand = new import_lib_dynamodb.PutCommand({
286
364
  TableName: env.FLOW_CONFIGS_TABLE_NAME,
287
365
  Item: body
@@ -296,7 +374,7 @@ async function saveFlowConfig(flowConfigId, event, claims) {
296
374
  __name(saveFlowConfig, "saveFlowConfig");
297
375
  async function deleteFlowConfig(flowConfigId, claims) {
298
376
  try {
299
- const accessLevel = await checkPermissions(claims, flowConfigId, "Delete");
377
+ const accessLevel = validateFlowConfigPermission(claims, flowConfigId, "Delete");
300
378
  if (!accessLevel) {
301
379
  return respondMessage(403, "Access denied");
302
380
  }
@@ -355,7 +433,7 @@ async function previewFlowConfig(event, claims) {
355
433
  "FlowConfig must have id, description, variables, and prompts"
356
434
  );
357
435
  }
358
- const accessLevel = await checkPermissions(claims, flowConfig.id, "Read");
436
+ const accessLevel = validateFlowConfigPermission(claims, flowConfig.id, "Read");
359
437
  if (!accessLevel) {
360
438
  return respondMessage(403, "Access denied");
361
439
  }
@@ -369,18 +447,34 @@ async function previewFlowConfig(event, claims) {
369
447
  }
370
448
  }
371
449
  __name(previewFlowConfig, "previewFlowConfig");
372
- async function checkPermissions(_claims, flowConfigId, action) {
373
- try {
374
- return Promise.resolve("Full");
375
- } catch (error) {
376
- console.error(
377
- `Error checking permissions for ${flowConfigId}, action ${action}:`,
378
- error
379
- );
380
- return null;
450
+ function validateEditOnlyChanges(existingConfig, newConfig) {
451
+ if (existingConfig.description !== newConfig.description) {
452
+ return "Cannot modify description";
453
+ }
454
+ const existingVarKeys = Object.keys(existingConfig.variables || {}).sort();
455
+ const newVarKeys = Object.keys(newConfig.variables || {}).sort();
456
+ if (existingVarKeys.length !== newVarKeys.length || !existingVarKeys.every((key, index) => key === newVarKeys[index])) {
457
+ return "Cannot add or remove variables";
458
+ }
459
+ const existingPromptKeys = Object.keys(existingConfig.prompts || {}).sort();
460
+ const newPromptKeys = Object.keys(newConfig.prompts || {}).sort();
461
+ if (existingPromptKeys.length !== newPromptKeys.length || !existingPromptKeys.every((key, index) => key === newPromptKeys[index])) {
462
+ return "Cannot add or remove prompts";
463
+ }
464
+ for (const promptName of existingPromptKeys) {
465
+ const existingPrompt = existingConfig.prompts[promptName];
466
+ const newPrompt = newConfig.prompts[promptName];
467
+ const existingLangs = Object.keys(existingPrompt || {});
468
+ const newLangs = Object.keys(newPrompt || {});
469
+ for (const existingLang of existingLangs) {
470
+ if (!newLangs.includes(existingLang)) {
471
+ return `Cannot remove language ${existingLang} from prompt ${promptName}`;
472
+ }
473
+ }
381
474
  }
475
+ return null;
382
476
  }
383
- __name(checkPermissions, "checkPermissions");
477
+ __name(validateEditOnlyChanges, "validateEditOnlyChanges");
384
478
  // Annotate the CommonJS export names for ESM import in node:
385
479
  0 && (module.exports = {
386
480
  handler