cdk-factory 0.8.6__tar.gz → 0.8.7__tar.gz

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.

Potentially problematic release.


This version of cdk-factory might be problematic. Click here for more details.

Files changed (149) hide show
  1. cdk_factory-0.8.7/LAMBDA_PERMISSION_FIX_SUMMARY.md +56 -0
  2. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/PKG-INFO +1 -1
  3. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/pyproject.toml +1 -1
  4. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/cognito.py +63 -0
  5. cdk_factory-0.8.7/src/cdk_factory/stack_library/cognito/cognito_stack.py +539 -0
  6. cdk_factory-0.8.7/src/cdk_factory/version.py +1 -0
  7. cdk_factory-0.8.6/src/cdk_factory/stack_library/cognito/cognito_stack.py +0 -181
  8. cdk_factory-0.8.6/src/cdk_factory/version.py +0 -1
  9. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/.gitignore +0 -0
  10. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/.windsurfrules +0 -0
  11. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/BUG_FIX_SSM_IMPORTS_METADATA_FIELDS.md +0 -0
  12. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/CHANGELOG_v0.8.1.md +0 -0
  13. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/CHANGELOG_v0.8.2.md +0 -0
  14. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/CHANGELOG_v0.8.3.md +0 -0
  15. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/LICENSE +0 -0
  16. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/README.md +0 -0
  17. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/RELEASE_NOTES_v0.8.2.md +0 -0
  18. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/SUMMARY_v0.8.2.md +0 -0
  19. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/SUMMARY_v0.8.3.md +0 -0
  20. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/archive/README.md +0 -0
  21. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/archive/migrate_to_enhanced_ssm.py +0 -0
  22. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/json-imports/README.md +0 -0
  23. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/README.md +0 -0
  24. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/api-gateway-stack.json +0 -0
  25. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/config.json +0 -0
  26. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/lambda-stack.json +0 -0
  27. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/mypy.ini +0 -0
  28. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/publish_to_pypi.py +0 -0
  29. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/publish_to_pypi.sh +0 -0
  30. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/pysetup.py +0 -0
  31. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/pysetup.sh +0 -0
  32. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/requirements.dev.txt +0 -0
  33. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/requirements.tests.txt +0 -0
  34. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/requirements.txt +0 -0
  35. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/run-checks.sh +0 -0
  36. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/run-tests-clean-venv.sh +0 -0
  37. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/run-tests.sh +0 -0
  38. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/__init__.py +0 -0
  39. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/app.py +0 -0
  40. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/builds/README.md +0 -0
  41. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/cdk.json +0 -0
  42. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/commands/command_loader.py +0 -0
  43. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/base_config.py +0 -0
  44. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/cdk_config.py +0 -0
  45. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/deployment.py +0 -0
  46. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/deployment_wave.py +0 -0
  47. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/devops.py +0 -0
  48. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/enhanced_base_config.py +0 -0
  49. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/enhanced_ssm_config.py +0 -0
  50. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/management.py +0 -0
  51. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/pipeline.py +0 -0
  52. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/pipeline_stage.py +0 -0
  53. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/_resources.py +0 -0
  54. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/api_gateway.py +0 -0
  55. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/apigateway_route_config.py +0 -0
  56. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/auto_scaling.py +0 -0
  57. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/cloudfront.py +0 -0
  58. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/cloudwatch_widget.py +0 -0
  59. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/code_artifact.py +0 -0
  60. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/code_artifact_login.py +0 -0
  61. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/code_repository.py +0 -0
  62. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/docker.py +0 -0
  63. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/dynamodb.py +0 -0
  64. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/ecr.py +0 -0
  65. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/exisiting.py +0 -0
  66. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/lambda_function.py +0 -0
  67. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/lambda_layers.py +0 -0
  68. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/lambda_triggers.py +0 -0
  69. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/load_balancer.py +0 -0
  70. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/rds.py +0 -0
  71. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/resource_mapping.py +0 -0
  72. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/resource_naming.py +0 -0
  73. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/resource_types.py +0 -0
  74. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/route53.py +0 -0
  75. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/route53_hosted_zone.py +0 -0
  76. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/rum.py +0 -0
  77. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/s3.py +0 -0
  78. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/security_group.py +0 -0
  79. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/security_group_full_stack.py +0 -0
  80. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/sqs.py +0 -0
  81. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/vpc.py +0 -0
  82. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/stack.py +0 -0
  83. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/workload.py +0 -0
  84. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +0 -0
  85. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/ecr/ecr_construct.py +0 -0
  86. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/lambda_function_construct.py +0 -0
  87. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/lambda_function_docker_construct.py +0 -0
  88. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/lambda_function_role_construct.py +0 -0
  89. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/policies/policy_docs.py +0 -0
  90. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/policies/policy_statements.py +0 -0
  91. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/s3_buckets/s3_bucket_construct.py +0 -0
  92. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_destination_construct.py +0 -0
  93. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_source_construct.py +0 -0
  94. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/sqs/policies/sqs_policies.py +0 -0
  95. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -0
  96. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/istack.py +0 -0
  97. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/live_ssm_resolver.py +0 -0
  98. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/ssm_parameter_mixin.py +0 -0
  99. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/lambdas/health_handler.py +0 -0
  100. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/pipeline_factory.py +0 -0
  101. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/security/policies.py +0 -0
  102. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/security/roles.py +0 -0
  103. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/stage.py +0 -0
  104. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/istack.py +0 -0
  105. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_factory.py +0 -0
  106. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_module_loader.py +0 -0
  107. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_module_registry.py +0 -0
  108. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_modules.py +0 -0
  109. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/__init__.py +0 -0
  110. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/api_gateway/api_gateway_stack.py +0 -0
  111. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/auto_scaling/__init__.py +0 -0
  112. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +0 -0
  113. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/aws_lambdas/lambda_stack.py +0 -0
  114. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/buckets/README.md +0 -0
  115. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/buckets/bucket_stack.py +0 -0
  116. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/code_artifact/code_artifact_stack.py +0 -0
  117. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/dynamodb/dynamodb_stack.py +0 -0
  118. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/ecr/README.md +0 -0
  119. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/ecr/ecr_stack.py +0 -0
  120. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/load_balancer/__init__.py +0 -0
  121. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/load_balancer/load_balancer_stack.py +0 -0
  122. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rds/__init__.py +0 -0
  123. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rds/rds_stack.py +0 -0
  124. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/route53/__init__.py +0 -0
  125. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/route53/route53_stack.py +0 -0
  126. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rum/__init__.py +0 -0
  127. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rum/rum_stack.py +0 -0
  128. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/security_group/__init__.py +0 -0
  129. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/security_group/security_group_full_stack.py +0 -0
  130. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/security_group/security_group_stack.py +0 -0
  131. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/simple_queue_service/sqs_stack.py +0 -0
  132. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/stack_base.py +0 -0
  133. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/vpc/__init__.py +0 -0
  134. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/vpc/vpc_stack.py +0 -0
  135. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/websites/static_website_stack.py +0 -0
  136. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stages/websites/static_website_stage.py +0 -0
  137. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/api_gateway_integration_utility.py +0 -0
  138. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/commandline_args.py +0 -0
  139. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/configuration_loader.py +0 -0
  140. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/docker_utilities.py +0 -0
  141. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/environment_services.py +0 -0
  142. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/file_operations.py +0 -0
  143. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/git_utilities.py +0 -0
  144. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/json_loading_utility.py +0 -0
  145. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/lambda_function_utilities.py +0 -0
  146. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/os_execute.py +0 -0
  147. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utils/api_gateway_utilities.py +0 -0
  148. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/workload/workload_factory.py +0 -0
  149. {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/handlers/test/handler.py +0 -0
@@ -0,0 +1,56 @@
1
+ # Lambda Permission Fix - Quick Summary
2
+
3
+ ## The Problem
4
+ ```
5
+ Execution failed due to configuration error: Invalid permissions on Lambda function
6
+ ```
7
+
8
+ ## The Root Cause
9
+ When importing Lambda from SSM (`lambda_name` or `lambda_arn_ssm_path`), API Gateway didn't have permission to invoke it.
10
+
11
+ ## The Fix (One Line Change)
12
+ Changed from:
13
+ ```python
14
+ lambda_fn = _lambda.Function.from_function_arn(self, id, lambda_arn)
15
+ ```
16
+
17
+ To:
18
+ ```python
19
+ lambda_fn = _lambda.Function.from_function_attributes(
20
+ self, id,
21
+ function_arn=lambda_arn,
22
+ same_environment=True # ← This is the key!
23
+ )
24
+ ```
25
+
26
+ Plus adding explicit permission:
27
+ ```python
28
+ _lambda.CfnPermission(
29
+ self, f"lambda-permission-{suffix}",
30
+ action="lambda:InvokeFunction",
31
+ function_name=lambda_fn.function_arn,
32
+ principal="apigateway.amazonaws.com",
33
+ source_arn=f"arn:aws:execute-api:{region}:{account}:{api_id}/*/{method}{path}"
34
+ )
35
+ ```
36
+
37
+ ## Why `same_environment=True`?
38
+ - Tells CDK the Lambda is in the same account/region
39
+ - Allows adding `CfnPermission` without validation errors
40
+ - `from_function_arn()` creates read-only references that block permissions
41
+
42
+ ## What Gets Created
43
+ A CloudFormation `AWS::Lambda::Permission` resource that grants your API Gateway invoke access to the Lambda.
44
+
45
+ ## Files Changed
46
+ - `src/cdk_factory/stack_library/api_gateway/api_gateway_stack.py` - The fix
47
+ - `tests/unit/test_api_gateway_lambda_permission.py` - New tests
48
+ - `docs/api_gateway_lambda_permissions_fix.md` - Full documentation
49
+
50
+ ## Applies To
51
+ ✅ Routes with `lambda_name` (SSM auto-discovery)
52
+ ✅ Routes with `lambda_arn_ssm_path` (explicit SSM path)
53
+ ❌ Routes with `src` (inline Lambda - already works)
54
+
55
+ ## Deployment
56
+ Just redeploy your API Gateway stack - no Lambda stack changes needed!
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdk_factory
3
- Version: 0.8.6
3
+ Version: 0.8.7
4
4
  Summary: CDK Factory. A QuickStarter and best practices setup for CDK projects
5
5
  Author-email: Eric Wilson <eric.wilson@geekcafe.com>
6
6
  License: MIT License
@@ -33,7 +33,7 @@ markers = [
33
33
  [project]
34
34
 
35
35
  name = "cdk_factory"
36
- version = "0.8.6"
36
+ version = "0.8.7"
37
37
  authors = [
38
38
  { name="Eric Wilson", email="eric.wilson@geekcafe.com" }
39
39
  ]
@@ -243,3 +243,66 @@ class CognitoConfig(EnhancedBaseConfig):
243
243
  def ssm(self) -> Dict[str, Any]:
244
244
  """Whether to export the user pool name (default: False)"""
245
245
  return self.__config.get("ssm", {})
246
+
247
+ @property
248
+ def app_clients(self) -> list | None:
249
+ """
250
+ App clients for the user pool.
251
+ Supports multiple clients with different auth flows and OAuth settings.
252
+
253
+ Structure:
254
+ [{
255
+ "name": "web-client",
256
+ "generate_secret": False,
257
+ "auth_flows": {
258
+ "user_password": True,
259
+ "user_srp": True,
260
+ "custom": False,
261
+ "admin_user_password": False
262
+ },
263
+ "oauth": {
264
+ "flows": {
265
+ "authorization_code_grant": True,
266
+ "implicit_code_grant": False,
267
+ "client_credentials": False
268
+ },
269
+ "scopes": ["email", "openid", "profile"],
270
+ "callback_urls": ["https://example.com/callback"],
271
+ "logout_urls": ["https://example.com/logout"]
272
+ },
273
+ "supported_identity_providers": ["COGNITO"],
274
+ "prevent_user_existence_errors": True,
275
+ "enable_token_revocation": True,
276
+ "access_token_validity": {"minutes": 60},
277
+ "id_token_validity": {"minutes": 60},
278
+ "refresh_token_validity": {"days": 30},
279
+ "read_attributes": ["email", "name"],
280
+ "write_attributes": ["name"]
281
+ }]
282
+
283
+ Example:
284
+ [
285
+ {
286
+ "name": "web-app",
287
+ "generate_secret": False,
288
+ "auth_flows": {
289
+ "user_password": True,
290
+ "user_srp": True
291
+ }
292
+ },
293
+ {
294
+ "name": "backend-service",
295
+ "generate_secret": True,
296
+ "oauth": {
297
+ "flows": {
298
+ "client_credentials": True
299
+ },
300
+ "scopes": ["api/read", "api/write"]
301
+ }
302
+ }
303
+ ]
304
+
305
+ Returns:
306
+ list: List of app client configurations
307
+ """
308
+ return self.__config.get("app_clients")
@@ -0,0 +1,539 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import aws_cdk as cdk
8
+ from aws_cdk import aws_cognito as cognito
9
+ from aws_cdk import aws_secretsmanager as secretsmanager
10
+ from aws_cdk import aws_iam as iam
11
+ from aws_cdk import aws_lambda as _lambda
12
+ from aws_cdk import custom_resources as cr
13
+ from constructs import Construct
14
+ from aws_lambda_powertools import Logger
15
+ from aws_cdk import aws_ssm as ssm
16
+ from cdk_factory.configurations.deployment import DeploymentConfig
17
+ from cdk_factory.configurations.resources.cognito import CognitoConfig
18
+ from cdk_factory.configurations.stack import StackConfig
19
+ from cdk_factory.interfaces.istack import IStack
20
+ from cdk_factory.interfaces.enhanced_ssm_parameter_mixin import EnhancedSsmParameterMixin
21
+ from cdk_factory.stack.stack_module_registry import register_stack
22
+ from cdk_factory.workload.workload_factory import WorkloadConfig
23
+
24
+ logger = Logger(__name__)
25
+
26
+
27
+ @register_stack("cognito_library_module")
28
+ @register_stack("cognito_stack")
29
+ class CognitoStack(IStack, EnhancedSsmParameterMixin):
30
+ """
31
+ Cognito Stack - Creates a Cognito User Pool with configurable settings.
32
+ """
33
+
34
+ def __init__(self, scope: Construct, id: str, **kwargs) -> None:
35
+ super().__init__(scope, id, **kwargs)
36
+ self.id = id
37
+ self.stack_config: StackConfig | None = None
38
+ self.deployment: DeploymentConfig | None = None
39
+ self.cognito_config: CognitoConfig | None = None
40
+ self.user_pool: cognito.UserPool | None = None
41
+ self.app_clients: dict = {} # Store created app clients by name
42
+
43
+ def build(
44
+ self,
45
+ stack_config: StackConfig,
46
+ deployment: DeploymentConfig,
47
+ workload: WorkloadConfig,
48
+ ) -> None:
49
+ """Build the stack"""
50
+ self.stack_config = stack_config
51
+ self.deployment = deployment
52
+ self.cognito_config = CognitoConfig(stack_config.dictionary.get("cognito", {}))
53
+
54
+ # Create user pool with configuration
55
+ self._create_user_pool_with_config()
56
+
57
+ # Create app clients if configured
58
+ if self.cognito_config.app_clients:
59
+ self._create_app_clients()
60
+
61
+ def _setup_custom_attributes(self):
62
+ attributes = {}
63
+ if self.cognito_config.custom_attributes:
64
+ for custom_attribute in self.cognito_config.custom_attributes:
65
+ if not custom_attribute.get("name"):
66
+ raise ValueError("Custom attribute name is required")
67
+ name = custom_attribute.get("name")
68
+ if "custom:" in name:
69
+ name = name.replace("custom:", "")
70
+
71
+ # Use StringAttribute for custom attributes (most common type)
72
+ # In a more complete implementation, we could support different attribute types
73
+ # based on a 'type' field in the custom_attribute dict
74
+ attributes[name] = cognito.StringAttribute(
75
+ mutable=custom_attribute.get("mutable", True),
76
+ max_len=custom_attribute.get("max_length", None),
77
+ min_len=custom_attribute.get("min_length", None),
78
+ )
79
+ return attributes
80
+
81
+ def _create_user_pool_with_config(self):
82
+ # Build kwargs for all supported Cognito UserPool parameters
83
+ kwargs = {
84
+ "user_pool_name": self.cognito_config.user_pool_name,
85
+ "self_sign_up_enabled": self.cognito_config.self_sign_up_enabled,
86
+ "sign_in_case_sensitive": self.cognito_config.sign_in_case_sensitive,
87
+ "sign_in_aliases": (
88
+ cognito.SignInAliases(**self.cognito_config.sign_in_aliases)
89
+ if self.cognito_config.sign_in_aliases
90
+ else None
91
+ ),
92
+ "sign_in_policy": self.cognito_config.sign_in_policy,
93
+ "auto_verify": (
94
+ cognito.AutoVerifiedAttrs(**self.cognito_config.auto_verify)
95
+ if self.cognito_config.auto_verify
96
+ else None
97
+ ),
98
+ "custom_attributes": self._setup_custom_attributes(),
99
+ "custom_sender_kms_key": self.cognito_config.custom_sender_kms_key,
100
+ "custom_threat_protection_mode": self.cognito_config.custom_threat_protection_mode,
101
+ "deletion_protection": self.cognito_config.deletion_protection,
102
+ "device_tracking": self.cognito_config.device_tracking,
103
+ "email": self.cognito_config.email,
104
+ "enable_sms_role": self.cognito_config.enable_sms_role,
105
+ "feature_plan": self.cognito_config.feature_plan,
106
+ "keep_original": self.cognito_config.keep_original,
107
+ "lambda_triggers": self.cognito_config.lambda_triggers,
108
+ "mfa": (
109
+ cognito.Mfa[self.cognito_config.mfa]
110
+ if self.cognito_config.mfa
111
+ else None
112
+ ),
113
+ "mfa_message": self.cognito_config.mfa_message,
114
+ "mfa_second_factor": (
115
+ cognito.MfaSecondFactor(**self.cognito_config.mfa_second_factor)
116
+ if self.cognito_config.mfa_second_factor
117
+ else None
118
+ ),
119
+ "passkey_relying_party_id": self.cognito_config.passkey_relying_party_id,
120
+ "passkey_user_verification": self.cognito_config.passkey_user_verification,
121
+ "password_policy": (
122
+ cognito.PasswordPolicy(**self.cognito_config.password_policy)
123
+ if self.cognito_config.password_policy
124
+ else None
125
+ ),
126
+ "removal_policy": (
127
+ cdk.RemovalPolicy[self.cognito_config.removal_policy]
128
+ if self.cognito_config.removal_policy
129
+ else None
130
+ ),
131
+ "account_recovery": (
132
+ cognito.AccountRecovery[self.cognito_config.account_recovery]
133
+ if self.cognito_config.account_recovery
134
+ else None
135
+ ),
136
+ "sms_role": self.cognito_config.sms_role,
137
+ "sms_role_external_id": self.cognito_config.sms_role_external_id,
138
+ "sns_region": self.cognito_config.sns_region,
139
+ "standard_attributes": self.cognito_config.standard_attributes,
140
+ "standard_threat_protection_mode": self.cognito_config.standard_threat_protection_mode,
141
+ "user_invitation": self.cognito_config.user_invitation,
142
+ "user_verification": self.cognito_config.user_verification,
143
+ "advanced_security_mode": (
144
+ cognito.AdvancedSecurityMode[self.cognito_config.advanced_security_mode]
145
+ if self.cognito_config.advanced_security_mode
146
+ else None
147
+ ),
148
+ }
149
+ # Remove None values
150
+ kwargs = {k: v for k, v in kwargs.items() if v is not None}
151
+
152
+ self.user_pool = cognito.UserPool(
153
+ self,
154
+ id=self.deployment.build_resource_name(
155
+ self.cognito_config.user_pool_name
156
+ or self.cognito_config.user_pool_id
157
+ or "user-pool"
158
+ ),
159
+ **kwargs,
160
+ )
161
+ logger.info(f"Created Cognito User Pool: {self.user_pool.user_pool_id}")
162
+
163
+ self._export_ssm_parameters(self.user_pool)
164
+
165
+ def _create_app_clients(self):
166
+ """Create app clients for the user pool based on configuration"""
167
+ if not self.user_pool:
168
+ raise ValueError("User pool must be created before app clients")
169
+
170
+ for client_config in self.cognito_config.app_clients:
171
+ client_name = client_config.get("name")
172
+ if not client_name:
173
+ raise ValueError("App client name is required")
174
+
175
+ # Build authentication flows
176
+ auth_flows = self._build_auth_flows(client_config.get("auth_flows", {}))
177
+
178
+ # Build OAuth settings
179
+ oauth_settings = self._build_oauth_settings(client_config.get("oauth"))
180
+
181
+ # Build token validity settings
182
+ token_validity = self._build_token_validity(client_config)
183
+
184
+ # Build app client kwargs
185
+ client_kwargs = {
186
+ "user_pool": self.user_pool,
187
+ "user_pool_client_name": client_name,
188
+ "generate_secret": client_config.get("generate_secret", False),
189
+ "auth_flows": auth_flows,
190
+ "o_auth": oauth_settings,
191
+ "prevent_user_existence_errors": client_config.get("prevent_user_existence_errors"),
192
+ "enable_token_revocation": client_config.get("enable_token_revocation", True),
193
+ "access_token_validity": token_validity.get("access_token"),
194
+ "id_token_validity": token_validity.get("id_token"),
195
+ "refresh_token_validity": token_validity.get("refresh_token"),
196
+ "read_attributes": self._build_attributes(client_config.get("read_attributes")),
197
+ "write_attributes": self._build_attributes(client_config.get("write_attributes")),
198
+ "supported_identity_providers": self._build_identity_providers(
199
+ client_config.get("supported_identity_providers")
200
+ ),
201
+ }
202
+
203
+ # Remove None values
204
+ client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
205
+
206
+ # Create the app client
207
+ app_client = cognito.UserPoolClient(
208
+ self,
209
+ id=self.deployment.build_resource_name(f"{client_name}-client"),
210
+ **client_kwargs,
211
+ )
212
+
213
+ # Store reference
214
+ self.app_clients[client_name] = app_client
215
+ logger.info(f"Created Cognito App Client: {client_name}")
216
+
217
+ # Store client secret in Secrets Manager if generated
218
+ if client_config.get("generate_secret", False):
219
+ self._store_client_secret_in_secrets_manager(
220
+ client_name, app_client, self.user_pool
221
+ )
222
+
223
+ def _build_auth_flows(self, auth_flows_config: dict) -> cognito.AuthFlow:
224
+ """
225
+ Build authentication flows from configuration.
226
+
227
+ Note: CDK automatically adds ALLOW_REFRESH_TOKEN_AUTH to all app clients,
228
+ which is required for token refresh functionality.
229
+ """
230
+ if not auth_flows_config:
231
+ return None
232
+
233
+ return cognito.AuthFlow(
234
+ user_password=auth_flows_config.get("user_password", False),
235
+ user_srp=auth_flows_config.get("user_srp", False),
236
+ custom=auth_flows_config.get("custom", False),
237
+ admin_user_password=auth_flows_config.get("admin_user_password", False),
238
+ )
239
+
240
+ def _build_oauth_settings(self, oauth_config: dict) -> cognito.OAuthSettings:
241
+ """Build OAuth settings from configuration"""
242
+ if not oauth_config:
243
+ return None
244
+
245
+ # Build OAuth flows
246
+ flows_config = oauth_config.get("flows", {})
247
+ flows = cognito.OAuthFlows(
248
+ authorization_code_grant=flows_config.get("authorization_code_grant", False),
249
+ implicit_code_grant=flows_config.get("implicit_code_grant", False),
250
+ client_credentials=flows_config.get("client_credentials", False),
251
+ )
252
+
253
+ # Build OAuth scopes
254
+ scopes = []
255
+ scope_list = oauth_config.get("scopes", [])
256
+ for scope in scope_list:
257
+ if scope.lower() == "openid":
258
+ scopes.append(cognito.OAuthScope.OPENID)
259
+ elif scope.lower() == "email":
260
+ scopes.append(cognito.OAuthScope.EMAIL)
261
+ elif scope.lower() == "phone":
262
+ scopes.append(cognito.OAuthScope.PHONE)
263
+ elif scope.lower() == "profile":
264
+ scopes.append(cognito.OAuthScope.PROFILE)
265
+ elif scope.lower() == "cognito_admin":
266
+ scopes.append(cognito.OAuthScope.COGNITO_ADMIN)
267
+ else:
268
+ # Custom scope
269
+ scopes.append(cognito.OAuthScope.custom(scope))
270
+
271
+ return cognito.OAuthSettings(
272
+ flows=flows,
273
+ scopes=scopes if scopes else None,
274
+ callback_urls=oauth_config.get("callback_urls"),
275
+ logout_urls=oauth_config.get("logout_urls"),
276
+ )
277
+
278
+ def _build_token_validity(self, client_config: dict) -> dict:
279
+ """Build token validity settings from configuration"""
280
+ result = {}
281
+
282
+ # Access token validity
283
+ if "access_token_validity" in client_config:
284
+ validity = client_config["access_token_validity"]
285
+ if "minutes" in validity:
286
+ result["access_token"] = cdk.Duration.minutes(validity["minutes"])
287
+ elif "hours" in validity:
288
+ result["access_token"] = cdk.Duration.hours(validity["hours"])
289
+ elif "days" in validity:
290
+ result["access_token"] = cdk.Duration.days(validity["days"])
291
+
292
+ # ID token validity
293
+ if "id_token_validity" in client_config:
294
+ validity = client_config["id_token_validity"]
295
+ if "minutes" in validity:
296
+ result["id_token"] = cdk.Duration.minutes(validity["minutes"])
297
+ elif "hours" in validity:
298
+ result["id_token"] = cdk.Duration.hours(validity["hours"])
299
+ elif "days" in validity:
300
+ result["id_token"] = cdk.Duration.days(validity["days"])
301
+
302
+ # Refresh token validity
303
+ if "refresh_token_validity" in client_config:
304
+ validity = client_config["refresh_token_validity"]
305
+ if "minutes" in validity:
306
+ result["refresh_token"] = cdk.Duration.minutes(validity["minutes"])
307
+ elif "hours" in validity:
308
+ result["refresh_token"] = cdk.Duration.hours(validity["hours"])
309
+ elif "days" in validity:
310
+ result["refresh_token"] = cdk.Duration.days(validity["days"])
311
+
312
+ return result
313
+
314
+ def _build_attributes(self, attribute_list: list) -> cognito.ClientAttributes:
315
+ """Build client attributes from configuration"""
316
+ if not attribute_list:
317
+ return None
318
+
319
+ # Standard attributes mapping
320
+ standard_attrs = {
321
+ "address": lambda: cognito.ClientAttributes().with_standard_attributes(address=True),
322
+ "birthdate": lambda: cognito.ClientAttributes().with_standard_attributes(birthdate=True),
323
+ "email": lambda: cognito.ClientAttributes().with_standard_attributes(email=True),
324
+ "email_verified": lambda: cognito.ClientAttributes().with_standard_attributes(email_verified=True),
325
+ "family_name": lambda: cognito.ClientAttributes().with_standard_attributes(family_name=True),
326
+ "gender": lambda: cognito.ClientAttributes().with_standard_attributes(gender=True),
327
+ "given_name": lambda: cognito.ClientAttributes().with_standard_attributes(given_name=True),
328
+ "locale": lambda: cognito.ClientAttributes().with_standard_attributes(locale=True),
329
+ "middle_name": lambda: cognito.ClientAttributes().with_standard_attributes(middle_name=True),
330
+ "name": lambda: cognito.ClientAttributes().with_standard_attributes(fullname=True),
331
+ "nickname": lambda: cognito.ClientAttributes().with_standard_attributes(nickname=True),
332
+ "phone_number": lambda: cognito.ClientAttributes().with_standard_attributes(phone_number=True),
333
+ "phone_number_verified": lambda: cognito.ClientAttributes().with_standard_attributes(phone_number_verified=True),
334
+ "picture": lambda: cognito.ClientAttributes().with_standard_attributes(picture=True),
335
+ "preferred_username": lambda: cognito.ClientAttributes().with_standard_attributes(preferred_username=True),
336
+ "profile": lambda: cognito.ClientAttributes().with_standard_attributes(profile=True),
337
+ "timezone": lambda: cognito.ClientAttributes().with_standard_attributes(timezone=True),
338
+ "updated_at": lambda: cognito.ClientAttributes().with_standard_attributes(last_update_time=True),
339
+ "website": lambda: cognito.ClientAttributes().with_standard_attributes(website=True),
340
+ }
341
+
342
+ # Start with empty attributes
343
+ attrs = cognito.ClientAttributes()
344
+
345
+ # Build standard attributes
346
+ standard_dict = {}
347
+ custom_list = []
348
+
349
+ for attr in attribute_list:
350
+ if attr in standard_attrs:
351
+ standard_dict[attr] = True
352
+ else:
353
+ # Custom attribute
354
+ custom_list.append(attr)
355
+
356
+ # Apply standard attributes if any
357
+ if standard_dict:
358
+ # Map attribute names to CDK parameter names
359
+ attr_mapping = {
360
+ "address": "address",
361
+ "birthdate": "birthdate",
362
+ "email": "email",
363
+ "email_verified": "email_verified",
364
+ "family_name": "family_name",
365
+ "gender": "gender",
366
+ "given_name": "given_name",
367
+ "locale": "locale",
368
+ "middle_name": "middle_name",
369
+ "name": "fullname",
370
+ "nickname": "nickname",
371
+ "phone_number": "phone_number",
372
+ "phone_number_verified": "phone_number_verified",
373
+ "picture": "picture",
374
+ "preferred_username": "preferred_username",
375
+ "profile": "profile",
376
+ "timezone": "timezone",
377
+ "updated_at": "last_update_time",
378
+ "website": "website",
379
+ }
380
+
381
+ # Convert to CDK parameter names
382
+ cdk_attrs = {attr_mapping.get(k, k): v for k, v in standard_dict.items()}
383
+ attrs = attrs.with_standard_attributes(**cdk_attrs)
384
+
385
+ # Add custom attributes if any
386
+ if custom_list:
387
+ attrs = attrs.with_custom_attributes(*custom_list)
388
+
389
+ return attrs
390
+
391
+ def _build_identity_providers(self, providers: list) -> list:
392
+ """Build identity provider list from configuration"""
393
+ if not providers:
394
+ return None
395
+
396
+ result = []
397
+ for provider in providers:
398
+ if isinstance(provider, str):
399
+ if provider.upper() == "COGNITO":
400
+ result.append(cognito.UserPoolClientIdentityProvider.COGNITO)
401
+ elif provider.upper() == "GOOGLE":
402
+ result.append(cognito.UserPoolClientIdentityProvider.GOOGLE)
403
+ elif provider.upper() == "FACEBOOK":
404
+ result.append(cognito.UserPoolClientIdentityProvider.FACEBOOK)
405
+ elif provider.upper() == "AMAZON":
406
+ result.append(cognito.UserPoolClientIdentityProvider.AMAZON)
407
+ elif provider.upper() == "APPLE":
408
+ result.append(cognito.UserPoolClientIdentityProvider.APPLE)
409
+ else:
410
+ # Custom provider
411
+ result.append(cognito.UserPoolClientIdentityProvider.custom(provider))
412
+
413
+ return result if result else None
414
+
415
+ def _store_client_secret_in_secrets_manager(
416
+ self,
417
+ client_name: str,
418
+ app_client: cognito.UserPoolClient,
419
+ user_pool: cognito.UserPool
420
+ ):
421
+ """
422
+ Store Cognito app client secret in AWS Secrets Manager.
423
+ Uses a custom resource to retrieve the secret from Cognito API.
424
+ """
425
+ # Create a custom resource to retrieve the client secret
426
+ # This is necessary because CDK doesn't expose the client secret
427
+ get_client_secret = cr.AwsCustomResource(
428
+ self,
429
+ f"{client_name}-secret-retriever",
430
+ on_create=cr.AwsSdkCall(
431
+ service="CognitoIdentityServiceProvider",
432
+ action="describeUserPoolClient",
433
+ parameters={
434
+ "UserPoolId": user_pool.user_pool_id,
435
+ "ClientId": app_client.user_pool_client_id,
436
+ },
437
+ physical_resource_id=cr.PhysicalResourceId.of(
438
+ f"{client_name}-secret-{app_client.user_pool_client_id}"
439
+ ),
440
+ ),
441
+ policy=cr.AwsCustomResourcePolicy.from_statements([
442
+ iam.PolicyStatement(
443
+ actions=["cognito-idp:DescribeUserPoolClient"],
444
+ resources=[user_pool.user_pool_arn],
445
+ )
446
+ ]),
447
+ )
448
+
449
+ # Get the client secret from the custom resource response
450
+ client_secret = get_client_secret.get_response_field(
451
+ "UserPoolClient.ClientSecret"
452
+ )
453
+
454
+ # Create secret in Secrets Manager
455
+ secret = secretsmanager.Secret(
456
+ self,
457
+ f"{client_name}-client-secret",
458
+ secret_name=self.deployment.build_resource_name(
459
+ f"cognito/{client_name}/client-secret"
460
+ ),
461
+ description=f"Cognito app client secret for {client_name}",
462
+ secret_string_value=cdk.SecretValue.unsafe_plain_text(client_secret),
463
+ )
464
+
465
+ # Also store client ID in the same secret for convenience
466
+ secret_with_metadata = secretsmanager.Secret(
467
+ self,
468
+ f"{client_name}-client-credentials",
469
+ secret_name=self.deployment.build_resource_name(
470
+ f"cognito/{client_name}/credentials"
471
+ ),
472
+ description=f"Cognito app client credentials for {client_name}",
473
+ secret_object_value={
474
+ "client_id": cdk.SecretValue.unsafe_plain_text(
475
+ app_client.user_pool_client_id
476
+ ),
477
+ "client_secret": cdk.SecretValue.unsafe_plain_text(client_secret),
478
+ "user_pool_id": cdk.SecretValue.unsafe_plain_text(
479
+ user_pool.user_pool_id
480
+ ),
481
+ },
482
+ )
483
+
484
+ logger.info(
485
+ f"Stored client secret for {client_name} in Secrets Manager: "
486
+ f"{secret_with_metadata.secret_name}"
487
+ )
488
+
489
+ # Export secret ARN to SSM for cross-stack reference
490
+ if self.cognito_config.ssm.get("enabled"):
491
+ safe_client_name = client_name.replace("-", "_").replace(" ", "_")
492
+ org = self.cognito_config.ssm.get("organization", "default")
493
+ env = self.cognito_config.ssm.get("environment", "dev")
494
+
495
+ ssm.StringParameter(
496
+ self,
497
+ f"{client_name}-secret-arn-param",
498
+ parameter_name=f"/{org}/{env}/cognito/user-pool/app_client_{safe_client_name}_secret_arn",
499
+ string_value=secret_with_metadata.secret_arn,
500
+ description=f"Secrets Manager ARN for {client_name} credentials",
501
+ )
502
+
503
+ def _export_ssm_parameters(self, user_pool: cognito.UserPool):
504
+ """Export Cognito resources to SSM using enhanced SSM parameter mixin"""
505
+
506
+ # Setup enhanced SSM integration with proper resource type and name
507
+ # Use "user-pool" as resource identifier for SSM paths, not the full pool name
508
+
509
+ self.setup_enhanced_ssm_integration(
510
+ scope=self,
511
+ config=self.stack_config.dictionary.get("cognito", {}),
512
+ resource_type="cognito",
513
+ resource_name="user-pool"
514
+ )
515
+
516
+ # Prepare resource values for export
517
+ resource_values = {
518
+ "user_pool_id": user_pool.user_pool_id,
519
+ "user_pool_name": self.cognito_config.user_pool_name,
520
+ "user_pool_arn": user_pool.user_pool_arn,
521
+ }
522
+
523
+ # Add app client IDs to export
524
+ for client_name, app_client in self.app_clients.items():
525
+ # Export client ID
526
+ safe_client_name = client_name.replace("-", "_").replace(" ", "_")
527
+ resource_values[f"app_client_{safe_client_name}_id"] = app_client.user_pool_client_id
528
+
529
+ # Note: Client secrets cannot be exported via SSM as they are only available
530
+ # at creation time and CDK doesn't expose them. Use AWS Secrets Manager
531
+ # or retrieve via AWS Console/CLI if needed.
532
+
533
+ # Use enhanced SSM parameter export
534
+ exported_params = self.auto_export_resources(resource_values)
535
+
536
+ if exported_params:
537
+ logger.info(f"Exported {len(exported_params)} Cognito parameters to SSM")
538
+ else:
539
+ logger.info("No SSM parameters configured for export")
@@ -0,0 +1 @@
1
+ __version__ = "0.8.7"