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.
- cdk_factory-0.8.7/LAMBDA_PERMISSION_FIX_SUMMARY.md +56 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/PKG-INFO +1 -1
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/pyproject.toml +1 -1
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/cognito.py +63 -0
- cdk_factory-0.8.7/src/cdk_factory/stack_library/cognito/cognito_stack.py +539 -0
- cdk_factory-0.8.7/src/cdk_factory/version.py +1 -0
- cdk_factory-0.8.6/src/cdk_factory/stack_library/cognito/cognito_stack.py +0 -181
- cdk_factory-0.8.6/src/cdk_factory/version.py +0 -1
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/.gitignore +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/.windsurfrules +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/BUG_FIX_SSM_IMPORTS_METADATA_FIELDS.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/CHANGELOG_v0.8.1.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/CHANGELOG_v0.8.2.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/CHANGELOG_v0.8.3.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/LICENSE +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/README.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/RELEASE_NOTES_v0.8.2.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/SUMMARY_v0.8.2.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/SUMMARY_v0.8.3.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/archive/README.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/archive/migrate_to_enhanced_ssm.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/json-imports/README.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/README.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/api-gateway-stack.json +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/config.json +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/examples/separate-api-gateway/lambda-stack.json +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/mypy.ini +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/publish_to_pypi.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/publish_to_pypi.sh +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/pysetup.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/pysetup.sh +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/requirements.dev.txt +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/requirements.tests.txt +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/requirements.txt +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/run-checks.sh +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/run-tests-clean-venv.sh +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/run-tests.sh +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/app.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/builds/README.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/cdk.json +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/commands/command_loader.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/base_config.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/cdk_config.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/deployment.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/deployment_wave.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/devops.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/enhanced_base_config.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/enhanced_ssm_config.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/management.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/pipeline.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/pipeline_stage.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/_resources.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/api_gateway.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/apigateway_route_config.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/auto_scaling.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/cloudfront.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/cloudwatch_widget.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/code_artifact.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/code_artifact_login.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/code_repository.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/docker.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/dynamodb.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/ecr.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/exisiting.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/lambda_function.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/lambda_layers.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/lambda_triggers.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/load_balancer.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/rds.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/resource_mapping.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/resource_naming.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/resource_types.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/route53.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/route53_hosted_zone.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/rum.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/s3.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/security_group.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/security_group_full_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/sqs.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/resources/vpc.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/configurations/workload.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/ecr/ecr_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/lambda_function_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/lambda_function_docker_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/lambda_function_role_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/policies/policy_docs.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/lambdas/policies/policy_statements.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/s3_buckets/s3_bucket_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_destination_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/s3_buckets/s3_bucket_replication_source_construct.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/constructs/sqs/policies/sqs_policies.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/enhanced_ssm_parameter_mixin.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/istack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/live_ssm_resolver.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/interfaces/ssm_parameter_mixin.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/lambdas/health_handler.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/pipeline_factory.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/security/policies.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/security/roles.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/pipeline/stage.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/istack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_factory.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_module_loader.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_module_registry.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack/stack_modules.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/api_gateway/api_gateway_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/auto_scaling/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/aws_lambdas/lambda_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/buckets/README.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/buckets/bucket_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/code_artifact/code_artifact_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/dynamodb/dynamodb_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/ecr/README.md +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/ecr/ecr_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/load_balancer/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/load_balancer/load_balancer_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rds/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rds/rds_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/route53/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/route53/route53_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rum/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/rum/rum_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/security_group/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/security_group/security_group_full_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/security_group/security_group_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/simple_queue_service/sqs_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/stack_base.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/vpc/__init__.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/vpc/vpc_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stack_library/websites/static_website_stack.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/stages/websites/static_website_stage.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/api_gateway_integration_utility.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/commandline_args.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/configuration_loader.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/docker_utilities.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/environment_services.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/file_operations.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/git_utilities.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/json_loading_utility.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/lambda_function_utilities.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utilities/os_execute.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/utils/api_gateway_utilities.py +0 -0
- {cdk_factory-0.8.6 → cdk_factory-0.8.7}/src/cdk_factory/workload/workload_factory.py +0 -0
- {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!
|
|
@@ -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"
|