├── .release-please-manifest.json ├── examples ├── complete │ ├── custom_style.css │ ├── variables.tf │ ├── terraform.tfvars │ ├── logo.png │ ├── provider.tf │ ├── README.md │ └── main.tf ├── simple │ ├── variables.tf │ ├── terraform.tfvars │ ├── provider.tf │ ├── main.tf │ └── README.md ├── simple_extended │ ├── variables.tf │ ├── terraform.tfvars │ ├── provider.tf │ ├── main.tf │ └── README.md ├── .DS_Store ├── with_branding │ ├── .DS_Store │ ├── assets │ │ ├── favicon.svg │ │ ├── logo-light.svg │ │ ├── logo-dark.svg │ │ ├── background.svg │ │ ├── README.md │ │ └── generate-sample-assets.sh │ ├── terraform.tfvars │ ├── provider.tf │ ├── variables.tf │ ├── outputs.tf │ ├── main.tf │ └── README.md ├── email_mfa │ ├── main.tf │ └── README.md └── refresh_token_rotation │ ├── main.tf │ └── README.md ├── .DS_Store ├── versions.tf ├── .release-please-config.json ├── domain.tf ├── Claude Code Slash Commands.md ├── .gitignore ├── .terraform-docs.yml ├── locals-admin.tf ├── locals-sms.tf ├── renovate.json ├── .trivy.yaml ├── user-group.tf ├── .pre-commit-config.yaml ├── resource-server.tf ├── .tflint.hcl ├── identity-provider.tf ├── ui-customization.tf ├── locals-device.tf ├── .github ├── workflows │ ├── release-please.yml │ ├── security.yml │ ├── claude.yml │ └── pre-commit.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── new-cognito-feature.md │ ├── cognito-deprecation.md │ └── cognito-bug-fix.md ├── feature-tracker │ └── cognito-features.json └── scripts │ └── discovery-prompt.md ├── locals-password.tf ├── managed-login-branding.tf ├── .claude └── agents │ ├── terraform-cognito.md │ ├── terraform-security.md │ ├── module-documentation.md │ └── cognito-migration.md ├── locals-lambda.tf ├── locals-main.tf ├── locals-email.tf ├── .secrets.baseline ├── .tfsec.yml ├── bug_fix_summary.md ├── MIGRATION.md ├── migrate-clients.sh ├── outputs.tf ├── client.tf ├── MIGRATION_GUIDE.md └── LICENSE /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "4.0.0" 3 | } 4 | -------------------------------------------------------------------------------- /examples/complete/custom_style.css: -------------------------------------------------------------------------------- 1 | .label-customizable {font-weight: 400;} 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgallard/terraform-aws-cognito-user-pool/HEAD/.DS_Store -------------------------------------------------------------------------------- /examples/complete/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | type = map(any) 3 | default = {} 4 | } 5 | -------------------------------------------------------------------------------- /examples/simple/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | type = map(any) 3 | default = {} 4 | } 5 | -------------------------------------------------------------------------------- /examples/complete/terraform.tfvars: -------------------------------------------------------------------------------- 1 | env = { 2 | region = "us-east-1" 3 | profile = "default" 4 | } 5 | -------------------------------------------------------------------------------- /examples/simple/terraform.tfvars: -------------------------------------------------------------------------------- 1 | env = { 2 | region = "us-east-1" 3 | profile = "default" 4 | } 5 | -------------------------------------------------------------------------------- /examples/simple_extended/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | type = map(any) 3 | default = {} 4 | } 5 | -------------------------------------------------------------------------------- /examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgallard/terraform-aws-cognito-user-pool/HEAD/examples/.DS_Store -------------------------------------------------------------------------------- /examples/simple_extended/terraform.tfvars: -------------------------------------------------------------------------------- 1 | env = { 2 | region = "us-east-1" 3 | profile = "default" 4 | } 5 | -------------------------------------------------------------------------------- /examples/simple/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.env["region"] 3 | profile = var.env["profile"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/complete/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgallard/terraform-aws-cognito-user-pool/HEAD/examples/complete/logo.png -------------------------------------------------------------------------------- /examples/complete/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.env["region"] 3 | profile = var.env["profile"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/simple_extended/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.env["region"] 3 | profile = var.env["profile"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/with_branding/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgallard/terraform-aws-cognito-user-pool/HEAD/examples/with_branding/.DS_Store -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 6.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/with_branding/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | A 4 | 5 | -------------------------------------------------------------------------------- /examples/with_branding/assets/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | My App 4 | 5 | -------------------------------------------------------------------------------- /.release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": { 4 | "release-type": "terraform-module", 5 | "changelog-path": "CHANGELOG.md", 6 | "include-v-in-tag": false, 7 | "include-component-in-tag": false 8 | } 9 | }, 10 | "pull-request-title-pattern": "chore: release ${version}" 11 | } 12 | -------------------------------------------------------------------------------- /examples/with_branding/assets/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | My App 4 | 5 | -------------------------------------------------------------------------------- /domain.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cognito_user_pool_domain" "domain" { 2 | count = !var.enabled || var.domain == null || var.domain == "" ? 0 : 1 3 | domain = var.domain 4 | certificate_arn = var.domain_certificate_arn 5 | user_pool_id = local.user_pool_id 6 | managed_login_version = var.domain_managed_login_version 7 | } 8 | -------------------------------------------------------------------------------- /examples/with_branding/assets/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/simple/main.tf: -------------------------------------------------------------------------------- 1 | module "aws_cognito_user_pool_simple_example" { 2 | 3 | source = "../../" 4 | 5 | user_pool_name = "simple_pool" 6 | 7 | # Recommended: Enable schema ignore changes for new deployments 8 | # This prevents perpetual diffs if you plan to use custom schemas in the future 9 | ignore_schema_changes = true 10 | 11 | # tags 12 | tags = { 13 | Owner = "infra" 14 | Environment = "production" 15 | Terraform = true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/with_branding/terraform.tfvars: -------------------------------------------------------------------------------- 1 | # Example configuration for Cognito User Pool with Managed Login Branding 2 | 3 | user_pool_name = "my-app-user-pool-with-branding" 4 | user_pool_tier = "ESSENTIALS" 5 | 6 | # Enable managed login branding 7 | enable_branding = true 8 | 9 | # Optional: Set a custom domain for hosted UI 10 | domain_name = "my-app-auth-branding-test-20250720" 11 | 12 | aws_region = "us-east-1" 13 | 14 | tags = { 15 | Environment = "development" 16 | Project = "my-app" 17 | Owner = "platform-team" 18 | } 19 | -------------------------------------------------------------------------------- /examples/with_branding/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 6.0" 8 | } 9 | 10 | # Required for managed login branding 11 | awscc = { 12 | source = "hashicorp/awscc" 13 | version = ">= 1.0" 14 | } 15 | } 16 | } 17 | 18 | provider "aws" { 19 | region = var.aws_region 20 | } 21 | 22 | provider "awscc" { 23 | region = var.aws_region 24 | } 25 | 26 | variable "aws_region" { 27 | description = "AWS region" 28 | type = string 29 | default = "us-east-1" 30 | } 31 | -------------------------------------------------------------------------------- /examples/with_branding/variables.tf: -------------------------------------------------------------------------------- 1 | variable "user_pool_name" { 2 | description = "The name of the user pool" 3 | type = string 4 | default = "example-pool-with-branding" 5 | } 6 | 7 | variable "user_pool_tier" { 8 | description = "The tier of the user pool" 9 | type = string 10 | default = "ESSENTIALS" 11 | } 12 | 13 | variable "domain_name" { 14 | description = "The domain name for the hosted UI" 15 | type = string 16 | default = "" 17 | } 18 | 19 | variable "enable_branding" { 20 | description = "Whether to enable managed login branding" 21 | type = bool 22 | default = true 23 | } 24 | 25 | variable "tags" { 26 | description = "A map of tags to assign to the resource" 27 | type = map(string) 28 | default = { 29 | Environment = "development" 30 | Project = "cognito-branding-example" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # The simple example 2 | 3 | This example creates a basic AWS Cognito User Pool with minimal configuration and follows best practices for new deployments. 4 | 5 | ## Best Practices 6 | 7 | 💡 **Recommendation**: Even for simple configurations, it's recommended to set `ignore_schema_changes = true` to prevent issues if you decide to add custom schemas in the future. 8 | 9 | ```hcl 10 | module "aws_cognito_user_pool_simple_example" { 11 | 12 | source = "lgallard/cognito-user-pool/aws" 13 | 14 | user_pool_name = "simple_pool" 15 | 16 | # Recommended: Enable schema ignore changes for new deployments 17 | # This prevents perpetual diffs if you plan to use custom schemas in the future 18 | ignore_schema_changes = true 19 | 20 | # tags 21 | tags = { 22 | Owner = "infra" 23 | Environment = "production" 24 | Terraform = true 25 | } 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /Claude Code Slash Commands.md: -------------------------------------------------------------------------------- 1 | # Claude Code Slash Commands 2 | 3 | ## Help & Support 4 | - `/help` - Get help with using Claude Code 5 | - For feedback or issues: https://github.com/anthropics/claude-code/issues 6 | 7 | ## Session Management 8 | - `/exit` or `/quit` - Exit Claude Code 9 | - `/clear` - Clear the conversation history 10 | - `/resume` - Resume from a previous conversation 11 | - `/settings` - View or modify settings 12 | 13 | ## Memory & Context 14 | - Claude Code automatically maintains context across sessions 15 | - Use `CLAUDE.md` files to provide persistent context about your project 16 | 17 | ## Key Features Available 18 | - File editing and creation 19 | - Code analysis and refactoring 20 | - Git operations and GitHub integration 21 | - Web search and fetching 22 | - Terminal/bash command execution 23 | - Multi-file operations 24 | 25 | ## Documentation 26 | For detailed documentation: https://docs.anthropic.com/en/docs/claude-code 27 | 28 | --- 29 | *Created: 2025-07-15* 30 | -------------------------------------------------------------------------------- /examples/with_branding/outputs.tf: -------------------------------------------------------------------------------- 1 | output "user_pool_id" { 2 | description = "The ID of the user pool" 3 | value = module.aws_cognito_user_pool.id 4 | } 5 | 6 | output "user_pool_arn" { 7 | description = "The ARN of the user pool" 8 | value = module.aws_cognito_user_pool.arn 9 | } 10 | 11 | output "user_pool_domain" { 12 | description = "The domain of the user pool" 13 | value = var.domain_name 14 | } 15 | 16 | output "hosted_ui_url" { 17 | description = "The hosted UI URL" 18 | value = var.domain_name != "" ? "https://${var.domain_name}.auth.${var.aws_region}.amazoncognito.com/login" : null 19 | } 20 | 21 | output "managed_login_branding_details" { 22 | description = "The managed login branding details from the module" 23 | value = module.aws_cognito_user_pool.managed_login_branding_details 24 | } 25 | 26 | output "client_ids_map" { 27 | description = "Map of user pool client IDs" 28 | value = module.aws_cognito_user_pool.client_ids_map 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Terraform lock files 9 | .terraform.lock.hcl 10 | 11 | # Crash log files 12 | crash.log 13 | 14 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 15 | # .tfvars files are managed as part of configuration and so should be included in 16 | # version control. 17 | # 18 | # example.tfvars 19 | 20 | # Ignore override files as they are usually used to override resources locally and so 21 | # are not checked in 22 | override.tf 23 | override.tf.json 24 | *_override.tf 25 | *_override.tf.json 26 | 27 | # Include override files you do wish to add to version control using negated pattern 28 | # 29 | # !example_override.tf 30 | 31 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 32 | # example: *tfplan* 33 | 34 | # Temporary documentation and summary files 35 | bug_fix_summary.md 36 | task_summary.md 37 | *_summary.md 38 | -------------------------------------------------------------------------------- /.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | version: ">=0.16" 2 | 3 | formatter: "markdown table" 4 | 5 | header-from: main.tf 6 | footer-from: "" 7 | 8 | recursive: 9 | enabled: false 10 | path: modules 11 | 12 | sections: 13 | hide: [] 14 | show: [] 15 | 16 | content: |- 17 | {{ .Header }} 18 | 19 | {{ .Requirements }} 20 | 21 | {{ .Providers }} 22 | 23 | {{ .Modules }} 24 | 25 | {{ .Resources }} 26 | 27 | {{ .Inputs }} 28 | 29 | {{ .Outputs }} 30 | 31 | output: 32 | file: "README.md" 33 | mode: inject 34 | template: |- 35 | 36 | {{ .Content }} 37 | 38 | 39 | output-values: 40 | enabled: false 41 | from: "" 42 | 43 | sort: 44 | enabled: true 45 | by: name 46 | 47 | settings: 48 | anchor: true 49 | color: true 50 | default: true 51 | description: false 52 | escape: true 53 | hide-empty: false 54 | html: true 55 | indent: 2 56 | lockfile: true 57 | read-comments: true 58 | required: true 59 | sensitive: true 60 | type: true 61 | -------------------------------------------------------------------------------- /locals-admin.tf: -------------------------------------------------------------------------------- 1 | # 2 | # Admin Create User Configuration Locals 3 | # 4 | # This file contains locals for managing admin create user configuration 5 | # including email and SMS message settings for user invitations. 6 | # 7 | 8 | locals { 9 | # admin_create_user_config 10 | # Build admin_create_user_config using modern Terraform syntax and default values 11 | admin_create_user_config_default = { 12 | allow_admin_create_user_only = try(var.admin_create_user_config.allow_admin_create_user_only, var.admin_create_user_config_allow_admin_create_user_only) 13 | email_message = try(var.admin_create_user_config.email_message, coalesce(var.email_verification_message, var.admin_create_user_config_email_message)) 14 | email_subject = try(var.admin_create_user_config.email_subject, coalesce(var.email_verification_subject, var.admin_create_user_config_email_subject)) 15 | sms_message = try(var.admin_create_user_config.sms_message, var.admin_create_user_config_sms_message) 16 | } 17 | 18 | admin_create_user_config = [local.admin_create_user_config_default] 19 | } 20 | -------------------------------------------------------------------------------- /examples/email_mfa/main.tf: -------------------------------------------------------------------------------- 1 | module "aws_cognito_user_pool_email_mfa_example" { 2 | source = "../../" 3 | 4 | user_pool_name = "email_mfa_pool" 5 | 6 | # Recommended: Enable schema ignore changes for new deployments 7 | # This prevents perpetual diffs if you plan to use custom schemas 8 | ignore_schema_changes = true 9 | 10 | # Email configuration 11 | email_configuration = { 12 | email_sending_account = "DEVELOPER" 13 | from_email_address = "noreply@example.com" 14 | source_arn = "arn:aws:ses:us-east-1:123456789012:identity/example.com" 15 | } 16 | 17 | # Email MFA configuration 18 | email_mfa_configuration = { 19 | message = "Your verification code is {####}" 20 | subject = "Your verification code" 21 | } 22 | 23 | # Account recovery settings (required for email MFA) 24 | recovery_mechanisms = [ 25 | { 26 | name = "verified_email" 27 | priority = 1 28 | }, 29 | { 30 | name = "verified_phone_number" 31 | priority = 2 32 | } 33 | ] 34 | 35 | # MFA configuration 36 | mfa_configuration = "ON" 37 | 38 | # Auto verify email 39 | auto_verified_attributes = ["email"] 40 | 41 | # Tags 42 | tags = { 43 | Owner = "infra" 44 | Environment = "production" 45 | Terraform = true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /locals-sms.tf: -------------------------------------------------------------------------------- 1 | # 2 | # SMS Configuration Locals 3 | # 4 | # This file contains locals for managing SMS configuration settings 5 | # including SNS caller ARN and external ID for SMS sending. 6 | # 7 | 8 | locals { 9 | # sms_configuration 10 | # Build SMS configuration using modern Terraform syntax 11 | sms_configuration_default = { 12 | external_id = try(var.sms_configuration.external_id, var.sms_configuration_external_id) 13 | sns_caller_arn = try(var.sms_configuration.sns_caller_arn, var.sms_configuration_sns_caller_arn) 14 | } 15 | 16 | # Only include SMS configuration if both required fields are provided 17 | sms_configuration = ( 18 | try(local.sms_configuration_default.external_id, "") == "" || 19 | try(local.sms_configuration_default.sns_caller_arn, "") == "" 20 | ) ? [] : [local.sms_configuration_default] 21 | 22 | # software_token_mfa_configuration 23 | # Build software token MFA configuration using modern Terraform syntax 24 | software_token_mfa_configuration_default = { 25 | enabled = try(var.software_token_mfa_configuration.enabled, var.software_token_mfa_configuration_enabled) 26 | } 27 | 28 | # Only include software token MFA if MFA is not OFF 29 | software_token_mfa_configuration = var.mfa_configuration == "OFF" ? [] : [local.software_token_mfa_configuration_default] 30 | } 31 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":dependencyDashboard" 6 | ], 7 | "major": { 8 | "dependencyDashboardApproval": true 9 | }, 10 | "packageRules": [ 11 | { 12 | "managers": ["terraform"], 13 | "matchPackageNames": ["terraform"], 14 | "enabled": true, 15 | "automerge": false 16 | }, 17 | { 18 | "matchDatasources": ["terraform-provider"], 19 | "enabled": true, 20 | "automerge": false 21 | }, 22 | { 23 | "matchDatasources": ["terraform-module"], 24 | "enabled": true, 25 | "automerge": false 26 | }, 27 | { 28 | "managers": ["terraform"], 29 | "matchPackagePatterns": ["^hashicorp/terraform$"], 30 | "enabled": true, 31 | "automerge": false 32 | }, 33 | { 34 | "matchDatasources": ["golang-version"], 35 | "ignoreUnstable": true, 36 | "respectLatest": true, 37 | "description": "Prevent pre-release Go versions in GitHub Actions workflows" 38 | }, 39 | { 40 | "managers": ["github-actions"], 41 | "matchPackageNames": ["actions/setup-go"], 42 | "ignoreUnstable": true, 43 | "respectLatest": true, 44 | "description": "Ensure setup-go action only uses stable Go versions" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /.trivy.yaml: -------------------------------------------------------------------------------- 1 | # Trivy configuration for Terraform security scanning 2 | # https://aquasecurity.github.io/trivy/latest/docs/references/configuration/config-file/ 3 | 4 | # Scan configuration 5 | scan: 6 | # Scan types to enable 7 | scanners: 8 | - vuln 9 | - secret 10 | - misconfig 11 | 12 | # Skip files/directories 13 | skip-files: 14 | - "**/.terraform/**" 15 | - "**/test/**" 16 | - "**/.git/**" 17 | 18 | # Output configuration 19 | format: table 20 | severity: 21 | - UNKNOWN 22 | - LOW 23 | - MEDIUM 24 | - HIGH 25 | - CRITICAL 26 | 27 | # Misconfiguration settings 28 | misconfig: 29 | # Include Terraform misconfigurations 30 | include-non-failures: false 31 | 32 | # Terraform specific settings 33 | terraform: 34 | # Variables files to use 35 | tfvars: [] 36 | 37 | # Skip certain checks 38 | skip-policy: 39 | # Skip checks that might be too strict for this module 40 | - "terraform.security.aws.iam.no_password_reuse" 41 | 42 | # Secret scanning settings 43 | secret: 44 | # Skip secret scanning in test files 45 | skip-paths: 46 | - "test/**" 47 | - "examples/**" 48 | 49 | # Vulnerability settings 50 | vulnerability: 51 | # Type of vulnerabilities to detect 52 | type: 53 | - os 54 | - library 55 | 56 | # Cache settings 57 | cache: 58 | # Enable caching for faster subsequent runs 59 | clear: false 60 | -------------------------------------------------------------------------------- /user-group.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cognito_user_group" "main" { 2 | for_each = var.enabled ? { for group in local.groups : group.name => group } : {} 3 | name = each.key 4 | description = try(each.value.description, null) 5 | precedence = try(each.value.precedence, null) 6 | role_arn = try(each.value.role_arn, null) 7 | user_pool_id = local.user_pool_id 8 | } 9 | 10 | # State migration from count to for_each requires manual intervention 11 | # Users need to run terraform state mv commands as documented in MIGRATION_GUIDE.md 12 | 13 | locals { 14 | groups_default = [ 15 | { 16 | name = var.user_group_name 17 | description = var.user_group_description 18 | precedence = var.user_group_precedence 19 | role_arn = var.user_group_role_arn 20 | 21 | } 22 | ] 23 | 24 | # This parses var.user_groups which is a list of objects (map), and transforms it to a tuple of elements to avoid conflict with the ternary and local.groups_default 25 | groups_parsed = [for e in var.user_groups : { 26 | name = try(e.name, null) 27 | description = try(e.description, null) 28 | precedence = try(e.precedence, null) 29 | role_arn = try(e.role_arn, null) 30 | } 31 | ] 32 | 33 | groups = length(var.user_groups) == 0 && (var.user_group_name == null || var.user_group_name == "") ? [] : (length(var.user_groups) > 0 ? local.groups_parsed : local.groups_default) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 # Updated to latest stable version 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-added-large-files 10 | args: ['--maxkb=500'] # Set specific file size limit 11 | - id: detect-aws-credentials 12 | args: ['--allow-missing-credentials'] # Avoid false positives 13 | - id: check-yaml # Added YAML validation 14 | - id: check-merge-conflict # Added merge conflict detection 15 | - id: check-json # Added JSON validation 16 | - id: check-toml # Added TOML validation 17 | - id: detect-private-key # Added private key detection 18 | - id: mixed-line-ending 19 | args: ['--fix=lf'] # Ensure consistent line endings 20 | - repo: https://github.com/antonbabenko/pre-commit-terraform 21 | rev: v1.96.0 # Updated to latest stable version 22 | hooks: 23 | - id: terraform_fmt 24 | - id: terraform_validate 25 | args: 26 | - --tf-init-args=-upgrade # Ensure latest provider versions 27 | - repo: https://github.com/crate-ci/typos 28 | rev: v1.16.23 29 | hooks: 30 | - id: typos 31 | types: [markdown] 32 | args: ['--format', 'brief'] 33 | exclude: 'CHANGELOG.md' 34 | -------------------------------------------------------------------------------- /resource-server.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cognito_resource_server" "resource" { 2 | count = var.enabled ? length(local.resource_servers) : 0 3 | name = element(local.resource_servers, count.index).name 4 | identifier = element(local.resource_servers, count.index).identifier 5 | 6 | #scope 7 | dynamic "scope" { 8 | for_each = element(local.resource_servers, count.index).scope 9 | content { 10 | scope_name = scope.value.scope_name 11 | scope_description = scope.value.scope_description 12 | } 13 | } 14 | 15 | user_pool_id = local.user_pool_id 16 | } 17 | 18 | locals { 19 | resource_server_default = [ 20 | { 21 | name = var.resource_server_name 22 | identifier = var.resource_server_identifier 23 | scope = [ 24 | { 25 | scope_name = var.resource_server_scope_name 26 | scope_description = var.resource_server_scope_description 27 | }] 28 | } 29 | ] 30 | 31 | # This parses var.user_groups which is a list of objects (map), and transforms it to a tuple of elements to avoid conflict with the ternary and local.groups_default 32 | resource_servers_parsed = [for e in var.resource_servers : { 33 | name = try(e.name, null) 34 | identifier = try(e.identifier, null) 35 | scope = try(e.scope, []) 36 | } 37 | ] 38 | 39 | resource_servers = length(var.resource_servers) == 0 && (var.resource_server_name == null || var.resource_server_name == "") ? [] : (length(var.resource_servers) > 0 ? local.resource_servers_parsed : local.resource_server_default) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /.tflint.hcl: -------------------------------------------------------------------------------- 1 | config { 2 | # Call module inspection 3 | call_module_type = "all" 4 | # Force = false (default) 5 | # Plugin system 6 | plugin_dir = "~/.tflint.d/plugins" 7 | } 8 | 9 | plugin "aws" { 10 | enabled = true 11 | version = "0.42.0" 12 | source = "github.com/terraform-linters/tflint-ruleset-aws" 13 | } 14 | 15 | # AWS provider rules for general validations 16 | rule "aws_iam_policy_invalid_policy" { 17 | enabled = true 18 | } 19 | 20 | rule "aws_iam_role_invalid_assume_role_policy" { 21 | enabled = true 22 | } 23 | 24 | # General terraform rules 25 | rule "terraform_deprecated_interpolation" { 26 | enabled = true 27 | } 28 | 29 | rule "terraform_deprecated_index" { 30 | enabled = true 31 | } 32 | 33 | rule "terraform_deprecated_lookup" { 34 | enabled = true 35 | } 36 | 37 | rule "terraform_unused_declarations" { 38 | enabled = true 39 | } 40 | 41 | rule "terraform_comment_syntax" { 42 | enabled = true 43 | } 44 | 45 | rule "terraform_documented_outputs" { 46 | enabled = true 47 | } 48 | 49 | rule "terraform_documented_variables" { 50 | enabled = true 51 | } 52 | 53 | rule "terraform_typed_variables" { 54 | enabled = true 55 | } 56 | 57 | rule "terraform_module_pinned_source" { 58 | enabled = true 59 | } 60 | 61 | rule "terraform_naming_convention" { 62 | enabled = true 63 | format = "snake_case" 64 | } 65 | 66 | rule "terraform_standard_module_structure" { 67 | enabled = true 68 | } 69 | 70 | # Terraform required version 71 | rule "terraform_required_version" { 72 | enabled = true 73 | } 74 | 75 | # Provider version constraints 76 | rule "terraform_required_providers" { 77 | enabled = true 78 | } 79 | -------------------------------------------------------------------------------- /identity-provider.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cognito_identity_provider" "identity_provider" { 2 | count = var.enabled ? length(var.identity_providers) : 0 3 | user_pool_id = local.user_pool_id 4 | 5 | # Required fields: Keep lookup() to enforce presence 6 | provider_name = lookup(element(var.identity_providers, count.index), "provider_name") 7 | provider_type = lookup(element(var.identity_providers, count.index), "provider_type") 8 | 9 | # Optional fields: Use try() for modern syntax 10 | attribute_mapping = try(element(var.identity_providers, count.index).attribute_mapping, {}) 11 | idp_identifiers = try(element(var.identity_providers, count.index).idp_identifiers, []) 12 | provider_details = try(element(var.identity_providers, count.index).provider_details, {}) 13 | 14 | # Ignore changes to provider_details that are managed by AWS 15 | # AWS automatically populates ActiveEncryptionCertificate for SAML providers 16 | # OAuth providers may have auto-managed fields from OIDC discovery and sensitive value handling 17 | lifecycle { 18 | ignore_changes = [ 19 | # SAML provider auto-managed fields 20 | provider_details["ActiveEncryptionCertificate"], 21 | 22 | # OAuth provider auto-managed fields that may cause drift 23 | provider_details["authorize_url"], # May be updated via OIDC discovery 24 | provider_details["token_url"], # May be updated via OIDC discovery 25 | provider_details["oidc_issuer"], # May be updated via OIDC discovery 26 | provider_details["jwks_uri"], # Auto-populated from OIDC discovery 27 | provider_details["issuer"], # May be auto-populated 28 | 29 | # Sensitive fields that cause drift due to Terraform's sensitive value handling 30 | provider_details["client_secret"], # Sensitive field causing plan drift 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ui-customization.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # Create a map of client names to client IDs for UI customization lookups 3 | # Handle null/empty names by using the for_each key as fallback 4 | client_ids_map = { 5 | for k, c in aws_cognito_user_pool_client.client : 6 | coalesce(c.name, k) => c.id 7 | } 8 | 9 | # Create UI customizations map using local.clients for consistency 10 | # Preserves backward-compatible key format to avoid resource recreation 11 | client_ui_customizations = { 12 | for idx, c in local.clients : 13 | coalesce(lookup(c, "name", null), "client-${idx}") => { 14 | css = try(c.ui_customization_css, null) 15 | image_file = try(c.ui_customization_image_file, null) 16 | } if try(c.ui_customization_css, null) != null || try(c.ui_customization_image_file, null) != null 17 | } 18 | } 19 | 20 | # UI customizations for specific clients 21 | resource "aws_cognito_user_pool_ui_customization" "ui_customization" { 22 | # Only create resources for clients that still exist in client_ids_map 23 | for_each = { 24 | for k, v in local.client_ui_customizations : 25 | k => v 26 | if contains(keys(local.client_ids_map), k) 27 | } 28 | 29 | client_id = lookup(local.client_ids_map, each.key, null) 30 | 31 | css = each.value.css 32 | image_file = each.value.image_file 33 | 34 | user_pool_id = local.user_pool_id 35 | 36 | depends_on = [aws_cognito_user_pool_client.client] 37 | } 38 | 39 | # Default UI customization 40 | resource "aws_cognito_user_pool_ui_customization" "default_ui_customization" { 41 | count = var.default_ui_customization_css != null || var.default_ui_customization_image_file != null ? 1 : 0 42 | 43 | css = var.default_ui_customization_css 44 | image_file = var.default_ui_customization_image_file 45 | user_pool_id = local.user_pool_id 46 | } 47 | -------------------------------------------------------------------------------- /locals-device.tf: -------------------------------------------------------------------------------- 1 | # 2 | # Device Configuration Locals 3 | # 4 | # This file contains locals for managing device configuration settings 5 | # including challenge requirements and device memory settings. 6 | # 7 | 8 | locals { 9 | # device_configuration 10 | # Simplified device configuration logic using modern Terraform syntax 11 | 12 | # Check if device configuration is provided 13 | device_config_provided = length(var.device_configuration) > 0 14 | 15 | # Extract device configuration values with fallbacks 16 | device_configuration_values = local.device_config_provided ? { 17 | challenge_required_on_new_device = try(var.device_configuration.challenge_required_on_new_device, var.device_configuration_challenge_required_on_new_device) 18 | device_only_remembered_on_user_prompt = try(var.device_configuration.device_only_remembered_on_user_prompt, var.device_configuration_device_only_remembered_on_user_prompt) 19 | } : {} 20 | 21 | # Check if device configuration differs from AWS defaults or has null values 22 | device_challenge_set = try(local.device_configuration_values.challenge_required_on_new_device, null) != null 23 | device_prompt_set = try(local.device_configuration_values.device_only_remembered_on_user_prompt, null) != null 24 | device_challenge_enabled = try(local.device_configuration_values.challenge_required_on_new_device, false) != false 25 | device_prompt_enabled = try(local.device_configuration_values.device_only_remembered_on_user_prompt, false) != false 26 | 27 | # Only include device configuration block if configuration is provided and meaningful 28 | device_configuration_needed = local.device_config_provided && ( 29 | local.device_challenge_set || local.device_prompt_set 30 | ) 31 | 32 | device_configuration = local.device_configuration_needed ? [local.device_configuration_values] : [] 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-24.04 11 | outputs: 12 | release_created: ${{ steps.release_please.outputs.release_created }} 13 | steps: 14 | - uses: googleapis/release-please-action@a02a34c4d625f9be7cb89156071d8567266a2445 # v4.2.0 15 | id: release_please 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | config-file: .release-please-config.json 19 | 20 | - name: Remove v-prefix from release title 21 | if: ${{ steps.release_please.outputs.release_created }} 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | run: | 25 | # Get the release tag and current title 26 | RELEASE_TAG="${{ steps.release_please.outputs.tag_name }}" 27 | echo "Release tag: $RELEASE_TAG" 28 | 29 | # Get current release info 30 | CURRENT_RELEASE=$(gh release view "$RELEASE_TAG" --json name,tagName --repo ${{ github.repository }}) 31 | CURRENT_NAME=$(echo "$CURRENT_RELEASE" | jq -r '.name') 32 | echo "Current release name: $CURRENT_NAME" 33 | 34 | # Check if the release title has v-prefix but tag doesn't 35 | if [[ "$CURRENT_NAME" =~ ^v[0-9] ]] && [[ ! "$RELEASE_TAG" =~ ^v[0-9] ]]; then 36 | # Remove v-prefix from release title to match the clean tag 37 | NEW_NAME="${CURRENT_NAME#v}" 38 | echo "Updating release title from '$CURRENT_NAME' to '$NEW_NAME'" 39 | 40 | gh release edit "$RELEASE_TAG" --title "$NEW_NAME" --repo ${{ github.repository }} 41 | echo "✅ Release title updated successfully" 42 | else 43 | echo "ℹ️ No v-prefix found in release title or tag already has v-prefix - no changes needed" 44 | fi 45 | -------------------------------------------------------------------------------- /examples/refresh_token_rotation/main.tf: -------------------------------------------------------------------------------- 1 | module "aws_cognito_user_pool_refresh_rotation" { 2 | source = "../../" 3 | 4 | user_pool_name = "mypool-refresh-rotation" 5 | alias_attributes = ["email"] 6 | auto_verified_attributes = ["email"] 7 | 8 | # Configure a client with refresh token rotation enabled 9 | clients = [ 10 | { 11 | name = "refresh-rotation-client" 12 | generate_secret = true 13 | explicit_auth_flows = ["ALLOW_USER_PASSWORD_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"] 14 | 15 | # Refresh token rotation configuration 16 | refresh_token_rotation = { 17 | feature = "ENABLED" 18 | retry_grace_period_seconds = 60 # Maximum 60 seconds per AWS limits 19 | } 20 | 21 | # Token validity settings 22 | refresh_token_validity = 30 23 | access_token_validity = 60 24 | id_token_validity = 60 25 | 26 | token_validity_units = { 27 | access_token = "minutes" 28 | id_token = "minutes" 29 | refresh_token = "days" 30 | } 31 | 32 | # Enable token revocation for enhanced security 33 | enable_token_revocation = true 34 | 35 | callback_urls = ["https://example.com/callback"] 36 | logout_urls = ["https://example.com/logout"] 37 | 38 | read_attributes = ["email", "name"] 39 | write_attributes = ["email", "name"] 40 | } 41 | ] 42 | 43 | # Basic configuration 44 | password_policy = { 45 | minimum_length = 8 46 | require_lowercase = true 47 | require_numbers = true 48 | require_symbols = true 49 | require_uppercase = true 50 | temporary_password_validity_days = 7 51 | password_history_size = 0 52 | } 53 | 54 | tags = { 55 | Name = "cognito-refresh-rotation-example" 56 | Environment = "development" 57 | Feature = "refresh_token_rotation" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/with_branding/assets/README.md: -------------------------------------------------------------------------------- 1 | # Assets Directory 2 | 3 | This directory should contain the branding assets referenced in the example: 4 | 5 | ## Required Files 6 | 7 | - `logo-light.svg` - Logo for light mode (recommended size: 200x60px) 8 | - `logo-dark.svg` - Logo for dark mode (recommended size: 200x60px) 9 | - `background.svg` - Background image (recommended size: 1920x1080px) 10 | - `favicon.svg` - Favicon (16x16px or 32x32px) 11 | 12 | **Note:** This example includes sample SVG files that work out of the box. For production use, replace these with your actual brand assets. 13 | 14 | ## Asset Guidelines 15 | 16 | ### Supported Formats 17 | - PNG, JPG, JPEG, SVG, ICO 18 | - Maximum file size: 2MB per asset 19 | 20 | ### Asset Categories 21 | - `FORM_LOGO` - Logo displayed on the login form 22 | - `PAGE_BACKGROUND` - Background image for the login page 23 | - `FAVICON_ICO` - Favicon for the browser tab 24 | - `PAGE_HEADER_LOGO` - Logo in the page header 25 | - `PAGE_FOOTER_LOGO` - Logo in the page footer 26 | - And more (see AWS documentation) 27 | 28 | ### Color Modes 29 | - `LIGHT` - Assets for light theme 30 | - `DARK` - Assets for dark theme 31 | - `DYNAMIC` - Assets that adapt to browser preference 32 | 33 | ## Quick Start - Generate Sample Assets 34 | 35 | Use the provided script to quickly generate sample assets for testing: 36 | 37 | ```bash 38 | # Run the asset generation script 39 | ./generate-sample-assets.sh 40 | ``` 41 | 42 | This will create all required assets with sample branding. Customize them with your actual brand assets before production use. 43 | 44 | ## Manual Asset Creation 45 | 46 | You can also create placeholder assets manually: 47 | 48 | ```bash 49 | # Create a simple colored rectangle as logo 50 | convert -size 200x60 xc:blue logo-light.png 51 | convert -size 200x60 xc:white logo-dark.png 52 | 53 | # Create a gradient background 54 | convert -size 1920x1080 gradient:blue-lightblue background.jpg 55 | 56 | # Create a simple favicon 57 | convert -size 32x32 xc:blue favicon.ico 58 | ``` 59 | 60 | Note: The `convert` command requires ImageMagick to be installed. 61 | -------------------------------------------------------------------------------- /locals-password.tf: -------------------------------------------------------------------------------- 1 | # 2 | # Password Policy Configuration Locals 3 | # 4 | # This file contains locals for managing password policy settings 5 | # including complexity requirements and password validity. 6 | # 7 | 8 | locals { 9 | # password_policy 10 | # Build password policy configuration using modern Terraform syntax 11 | 12 | # Default password policy when no configuration is provided 13 | password_policy_defaults = { 14 | minimum_length = var.password_policy_minimum_length 15 | require_lowercase = var.password_policy_require_lowercase 16 | require_numbers = var.password_policy_require_numbers 17 | require_symbols = var.password_policy_require_symbols 18 | require_uppercase = var.password_policy_require_uppercase 19 | temporary_password_validity_days = var.password_policy_temporary_password_validity_days 20 | password_history_size = var.password_policy_password_history_size 21 | } 22 | 23 | # Password policy with fallback to defaults 24 | password_policy_configuration = var.password_policy == null ? local.password_policy_defaults : { 25 | minimum_length = try(var.password_policy.minimum_length, var.password_policy_minimum_length) 26 | require_lowercase = try(var.password_policy.require_lowercase, var.password_policy_require_lowercase) 27 | require_numbers = try(var.password_policy.require_numbers, var.password_policy_require_numbers) 28 | require_symbols = try(var.password_policy.require_symbols, var.password_policy_require_symbols) 29 | require_uppercase = try(var.password_policy.require_uppercase, var.password_policy_require_uppercase) 30 | temporary_password_validity_days = try(var.password_policy.temporary_password_validity_days, var.password_policy_temporary_password_validity_days) 31 | password_history_size = try(var.password_policy.password_history_size, var.password_policy_password_history_size) 32 | } 33 | 34 | password_policy = [local.password_policy_configuration] 35 | } 36 | -------------------------------------------------------------------------------- /managed-login-branding.tf: -------------------------------------------------------------------------------- 1 | # AWS Cloud Control API provider is required for managed login branding 2 | # Ensure awscc provider is configured in your root module when enabling this feature 3 | # Note: color_mode values must be one of: LIGHT, DARK, DYNAMIC (for awscc provider compatibility) 4 | resource "awscc_cognito_managed_login_branding" "branding" { 5 | for_each = var.enabled && var.managed_login_branding_enabled ? var.managed_login_branding : {} 6 | 7 | user_pool_id = local.user_pool_id 8 | # Support both client IDs and client names with proper resolution 9 | # Try to look up the value in the client name map first 10 | # If not found, treat it as a literal client ID 11 | client_id = try(local.client_name_to_id_map[each.value.client_id], each.value.client_id) 12 | 13 | # Assets configuration for branding images 14 | assets = try(each.value.assets, []) 15 | 16 | # Settings as JSON for advanced branding configuration 17 | settings = try(each.value.settings, null) 18 | 19 | # Whether to return merged resources (defaults + custom) 20 | return_merged_resources = try(each.value.return_merged_resources, false) 21 | 22 | # Whether to use Cognito provided default values 23 | use_cognito_provided_values = try(each.value.use_cognito_provided_values, false) 24 | 25 | depends_on = [ 26 | aws_cognito_user_pool_client.client 27 | ] 28 | } 29 | 30 | locals { 31 | # Create a map of client names to client IDs for branding lookups 32 | # Handle null/empty names by using the for_each key as fallback 33 | client_name_to_id_map = var.enabled ? { 34 | for k, client in aws_cognito_user_pool_client.client : 35 | coalesce(client.name, k) => client.id 36 | } : {} 37 | 38 | # Create a map of branding configurations for outputs 39 | managed_login_branding_map = var.enabled && var.managed_login_branding_enabled ? { 40 | for k, v in awscc_cognito_managed_login_branding.branding : k => { 41 | id = v.id 42 | managed_login_branding_id = v.managed_login_branding_id 43 | client_id = v.client_id 44 | user_pool_id = v.user_pool_id 45 | } 46 | } : {} 47 | } 48 | -------------------------------------------------------------------------------- /examples/email_mfa/README.md: -------------------------------------------------------------------------------- 1 | # Email MFA Configuration Example 2 | 3 | This example demonstrates how to configure a Cognito User Pool with email-based Multi-Factor Authentication (MFA). 4 | 5 | ## Features 6 | 7 | - Email-based MFA configuration 8 | - Email configuration using Amazon SES 9 | - Account recovery settings 10 | - Auto-verified email attributes 11 | - **Best practices**: Includes `ignore_schema_changes` for future-proofing 12 | 13 | ## Usage 14 | 15 | ```hcl 16 | module "aws_cognito_user_pool_email_mfa_example" { 17 | source = "../../" 18 | 19 | user_pool_name = "email_mfa_pool" 20 | 21 | # Recommended: Enable schema ignore changes for new deployments 22 | # This prevents perpetual diffs if you plan to use custom schemas 23 | ignore_schema_changes = true 24 | 25 | # Email configuration 26 | email_configuration = { 27 | email_sending_account = "DEVELOPER" 28 | from_email_address = "noreply@example.com" 29 | source_arn = "arn:aws:ses:us-east-1:123456789012:identity/example.com" 30 | reply_to_email_address = "reply@example.com" 31 | configuration_set = "my-configuration-set" 32 | } 33 | 34 | # Email MFA configuration 35 | email_mfa_configuration = { 36 | message = "Your verification code is {####}" 37 | subject = "Your verification code" 38 | } 39 | 40 | # Account recovery settings (required for email MFA) 41 | recovery_mechanisms = [ 42 | { 43 | name = "verified_email" 44 | priority = 1 45 | }, 46 | { 47 | name = "verified_phone_number" 48 | priority = 2 49 | } 50 | ] 51 | 52 | # MFA configuration 53 | mfa_configuration = "ON" 54 | 55 | # Auto verify email 56 | auto_verified_attributes = ["email"] 57 | } 58 | ``` 59 | 60 | ## Requirements 61 | 62 | - AWS SES configured and verified domain/email 63 | - Valid SES source ARN 64 | - At least two account recovery mechanisms configured 65 | 66 | ## Notes 67 | 68 | - The `email_sending_account` must be set to "DEVELOPER" to use Amazon SES 69 | - The `source_arn` must be a valid SES ARN 70 | - At least two account recovery mechanisms are required when using email MFA 71 | - The `{####}` placeholder in the message will be replaced with the verification code 72 | -------------------------------------------------------------------------------- /.claude/agents/terraform-cognito.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: terraform-cognito 3 | description: AWS Cognito User Pool specialist for Terraform infrastructure development 4 | --- 5 | 6 | You are a specialized agent for AWS Cognito User Pool Terraform module development. Your expertise includes: 7 | 8 | CORE COMPETENCIES: 9 | - AWS Cognito User Pool resource configuration and best practices 10 | - Terraform module structure for user pools, clients, domains, identity providers 11 | - Cognito security features: MFA, advanced security, password policies 12 | - User pool branding, UI customization, and managed login features 13 | - Resource servers, user groups, and identity provider configurations 14 | 15 | TERRAFORM PATTERNS: 16 | - Prefer for_each over count for resource creation (as per CLAUDE.md) 17 | - Handle schema changes with ignore_schema_changes parameter 18 | - Support conditional resource creation with enabled flag 19 | - Use dynamic blocks for complex nested configurations 20 | - Maintain backward compatibility with existing variable interfaces 21 | 22 | AWS COGNITO EXPERTISE: 23 | - User pool tiers: LITE, ESSENTIALS, PLUS configurations 24 | - Advanced security modes: OFF, AUDIT, ENFORCED 25 | - MFA configurations: SMS, TOTP, email verification 26 | - Identity provider integrations: SAML, OIDC, social providers 27 | - Client configurations: auth flows, scopes, callback URLs 28 | - Domain configurations: custom domains, certificates, DNS 29 | 30 | SECURITY BEST PRACTICES: 31 | - Strong password policies and complexity requirements 32 | - Multi-factor authentication implementation 33 | - Account takeover prevention mechanisms 34 | - Secure token expiration and refresh policies 35 | - Proper IAM roles and policies for Cognito access 36 | 37 | MODULE DEVELOPMENT: 38 | - Variable validation for critical inputs 39 | - Output definitions for dependent resources 40 | - Support multiple input formats for flexibility 41 | - Handle resource creation conflicts gracefully 42 | - Follow established naming conventions 43 | 44 | When working on this module, always: 45 | 1. Reference CLAUDE.md for module-specific patterns and decisions 46 | 2. Consider backward compatibility for existing deployments 47 | 3. Use MCP Terraform server for latest AWS provider documentation 48 | 4. Test configurations with examples in the examples/ directory 49 | 5. Validate security configurations meet compliance requirements 50 | -------------------------------------------------------------------------------- /locals-lambda.tf: -------------------------------------------------------------------------------- 1 | # 2 | # Lambda Configuration Locals 3 | # 4 | # This file contains locals for managing Lambda trigger configurations 5 | # including authentication challenges, custom messages, and various triggers. 6 | # 7 | 8 | locals { 9 | # lambda_config 10 | # Build lambda configuration using modern Terraform syntax 11 | lambda_config_default = { 12 | create_auth_challenge = try(var.lambda_config.create_auth_challenge, var.lambda_config_create_auth_challenge) 13 | custom_message = try(var.lambda_config.custom_message, var.lambda_config_custom_message) 14 | define_auth_challenge = try(var.lambda_config.define_auth_challenge, var.lambda_config_define_auth_challenge) 15 | post_authentication = try(var.lambda_config.post_authentication, var.lambda_config_post_authentication) 16 | post_confirmation = try(var.lambda_config.post_confirmation, var.lambda_config_post_confirmation) 17 | pre_authentication = try(var.lambda_config.pre_authentication, var.lambda_config_pre_authentication) 18 | pre_sign_up = try(var.lambda_config.pre_sign_up, var.lambda_config_pre_sign_up) 19 | pre_token_generation = try(var.lambda_config.pre_token_generation, var.lambda_config_pre_token_generation) 20 | pre_token_generation_config = length(try(var.lambda_config.pre_token_generation_config, var.lambda_config_pre_token_generation_config, {})) == 0 ? [] : [try(var.lambda_config.pre_token_generation_config, var.lambda_config_pre_token_generation_config)] 21 | user_migration = try(var.lambda_config.user_migration, var.lambda_config_user_migration) 22 | verify_auth_challenge_response = try(var.lambda_config.verify_auth_challenge_response, var.lambda_config_verify_auth_challenge_response) 23 | kms_key_id = try(var.lambda_config.kms_key_id, var.lambda_config_kms_key_id) 24 | custom_email_sender = length(try(var.lambda_config.custom_email_sender, var.lambda_config_custom_email_sender, {})) == 0 ? [] : [try(var.lambda_config.custom_email_sender, var.lambda_config_custom_email_sender)] 25 | custom_sms_sender = length(try(var.lambda_config.custom_sms_sender, var.lambda_config_custom_sms_sender, {})) == 0 ? [] : [try(var.lambda_config.custom_sms_sender, var.lambda_config_custom_sms_sender)] 26 | } 27 | 28 | lambda_config = ( 29 | var.lambda_config == null || length(var.lambda_config) == 0 30 | ) ? [] : [local.lambda_config_default] 31 | } 32 | -------------------------------------------------------------------------------- /locals-main.tf: -------------------------------------------------------------------------------- 1 | # 2 | # Core User Pool Configuration Locals 3 | # 4 | # This file contains core locals for the user pool resource management 5 | # and other general configuration settings. 6 | # 7 | 8 | locals { 9 | # Get the user pool resource and ID regardless of which variant is created 10 | user_pool = var.enabled ? ( 11 | var.ignore_schema_changes ? aws_cognito_user_pool.pool_with_schema_ignore[0] : aws_cognito_user_pool.pool[0] 12 | ) : null 13 | user_pool_id = var.enabled ? local.user_pool.id : null 14 | 15 | # username_configuration 16 | # Build username configuration with case sensitivity settings 17 | username_configuration_default = length(var.username_configuration) == 0 ? {} : { 18 | case_sensitive = try(var.username_configuration.case_sensitive, true) 19 | } 20 | username_configuration = length(local.username_configuration_default) == 0 ? [] : [local.username_configuration_default] 21 | 22 | # user_pool_add_ons 23 | # Build user pool add-ons configuration for advanced security features 24 | user_pool_add_ons_default = { 25 | advanced_security_mode = try(var.user_pool_add_ons.advanced_security_mode, var.user_pool_add_ons_advanced_security_mode) 26 | advanced_security_additional_flows = try(var.user_pool_add_ons.advanced_security_additional_flows, var.user_pool_add_ons_advanced_security_additional_flows) 27 | } 28 | 29 | # Only include user pool add-ons if any advanced security features are configured 30 | user_pool_add_ons = ( 31 | var.user_pool_add_ons_advanced_security_mode == null && 32 | var.user_pool_add_ons_advanced_security_additional_flows == null && 33 | length(var.user_pool_add_ons) == 0 34 | ) ? [] : [local.user_pool_add_ons_default] 35 | 36 | # user_attribute_update_settings 37 | # Configure which attributes require verification before update 38 | user_attribute_update_settings = var.user_attribute_update_settings == null ? ( 39 | length(var.auto_verified_attributes) > 0 ? [{ 40 | attributes_require_verification_before_update = var.auto_verified_attributes 41 | }] : [] 42 | ) : [var.user_attribute_update_settings] 43 | 44 | # sign_in_policy 45 | # Build sign-in policy configuration for allowed authentication factors 46 | sign_in_policy_default = { 47 | allowed_first_auth_factors = var.sign_in_policy == null ? var.sign_in_policy_allowed_first_auth_factors : ( 48 | try(var.sign_in_policy.allowed_first_auth_factors, var.sign_in_policy_allowed_first_auth_factors) 49 | ) 50 | } 51 | 52 | # Only include sign-in policy if factors are configured 53 | sign_in_policy = ( 54 | var.sign_in_policy == null && 55 | length(var.sign_in_policy_allowed_first_auth_factors) == 0 56 | ) ? [] : [local.sign_in_policy_default] 57 | } 58 | -------------------------------------------------------------------------------- /.claude/agents/terraform-security.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: terraform-security 3 | description: Security analysis and hardening specialist for Terraform AWS Cognito configurations 4 | --- 5 | 6 | You are a specialized agent for security analysis and hardening of AWS Cognito User Pool configurations. Your expertise includes: 7 | 8 | COGNITO SECURITY FEATURES: 9 | - Advanced security mode (OFF, AUDIT, ENFORCED) configurations 10 | - Multi-factor authentication: SMS, TOTP, email-based MFA 11 | - Password policy enforcement and complexity requirements 12 | - Account takeover prevention mechanisms 13 | - User pool encryption settings and key management 14 | 15 | AUTHENTICATION & AUTHORIZATION: 16 | - Secure authentication flows and client configurations 17 | - OAuth 2.0 and OpenID Connect security best practices 18 | - Scope-based access control implementation 19 | - Identity provider security configurations 20 | - Token expiration and refresh policies 21 | 22 | SECURITY COMPLIANCE: 23 | - GDPR compliance for user data handling 24 | - HIPAA considerations for healthcare applications 25 | - SOC 2 compliance for user authentication 26 | - PCI compliance for payment-related user pools 27 | - Industry-specific security requirements 28 | 29 | INFRASTRUCTURE SECURITY: 30 | - IAM role and policy least privilege principles 31 | - VPC configuration for Cognito resources 32 | - Network security and access controls 33 | - Logging and monitoring for security events 34 | - Backup and disaster recovery considerations 35 | 36 | VULNERABILITY ASSESSMENT: 37 | - Common Cognito misconfigurations identification 38 | - Security configuration drift detection 39 | - Credential exposure prevention 40 | - Session management security analysis 41 | - API security and rate limiting 42 | 43 | SECURITY TESTING: 44 | - Security-focused test scenarios 45 | - Penetration testing considerations 46 | - Compliance validation testing 47 | - Security regression testing 48 | - Automated security scanning integration 49 | 50 | MONITORING & ALERTING: 51 | - CloudTrail logging for Cognito events 52 | - CloudWatch metrics for security monitoring 53 | - Anomaly detection for authentication patterns 54 | - Security incident response procedures 55 | - Audit trail and compliance reporting 56 | 57 | HARDENING RECOMMENDATIONS: 58 | - Security baseline configurations 59 | - Risk assessment and mitigation strategies 60 | - Security control implementation 61 | - Regular security reviews and updates 62 | - Documentation of security decisions 63 | 64 | When performing security analysis, always: 65 | 1. Reference AWS security best practices documentation 66 | 2. Consider the principle of least privilege 67 | 3. Validate all security configurations with tests 68 | 4. Document security decisions and rationale 69 | 5. Provide remediation guidance for identified issues 70 | -------------------------------------------------------------------------------- /locals-email.tf: -------------------------------------------------------------------------------- 1 | # 2 | # Email Configuration Locals 3 | # 4 | # This file contains locals for managing email configuration settings 5 | # including SES configuration, verification templates, and MFA settings. 6 | # 7 | 8 | locals { 9 | # email_configuration 10 | # Build email configuration using modern Terraform syntax 11 | email_configuration_default = { 12 | configuration_set = try(var.email_configuration.configuration_set, var.email_configuration_configuration_set) 13 | reply_to_email_address = try(var.email_configuration.reply_to_email_address, var.email_configuration_reply_to_email_address) 14 | source_arn = try(var.email_configuration.source_arn, var.email_configuration_source_arn) 15 | email_sending_account = try(var.email_configuration.email_sending_account, var.email_configuration_email_sending_account) 16 | from_email_address = try(var.email_configuration.from_email_address, var.email_configuration_from_email_address) 17 | } 18 | 19 | email_configuration = [local.email_configuration_default] 20 | 21 | # email_mfa_configuration 22 | email_mfa_configuration = var.email_mfa_configuration == null ? [] : [var.email_mfa_configuration] 23 | 24 | # verification_message_template 25 | # Build verification message template using modern Terraform syntax 26 | verification_message_template_default = { 27 | default_email_option = try(var.verification_message_template.default_email_option, var.verification_message_template_default_email_option) 28 | email_message = try(var.verification_message_template.email_message, var.verification_message_template_email_message) 29 | email_message_by_link = try(var.verification_message_template.email_message_by_link, var.verification_message_template_email_message_by_link) 30 | email_subject = try(var.verification_message_template.email_subject, var.verification_message_template_email_subject) 31 | email_subject_by_link = try(var.verification_message_template.email_subject_by_link, var.verification_message_template_email_subject_by_link) 32 | sms_message = try(var.verification_message_template.sms_message, var.verification_message_template_sms_message) 33 | } 34 | 35 | verification_message_template = [local.verification_message_template_default] 36 | 37 | # Verification email settings with improved fallback logic 38 | verification_email_subject = try(local.verification_message_template_default.email_subject, "") == "" ? ( 39 | coalesce(var.email_verification_subject, var.admin_create_user_config_email_subject) 40 | ) : null 41 | 42 | verification_email_message = try(local.verification_message_template_default.email_message, "") == "" ? ( 43 | coalesce(var.email_verification_message, var.admin_create_user_config_email_message) 44 | ) : null 45 | } 46 | -------------------------------------------------------------------------------- /.secrets.baseline: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.5.0", 3 | "plugins_used": [ 4 | { 5 | "name": "ArtifactoryDetector" 6 | }, 7 | { 8 | "name": "AWSKeyDetector" 9 | }, 10 | { 11 | "name": "AzureStorageKeyDetector" 12 | }, 13 | { 14 | "name": "Base64HighEntropyString", 15 | "limit": 4.5 16 | }, 17 | { 18 | "name": "BasicAuthDetector" 19 | }, 20 | { 21 | "name": "CloudantDetector" 22 | }, 23 | { 24 | "name": "DiscordBotTokenDetector" 25 | }, 26 | { 27 | "name": "EmailAddressDetector" 28 | }, 29 | { 30 | "name": "HexHighEntropyString", 31 | "limit": 3.0 32 | }, 33 | { 34 | "name": "IbmCloudIamDetector" 35 | }, 36 | { 37 | "name": "IbmCosHmacDetector" 38 | }, 39 | { 40 | "name": "JwtTokenDetector" 41 | }, 42 | { 43 | "name": "KeywordDetector", 44 | "keyword_exclude": "" 45 | }, 46 | { 47 | "name": "MailchimpDetector" 48 | }, 49 | { 50 | "name": "NpmDetector" 51 | }, 52 | { 53 | "name": "PrivateKeyDetector" 54 | }, 55 | { 56 | "name": "SendGridDetector" 57 | }, 58 | { 59 | "name": "SlackDetector" 60 | }, 61 | { 62 | "name": "SoftlayerDetector" 63 | }, 64 | { 65 | "name": "SquareOAuthDetector" 66 | }, 67 | { 68 | "name": "StripeDetector" 69 | }, 70 | { 71 | "name": "TwilioKeyDetector" 72 | } 73 | ], 74 | "filters_used": [ 75 | { 76 | "path": "detect_secrets.filters.allowlist.is_line_allowlisted" 77 | }, 78 | { 79 | "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", 80 | "min_level": 2 81 | }, 82 | { 83 | "path": "detect_secrets.filters.heuristic.is_indirect_reference" 84 | }, 85 | { 86 | "path": "detect_secrets.filters.heuristic.is_likely_id_string" 87 | }, 88 | { 89 | "path": "detect_secrets.filters.heuristic.is_lock_file" 90 | }, 91 | { 92 | "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" 93 | }, 94 | { 95 | "path": "detect_secrets.filters.heuristic.is_potential_uuid" 96 | }, 97 | { 98 | "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" 99 | }, 100 | { 101 | "path": "detect_secrets.filters.heuristic.is_sequential_string" 102 | }, 103 | { 104 | "path": "detect_secrets.filters.heuristic.is_swagger_file" 105 | }, 106 | { 107 | "path": "detect_secrets.filters.heuristic.is_templated_secret" 108 | } 109 | ], 110 | "results": {}, 111 | "generated_at": "2025-07-28T22:30:00Z" 112 | } 113 | -------------------------------------------------------------------------------- /examples/with_branding/main.tf: -------------------------------------------------------------------------------- 1 | # 2 | # Cognito User Pool with Managed Login Branding 3 | # 4 | module "aws_cognito_user_pool" { 5 | source = "../../" 6 | 7 | user_pool_name = var.user_pool_name 8 | user_pool_tier = var.user_pool_tier 9 | 10 | alias_attributes = ["email", "preferred_username"] 11 | auto_verified_attributes = ["email"] 12 | 13 | # User pool client configuration 14 | clients = [ 15 | { 16 | name = "app-client-web" 17 | callback_urls = ["https://example.com/callback"] 18 | logout_urls = ["https://example.com/logout"] 19 | default_redirect_uri = "https://example.com/callback" 20 | read_attributes = ["email", "email_verified"] 21 | 22 | allowed_oauth_flows_user_pool_client = true 23 | allowed_oauth_flows = ["code"] 24 | allowed_oauth_scopes = ["email", "openid", "profile"] 25 | 26 | explicit_auth_flows = [ 27 | "ALLOW_USER_PASSWORD_AUTH", 28 | "ALLOW_USER_SRP_AUTH", 29 | "ALLOW_REFRESH_TOKEN_AUTH" 30 | ] 31 | 32 | supported_identity_providers = ["COGNITO"] 33 | } 34 | ] 35 | 36 | # Domain configuration 37 | domain = var.domain_name 38 | 39 | # Managed Login Branding configuration 40 | managed_login_branding_enabled = var.enable_branding 41 | managed_login_branding = var.enable_branding ? { 42 | "main" = { 43 | client_id = "app-client-web" # Reference client by name 44 | assets = [ 45 | { 46 | bytes = filebase64("${path.module}/assets/logo-light.svg") 47 | category = "PAGE_HEADER_LOGO" 48 | color_mode = "LIGHT" 49 | extension = "SVG" 50 | }, 51 | { 52 | bytes = filebase64("${path.module}/assets/logo-dark.svg") 53 | category = "PAGE_HEADER_LOGO" 54 | color_mode = "DARK" 55 | extension = "SVG" 56 | }, 57 | { 58 | bytes = filebase64("${path.module}/assets/logo-light.svg") 59 | category = "FORM_LOGO" 60 | color_mode = "LIGHT" 61 | extension = "SVG" 62 | }, 63 | { 64 | bytes = filebase64("${path.module}/assets/logo-dark.svg") 65 | category = "FORM_LOGO" 66 | color_mode = "DARK" 67 | extension = "SVG" 68 | }, 69 | { 70 | bytes = filebase64("${path.module}/assets/background.svg") 71 | category = "PAGE_BACKGROUND" 72 | color_mode = "DYNAMIC" 73 | extension = "SVG" 74 | }, 75 | ] 76 | # Use AWS default values for styling, just custom assets 77 | use_cognito_provided_values = true 78 | return_merged_resources = true 79 | } 80 | } : {} 81 | 82 | tags = var.tags 83 | } 84 | -------------------------------------------------------------------------------- /.tfsec.yml: -------------------------------------------------------------------------------- 1 | # TFSec configuration for Cognito User Pool security scanning 2 | # See https://aquasecurity.github.io/tfsec/latest/getting-started/configuration/ 3 | 4 | # Severity levels: CRITICAL, HIGH, MEDIUM, LOW 5 | minimum_severity: MEDIUM 6 | 7 | # Include specific checks for Cognito and IAM 8 | include: 9 | # Cognito specific checks 10 | - AWS048 # Cognito User Pool should define at least one attribute 11 | - AWS049 # Cognito User Pool should define MFA configuration 12 | - AWS050 # Cognito User Pool domain should not use deprecated domain 13 | - AWS051 # Cognito User Pool should define password policy 14 | - AWS052 # Cognito User Pool client should not have secret 15 | - AWS053 # Cognito User Pool should define schema 16 | 17 | # IAM security checks 18 | - AWS001 # S3 Bucket has an ACL defined which allows public read access 19 | - AWS002 # S3 Bucket does not have logging enabled 20 | - AWS003 # AWS Classic resource usage 21 | - AWS004 # Use of plain HTTP 22 | - AWS005 # Load balancer is exposed to the internet 23 | - AWS062 # IAM policy document is overly permissive 24 | - AWS063 # IAM managed policy allows * action 25 | - AWS064 # IAM policy document allows * resource 26 | - AWS065 # IAM policy document allows * principal 27 | - AWS066 # IAM policy document allows root user 28 | - AWS067 # IAM role allows assume role from all accounts 29 | - AWS068 # IAM role or user policy allows all or contains wildcards 30 | 31 | # Exclude checks that are not relevant or too restrictive for modules 32 | exclude: 33 | - AWS018 # Resource is not encrypted - allow module flexibility 34 | - AWS019 # Resource does not have encryption enabled - allow module flexibility 35 | - AWS020 # CloudTrail logging is not enabled - not relevant for Cognito modules 36 | - AWS021 # CloudWatch Log Group does not have retention set - allow module flexibility 37 | - AWS025 # API Gateway domain name uses outdated SSL/TLS - not relevant 38 | - AWS026 # SQS queue policy is wildcard - not relevant for Cognito 39 | - AWS027 # SQS queue should be encrypted - not relevant for Cognito 40 | 41 | # Custom severity overrides for Cognito-specific issues 42 | severity_overrides: 43 | AWS049: HIGH # MFA configuration is important for security 44 | AWS051: HIGH # Password policy is critical for identity security 45 | AWS062: CRITICAL # Overly permissive IAM policies are critical in identity systems 46 | AWS063: CRITICAL # Wildcard actions in IAM are critical security risks 47 | AWS064: HIGH # Wildcard resources in IAM are high risk 48 | AWS065: HIGH # Wildcard principals in IAM are high risk 49 | 50 | # Enable soft fail mode to prevent CI failures during initial setup 51 | soft_fail: false 52 | 53 | # Output format 54 | format: default 55 | 56 | # Custom checks directory (if any) 57 | custom_check_dir: "" 58 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # GitHub Actions updates 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | day: "monday" 9 | time: "09:00" 10 | open-pull-requests-limit: 2 11 | commit-message: 12 | prefix: "ci" 13 | include: "scope" 14 | 15 | # Terraform updates for root module 16 | - package-ecosystem: "terraform" 17 | directory: "/" 18 | schedule: 19 | interval: "monthly" 20 | day: "monday" 21 | time: "10:00" 22 | open-pull-requests-limit: 2 23 | commit-message: 24 | prefix: "deps" 25 | include: "scope" 26 | 27 | # Terraform updates for examples 28 | - package-ecosystem: "terraform" 29 | directory: "/examples/simple" 30 | schedule: 31 | interval: "monthly" 32 | day: "monday" 33 | time: "11:00" 34 | open-pull-requests-limit: 2 35 | commit-message: 36 | prefix: "deps" 37 | include: "scope" 38 | 39 | - package-ecosystem: "terraform" 40 | directory: "/examples/complete" 41 | schedule: 42 | interval: "monthly" 43 | day: "monday" 44 | time: "12:00" 45 | open-pull-requests-limit: 2 46 | commit-message: 47 | prefix: "deps" 48 | include: "scope" 49 | 50 | - package-ecosystem: "terraform" 51 | directory: "/examples/email_mfa" 52 | schedule: 53 | interval: "monthly" 54 | day: "monday" 55 | time: "13:00" 56 | open-pull-requests-limit: 2 57 | commit-message: 58 | prefix: "deps" 59 | include: "scope" 60 | 61 | - package-ecosystem: "terraform" 62 | directory: "/examples/simple_extended" 63 | schedule: 64 | interval: "monthly" 65 | day: "monday" 66 | time: "14:00" 67 | open-pull-requests-limit: 2 68 | commit-message: 69 | prefix: "deps" 70 | include: "scope" 71 | 72 | - package-ecosystem: "terraform" 73 | directory: "/examples/with_branding" 74 | schedule: 75 | interval: "monthly" 76 | day: "monday" 77 | time: "15:00" 78 | open-pull-requests-limit: 2 79 | commit-message: 80 | prefix: "deps" 81 | include: "scope" 82 | 83 | - package-ecosystem: "terraform" 84 | directory: "/examples/refresh_token_rotation" 85 | schedule: 86 | interval: "monthly" 87 | day: "monday" 88 | time: "16:00" 89 | open-pull-requests-limit: 2 90 | commit-message: 91 | prefix: "deps" 92 | include: "scope" 93 | 94 | # Go modules for testing 95 | - package-ecosystem: "gomod" 96 | directory: "/test" 97 | schedule: 98 | interval: "monthly" 99 | day: "monday" 100 | time: "17:00" 101 | open-pull-requests-limit: 2 102 | commit-message: 103 | prefix: "deps" 104 | include: "scope" 105 | -------------------------------------------------------------------------------- /examples/simple_extended/main.tf: -------------------------------------------------------------------------------- 1 | module "aws_cognito_user_pool_simple_extended_example" { 2 | 3 | source = "../../" 4 | 5 | user_pool_name = "simple_extended_pool" 6 | alias_attributes = ["email", "phone_number"] 7 | auto_verified_attributes = ["email"] 8 | sms_authentication_message = "Your username is {username} and temporary password is {####}." 9 | sms_verification_message = "This is the verification message {####}." 10 | lambda_config_verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:my_lambda_function" 11 | password_policy_require_lowercase = false 12 | password_policy_minimum_length = 11 13 | user_pool_add_ons_advanced_security_mode = "OFF" 14 | verification_message_template_default_email_option = "CONFIRM_WITH_CODE" 15 | 16 | # IMPORTANT: Enable schema ignore changes to prevent perpetual diffs with custom schemas 17 | # This example uses custom schemas (schemas and string_schemas), so this prevents AWS API errors 18 | ignore_schema_changes = true 19 | 20 | # schemas 21 | schemas = [ 22 | { 23 | attribute_data_type = "Boolean" 24 | developer_only_attribute = false 25 | mutable = true 26 | name = "available" 27 | required = false 28 | }, 29 | ] 30 | 31 | string_schemas = [ 32 | { 33 | attribute_data_type = "String" 34 | developer_only_attribute = false 35 | mutable = false 36 | name = "email" 37 | required = true 38 | 39 | string_attribute_constraints = { 40 | min_length = 7 41 | max_length = 15 42 | } 43 | }, 44 | ] 45 | 46 | # user_pool_domain 47 | domain = "mydomain-com" 48 | 49 | # client 50 | client_name = "client0" 51 | client_allowed_oauth_flows_user_pool_client = false 52 | client_callback_urls = ["https://mydomain.com/callback"] 53 | client_default_redirect_uri = "https://mydomain.com/callback" 54 | client_read_attributes = ["email"] 55 | client_refresh_token_validity = 30 56 | 57 | 58 | # user_group 59 | user_group_name = "mygroup" 60 | user_group_description = "My group" 61 | 62 | # resource server 63 | resource_server_identifier = "https://mydomain.com" 64 | resource_server_name = "mydomain" 65 | resource_server_scope_name = "scope" 66 | resource_server_scope_description = "a Sample Scope Description for mydomain" 67 | 68 | # tags 69 | tags = { 70 | Owner = "infra" 71 | Environment = "production" 72 | Terraform = true 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/refresh_token_rotation/README.md: -------------------------------------------------------------------------------- 1 | # Refresh Token Rotation Example 2 | 3 | This example demonstrates how to configure AWS Cognito User Pool with refresh token rotation enabled for enhanced security. 4 | 5 | ## Features Demonstrated 6 | 7 | - **Refresh Token Rotation**: Enables automatic rotation of refresh tokens for enhanced security 8 | - **Configurable Grace Period**: Sets a retry grace period for token rotation failures 9 | - **Token Validity Configuration**: Demonstrates proper token lifetime settings 10 | - **Security Best Practices**: Combines refresh token rotation with token revocation 11 | 12 | ## Refresh Token Rotation Benefits 13 | 14 | Refresh token rotation is a security best practice that provides: 15 | 16 | 1. **Enhanced Security**: Each token refresh provides a completely new set of tokens 17 | 2. **Reduced Token Exposure**: Limits the window of vulnerability for compromised tokens 18 | 3. **Replay Attack Protection**: Old refresh tokens become invalid after use 19 | 4. **Configurable Grace Period**: Allows handling of network issues and retries 20 | 21 | ## Configuration Details 22 | 23 | ### Refresh Token Rotation 24 | 25 | ```hcl 26 | refresh_token_rotation = { 27 | feature = "ENABLED" # Enable rotation 28 | retry_grace_period_seconds = 60 # Maximum 60 seconds per AWS limits 29 | } 30 | ``` 31 | 32 | - **feature**: Set to "ENABLED" to enable refresh token rotation, or "DISABLED" to disable 33 | - **retry_grace_period_seconds**: Grace period (0-60 seconds) for handling retry scenarios 34 | 35 | ### Token Validity Settings 36 | 37 | The example demonstrates recommended token validity periods: 38 | 39 | - **Access tokens**: 60 minutes (short-lived for security) 40 | - **ID tokens**: 60 minutes (short-lived for security) 41 | - **Refresh tokens**: 30 days (longer-lived but with rotation) 42 | 43 | ## Usage 44 | 45 | 1. Clone this repository 46 | 2. Navigate to this example directory 47 | 3. Configure your AWS credentials 48 | 4. Run the following commands: 49 | 50 | ```bash 51 | terraform init 52 | terraform plan 53 | terraform apply 54 | ``` 55 | 56 | ## Security Considerations 57 | 58 | When implementing refresh token rotation: 59 | 60 | 1. **Client Implementation**: Ensure your application can handle new token sets on each refresh 61 | 2. **Error Handling**: Implement proper error handling for token rotation failures 62 | 3. **Grace Period**: Set appropriate grace period based on your network conditions 63 | 4. **Token Storage**: Store tokens securely and update all stored tokens on rotation 64 | 65 | ## Clean Up 66 | 67 | To clean up the resources created by this example: 68 | 69 | ```bash 70 | terraform destroy 71 | ``` 72 | 73 | ## More Information 74 | 75 | - [AWS Cognito Refresh Token Rotation Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-refresh-token.html) 76 | - [Terraform AWS Cognito User Pool Client](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool_client) 77 | -------------------------------------------------------------------------------- /bug_fix_summary.md: -------------------------------------------------------------------------------- 1 | # Bug Fix Summary: AWS Cognito Managed Login Branding Client ID Resolution 2 | 3 | ## Issue Description 4 | The `awscc_cognito_managed_login_branding` resource's `client_id` resolution logic had two critical flaws: 5 | 6 | 1. **Incorrect Regex Pattern**: The regex `^[a-zA-Z0-9+]{26}$` incorrectly included the `+` character for detecting literal AWS Cognito client IDs. AWS Cognito client IDs are 26-character alphanumeric strings (A-Z, a-z, 0-9 only), without plus signs. 7 | 8 | 2. **Unsafe Fallback Logic**: When a client name lookup failed, the original code passed the invalid input directly as a client ID using `lookup(local.client_name_to_id_map, each.value.client_id, each.value.client_id)`, which would cause AWS API errors. 9 | 10 | ## Root Cause 11 | - Client names that were exactly 26 characters long and alphanumeric were misidentified as literal client IDs due to the incorrect regex pattern 12 | - Failed client name lookups resulted in passing invalid inputs to the AWS API instead of failing fast 13 | 14 | ## Solution Implemented 15 | 16 | ### File: `managed-login-branding.tf` (lines 7-11) 17 | 18 | **Before:** 19 | ```terraform 20 | # AWS Cognito client IDs are 26-character alphanumeric strings (pattern: [\w+]+) 21 | # If the input is exactly 26 characters and alphanumeric, treat it as a literal client ID 22 | # Otherwise, look it up in the client name map 23 | client_id = can(regex("^[a-zA-Z0-9+]{26}$", each.value.client_id)) ? each.value.client_id : lookup(local.client_name_to_id_map, each.value.client_id, each.value.client_id) 24 | ``` 25 | 26 | **After:** 27 | ```terraform 28 | # AWS Cognito client IDs are 26-character alphanumeric strings (A-Z, a-z, 0-9 only) 29 | # If the input is exactly 26 characters and alphanumeric, treat it as a literal client ID 30 | # Otherwise, look it up in the client name map (will fail if not found) 31 | client_id = can(regex("^[a-zA-Z0-9]{26}$", each.value.client_id)) ? each.value.client_id : local.client_name_to_id_map[each.value.client_id] 32 | ``` 33 | 34 | ## Changes Made 35 | 36 | 1. **Fixed Regex Pattern**: 37 | - Changed from `^[a-zA-Z0-9+]{26}$` to `^[a-zA-Z0-9]{26}$` 38 | - Removed the incorrect `+` character from the pattern 39 | - Updated comment to clarify the correct format 40 | 41 | 2. **Improved Error Handling**: 42 | - Replaced `lookup(local.client_name_to_id_map, each.value.client_id, each.value.client_id)` with `local.client_name_to_id_map[each.value.client_id]` 43 | - This ensures Terraform will fail fast with a clear error message when a client name is not found 44 | - Prevents invalid inputs from being passed to the AWS API 45 | 46 | ## Impact 47 | - Client names that are 26 characters long and alphanumeric will now be correctly looked up in the client name map instead of being treated as literal client IDs 48 | - Invalid client name references will now fail with a clear Terraform error instead of causing AWS API errors 49 | - The configuration is more robust and provides better error reporting 50 | 51 | ## Testing Recommendations 52 | - Test with 26-character alphanumeric client names to ensure they're properly resolved 53 | - Test with invalid client names to verify proper error handling 54 | - Verify that legitimate 26-character client IDs still work correctly 55 | -------------------------------------------------------------------------------- /.claude/agents/module-documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: module-documentation 3 | description: Documentation and example specialist for Terraform AWS Cognito User Pool module 4 | --- 5 | 6 | You are a specialized agent for creating and maintaining documentation and examples for the terraform-aws-cognito-user-pool module. Your expertise includes: 7 | 8 | DOCUMENTATION STANDARDS: 9 | - Clear and comprehensive README documentation 10 | - Variable documentation with types and validation rules 11 | - Output documentation with descriptions and usage examples 12 | - Architecture diagrams and configuration patterns 13 | - Migration guides and upgrade procedures 14 | 15 | EXAMPLE DEVELOPMENT: 16 | - Simple example: Basic user pool configuration 17 | - Complete example: Full-featured configuration with all options 18 | - Email MFA example: Email-based multi-factor authentication 19 | - Simple extended example: Basic plus clients, domain, resource servers 20 | - With branding example: Managed login branding and UI customization 21 | 22 | TERRAFORM DOCUMENTATION: 23 | - HCL syntax and formatting best practices 24 | - Variable type definitions and constraints 25 | - Resource relationship documentation 26 | - Module composition patterns 27 | - Provider version compatibility 28 | 29 | USER GUIDES: 30 | - Getting started guides and tutorials 31 | - Common configuration patterns and recipes 32 | - Troubleshooting guides and FAQ 33 | - Best practices and security considerations 34 | - Integration guides with other AWS services 35 | 36 | API REFERENCE: 37 | - Input variable reference documentation 38 | - Output value reference documentation 39 | - Resource configuration examples 40 | - Attribute mapping and relationships 41 | - Version compatibility matrices 42 | 43 | EXAMPLE VALIDATION: 44 | - Example configuration testing and validation 45 | - Terraform plan validation for examples 46 | - Cost estimation for example configurations 47 | - Security review of example configurations 48 | - Performance considerations for examples 49 | 50 | CONTENT ORGANIZATION: 51 | - Logical structure and navigation 52 | - Cross-references and linking 53 | - Code example formatting and highlighting 54 | - Version-specific documentation 55 | - Multi-format output (README, docs, wiki) 56 | 57 | VISUAL DOCUMENTATION: 58 | - Architecture diagrams for complex configurations 59 | - Flow diagrams for authentication processes 60 | - Configuration relationship diagrams 61 | - Migration flow visualizations 62 | - Integration pattern diagrams 63 | 64 | MAINTENANCE PROCEDURES: 65 | - Documentation update procedures 66 | - Example synchronization with module changes 67 | - Link validation and maintenance 68 | - Version-specific documentation branches 69 | - Automated documentation generation 70 | 71 | ACCESSIBILITY: 72 | - Clear language and terminology 73 | - Beginner-friendly explanations 74 | - Progressive complexity in examples 75 | - Multiple learning paths and approaches 76 | - Community contribution guidelines 77 | 78 | When creating documentation: 79 | 1. Always validate examples with terraform plan/apply 80 | 2. Follow the established example structure in examples/ 81 | 3. Use consistent formatting and style 82 | 4. Reference the module's CLAUDE.md for specific patterns 83 | 5. Consider the audience (beginners vs advanced users) 84 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | name: Security Scanning 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | paths: 7 | - '**.tf' 8 | - '**.tfvars' 9 | push: 10 | branches: [master] 11 | paths: 12 | - '**.tf' 13 | - '**.tfvars' 14 | 15 | jobs: 16 | security: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 25 19 | permissions: 20 | contents: read 21 | security-events: write 22 | pull-requests: read 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Run tfsec 31 | uses: aquasecurity/tfsec-action@b466648d6e39e7c75324f25d83891162a721f2d6 # v1.0.3 32 | with: 33 | soft_fail: true 34 | format: sarif 35 | additional_args: --out=tfsec.sarif 36 | 37 | - name: Run Checkov 38 | uses: bridgecrewio/checkov-action@cba89e33f08479973cadc681333ffe84f7c8e824 # v3.2.477 39 | with: 40 | directory: . 41 | framework: terraform 42 | soft_fail: true 43 | output_format: sarif 44 | output_file_path: checkov.sarif 45 | skip_path: examples/ 46 | 47 | - name: Upload tfsec SARIF to GitHub Security 48 | if: always() 49 | uses: github/codeql-action/upload-sarif@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 50 | with: 51 | sarif_file: tfsec.sarif 52 | category: tfsec 53 | 54 | - name: Upload Checkov SARIF to GitHub Security 55 | if: always() 56 | uses: github/codeql-action/upload-sarif@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 57 | with: 58 | sarif_file: checkov.sarif 59 | category: checkov 60 | 61 | - name: Security scan summary 62 | if: always() 63 | run: | 64 | echo "## 🔒 Security Scan Results" >> $GITHUB_STEP_SUMMARY 65 | echo "" >> $GITHUB_STEP_SUMMARY 66 | 67 | if [ "${{ job.status }}" == "success" ]; then 68 | echo "✅ Security scans completed successfully!" >> $GITHUB_STEP_SUMMARY 69 | echo "" >> $GITHUB_STEP_SUMMARY 70 | echo "**Tools executed:**" >> $GITHUB_STEP_SUMMARY 71 | echo "- 🔍 tfsec - Terraform security scanner" >> $GITHUB_STEP_SUMMARY 72 | echo "- 🛡️ Checkov - Infrastructure security analysis" >> $GITHUB_STEP_SUMMARY 73 | echo "" >> $GITHUB_STEP_SUMMARY 74 | echo "**Results uploaded to:**" >> $GITHUB_STEP_SUMMARY 75 | echo "- GitHub Security tab (SARIF format)" >> $GITHUB_STEP_SUMMARY 76 | echo "- Available for review in Security → Code scanning alerts" >> $GITHUB_STEP_SUMMARY 77 | else 78 | echo "⚠️ Security scans completed with findings" >> $GITHUB_STEP_SUMMARY 79 | echo "" >> $GITHUB_STEP_SUMMARY 80 | echo "Please review the logs above and check the GitHub Security tab for details." >> $GITHUB_STEP_SUMMARY 81 | fi 82 | 83 | echo "" >> $GITHUB_STEP_SUMMARY 84 | echo "**Note:** This workflow runs with soft-fail enabled to prevent blocking development while security posture is being established." >> $GITHUB_STEP_SUMMARY 85 | -------------------------------------------------------------------------------- /.claude/agents/cognito-migration.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: cognito-migration 3 | description: AWS Cognito upgrade and migration specialist for Terraform modules 4 | --- 5 | 6 | You are a specialized agent for AWS Cognito User Pool migrations, upgrades, and configuration transitions. Your expertise includes: 7 | 8 | VERSION MIGRATIONS: 9 | - Terraform provider version upgrades (AWS provider v3 to v5+) 10 | - Module version transitions and breaking changes 11 | - State file migration and resource import strategies 12 | - Backwards compatibility maintenance during transitions 13 | - Schema change management with ignore_schema_changes 14 | 15 | COGNITO FEATURE MIGRATIONS: 16 | - Legacy authentication flows to modern flows 17 | - User pool tier migrations (LITE → ESSENTIALS → PLUS) 18 | - MFA configuration transitions and user migration 19 | - Identity provider migrations (SAML, OIDC transitions) 20 | - Custom authentication flow migrations 21 | 22 | DATA MIGRATION STRATEGIES: 23 | - User data preservation during migrations 24 | - User pool consolidation strategies 25 | - Cross-region user pool migrations 26 | - Bulk user import/export procedures 27 | - Group and role migration patterns 28 | 29 | CONFIGURATION TRANSITIONS: 30 | - Client configuration updates and migrations 31 | - Domain migration (hosted UI to custom domains) 32 | - Branding and UI customization transitions 33 | - Resource server and scope migrations 34 | - Email configuration transitions (SES integration) 35 | 36 | STATE MANAGEMENT: 37 | - Terraform state manipulation for migrations 38 | - Resource import strategies for existing resources 39 | - State file backup and recovery procedures 40 | - Multi-environment migration coordination 41 | - Rollback strategies and procedures 42 | 43 | COMPATIBILITY ANALYSIS: 44 | - Breaking change identification and impact assessment 45 | - Dependency analysis for connected services 46 | - API compatibility validation 47 | - Client-side application impact analysis 48 | - Third-party integration compatibility 49 | 50 | MIGRATION PLANNING: 51 | - Risk assessment and mitigation strategies 52 | - Migration timeline and milestone planning 53 | - Testing strategies for migration validation 54 | - Rollback procedures and contingency planning 55 | - Communication and stakeholder management 56 | 57 | AUTOMATION TOOLS: 58 | - Migration script development and testing 59 | - Automated validation and verification 60 | - Infrastructure as Code (IaC) migration tools 61 | - CI/CD pipeline integration for migrations 62 | - Monitoring and alerting during migrations 63 | 64 | POST-MIGRATION VALIDATION: 65 | - Functionality testing and validation 66 | - Performance impact assessment 67 | - Security configuration verification 68 | - User experience validation 69 | - Monitoring and alerting setup 70 | 71 | DOCUMENTATION & PROCEDURES: 72 | - Migration runbook development 73 | - Troubleshooting guides and procedures 74 | - Lessons learned documentation 75 | - Best practices documentation 76 | - Training and knowledge transfer 77 | 78 | When handling migrations, always: 79 | 1. Create comprehensive backup strategies before starting 80 | 2. Use the migrate-clients.sh script as reference for client migrations 81 | 3. Test migrations in non-production environments first 82 | 4. Document all changes and decisions made during migration 83 | 5. Provide rollback procedures for every migration step 84 | -------------------------------------------------------------------------------- /examples/simple_extended/README.md: -------------------------------------------------------------------------------- 1 | # This is the simple example, but extended 2 | 3 | This example extends the basic Cognito User Pool configuration by adding custom schemas, domain, client, user group, and resource server configurations. 4 | 5 | ## Important: Schema Management 6 | 7 | ⚠️ **This example uses custom schemas** (`schemas` and `string_schemas`). The `ignore_schema_changes = true` variable is **essential** to prevent perpetual diffs and AWS API errors. 8 | 9 | ```hcl 10 | module "aws_cognito_user_pool_simple_extended_example" { 11 | 12 | source = "lgallard/cognito-user-pool/aws" 13 | 14 | user_pool_name = "simple_extended_pool" 15 | alias_attributes = ["email", "phone_number"] 16 | auto_verified_attributes = ["email"] 17 | sms_authentication_message = "Your username is {username} and temporary password is {####}." 18 | sms_verification_message = "This is the verification message {####}." 19 | lambda_config_verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:my_lambda_function" 20 | password_policy_require_lowercase = false 21 | password_policy_minimum_length = 11 22 | user_pool_add_ons_advanced_security_mode = "OFF" 23 | verification_message_template_default_email_option = "CONFIRM_WITH_CODE" 24 | 25 | # IMPORTANT: Enable schema ignore changes to prevent perpetual diffs with custom schemas 26 | # This example uses custom schemas (schemas and string_schemas), so this prevents AWS API errors 27 | ignore_schema_changes = true 28 | 29 | # schemas 30 | schemas = [ 31 | { 32 | attribute_data_type = "Boolean" 33 | developer_only_attribute = false 34 | mutable = true 35 | name = "available" 36 | required = false 37 | }, 38 | ] 39 | 40 | string_schemas = [ 41 | { 42 | attribute_data_type = "String" 43 | developer_only_attribute = false 44 | mutable = false 45 | name = "email" 46 | required = true 47 | 48 | string_attribute_constraints = { 49 | min_length = 7 50 | max_length = 15 51 | } 52 | }, 53 | ] 54 | 55 | # user_pool_domain 56 | domain = "mydomain-com" 57 | 58 | # client 59 | client_name = "client0" 60 | client_allowed_oauth_flows_user_pool_client = false 61 | client_callback_urls = ["https://mydomain.com/callback"] 62 | client_default_redirect_uri = "https://mydomain.com/callback" 63 | client_read_attributes = ["email"] 64 | client_refresh_token_validity = 30 65 | 66 | 67 | # user_group 68 | user_group_name = "mygroup" 69 | user_group_description = "My group" 70 | 71 | # resource server 72 | resource_server_identifier = "https://mydomain.com" 73 | resource_server_name = "mydomain" 74 | resource_server_scope_name = "scope" 75 | resource_server_scope_description = "a Sample Scope Description for mydomain" 76 | 77 | # tags 78 | tags = { 79 | Owner = "infra" 80 | Environment = "production" 81 | Terraform = true 82 | } 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /examples/with_branding/assets/generate-sample-assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Generate sample SVG assets for Cognito Managed Login Branding 3 | # Creates SVG files that match the example configuration 4 | 5 | set -e 6 | 7 | echo "🎨 Generating sample branding assets..." 8 | 9 | # Create assets directory if it doesn't exist 10 | mkdir -p "$(dirname "$0")" 11 | 12 | cd "$(dirname "$0")" 13 | 14 | # Function to validate file size (2MB limit) 15 | validate_file_size() { 16 | local file="$1" 17 | local max_size=2097152 # 2MB in bytes 18 | 19 | if [[ -f "$file" ]]; then 20 | local file_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo "0") 21 | if [[ $file_size -gt $max_size ]]; then 22 | echo "❌ Warning: $file exceeds 2MB limit ($file_size bytes)" 23 | return 1 24 | fi 25 | fi 26 | return 0 27 | } 28 | 29 | echo "📝 Creating logo-light.svg (200x60px, blue background, white text)..." 30 | cat > logo-light.svg << 'EOF' 31 | 32 | 33 | My App 34 | 35 | EOF 36 | 37 | echo "📝 Creating logo-dark.svg (200x60px, white background, dark text)..." 38 | cat > logo-dark.svg << 'EOF' 39 | 40 | 41 | My App 42 | 43 | EOF 44 | 45 | echo "📝 Creating background.svg (1920x1080px, gradient)..." 46 | cat > background.svg << 'EOF' 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | EOF 57 | 58 | echo "📝 Creating favicon.svg (32x32px, blue square)..." 59 | cat > favicon.svg << 'EOF' 60 | 61 | 62 | A 63 | 64 | EOF 65 | 66 | # Validate file sizes 67 | echo "🔍 Validating file sizes..." 68 | files_created=("logo-light.svg" "logo-dark.svg" "background.svg" "favicon.svg") 69 | validation_failed=false 70 | 71 | for file in "${files_created[@]}"; do 72 | if ! validate_file_size "$file"; then 73 | validation_failed=true 74 | fi 75 | done 76 | 77 | if [[ "$validation_failed" == true ]]; then 78 | echo "❌ Some files exceed the 2MB limit. Please optimize them before deployment." 79 | exit 1 80 | fi 81 | 82 | echo "✅ Sample assets generated successfully!" 83 | echo "" 84 | echo "📋 Generated files:" 85 | echo " - logo-light.svg (200x60px) - Light theme logo" 86 | echo " - logo-dark.svg (200x60px) - Dark theme logo" 87 | echo " - background.svg (1920x1080px) - Background image" 88 | echo " - favicon.svg (32x32px) - Browser favicon" 89 | echo "" 90 | echo "💡 Customize these files with your actual branding assets before deployment." 91 | echo "🔧 Edit colors, text, and dimensions as needed for your brand." 92 | echo "✅ All files are under the 2MB AWS limit and ready for use." 93 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migration Guide - AWS Provider 6.x 2 | 3 | ## ⚠️ BREAKING CHANGE: AWS Provider 6.x Required 4 | 5 | This module now requires AWS Provider **6.0 or later** due to breaking changes in the `advanced_security_additional_flows` configuration syntax. 6 | 7 | ### What Changed 8 | 9 | **AWS Provider 5.x** (deprecated syntax): 10 | ```hcl 11 | user_pool_add_ons { 12 | advanced_security_mode = "ENFORCED" 13 | advanced_security_additional_flows = var.advanced_security_additional_flows 14 | } 15 | ``` 16 | 17 | **AWS Provider 6.x** (current syntax): 18 | ```hcl 19 | user_pool_add_ons { 20 | advanced_security_mode = "ENFORCED" 21 | 22 | dynamic "advanced_security_additional_flows" { 23 | for_each = var.advanced_security_additional_flows != null ? [1] : [] 24 | content { 25 | custom_auth_mode = var.advanced_security_additional_flows 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | ### Migration Steps 32 | 33 | #### 1. Update Your Provider Version 34 | 35 | Update your Terraform configuration to require AWS provider 6.x: 36 | 37 | ```hcl 38 | terraform { 39 | required_providers { 40 | aws = { 41 | source = "hashicorp/aws" 42 | version = ">= 6.0" 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | #### 2. Update Your Module Version 49 | 50 | Update to the latest module version in your configuration: 51 | 52 | ```hcl 53 | module "cognito_user_pool" { 54 | source = "lgallard/cognito-user-pool/aws" 55 | version = "1.14.1" # or latest version 56 | 57 | # Your existing configuration remains the same 58 | user_pool_name = "my-pool" 59 | # ... other settings 60 | } 61 | ``` 62 | 63 | #### 3. Run Migration Commands 64 | 65 | Execute the following steps to migrate: 66 | 67 | ```bash 68 | # 1. Download the new provider version 69 | terraform init -upgrade 70 | 71 | # 2. Review the changes (should be minimal/none for most users) 72 | terraform plan 73 | 74 | # 3. Apply the changes 75 | terraform apply 76 | ``` 77 | 78 | ### What to Expect 79 | 80 | #### For Most Users 81 | - **No configuration changes required** - your module usage remains identical 82 | - **No resource changes** - existing user pools will not be modified 83 | - **Provider update only** - just need to upgrade AWS provider to 6.x 84 | 85 | #### For Advanced Security Users 86 | If you use `user_pool_add_ons_advanced_security_additional_flows`: 87 | 88 | **Before (still works the same way):** 89 | ```hcl 90 | module "cognito_user_pool" { 91 | source = "lgallard/cognito-user-pool/aws" 92 | 93 | user_pool_name = "my-pool" 94 | user_pool_add_ons_advanced_security_mode = "ENFORCED" 95 | user_pool_add_ons_advanced_security_additional_flows = "AUDIT" 96 | } 97 | ``` 98 | 99 | **After (identical configuration):** 100 | ```hcl 101 | module "cognito_user_pool" { 102 | source = "lgallard/cognito-user-pool/aws" 103 | 104 | user_pool_name = "my-pool" 105 | user_pool_add_ons_advanced_security_mode = "ENFORCED" 106 | user_pool_add_ons_advanced_security_additional_flows = "AUDIT" 107 | } 108 | ``` 109 | 110 | ### Troubleshooting 111 | 112 | #### Error: "Module requires newer AWS provider" 113 | ``` 114 | Error: Module requires aws provider version >= 6.0 115 | ``` 116 | 117 | **Solution**: Update your provider constraint and run `terraform init -upgrade`. 118 | 119 | #### Error: "Invalid argument - advanced_security_additional_flows" 120 | This indicates you're still using AWS provider 5.x. 121 | 122 | **Solution**: 123 | 1. Update provider version in your `versions.tf` or main configuration 124 | 2. Run `terraform init -upgrade` 125 | 3. Run `terraform plan` and `terraform apply` 126 | 127 | #### Error: "Provider configuration not available" 128 | **Solution**: Ensure your AWS provider is properly configured with valid credentials. 129 | 130 | ### Version Compatibility Matrix 131 | 132 | | Module Version | AWS Provider | Terraform | 133 | |----------------|-------------|-----------| 134 | | 1.14.1+ | >= 6.0 | >= 1.3.0 | 135 | | 1.14.0 | >= 5.98 | >= 1.3.0 | 136 | | < 1.14.0 | >= 5.0 | >= 1.0 | 137 | 138 | ### Need Help? 139 | 140 | If you encounter issues during migration: 141 | 142 | 1. **Review this guide** carefully 143 | 2. **Check provider versions** with `terraform version` 144 | 3. **Validate configuration** with `terraform validate` 145 | 4. **Open an issue** on [GitHub](https://github.com/lgallard/terraform-aws-cognito-user-pool/issues) with: 146 | - Your Terraform version 147 | - Your AWS provider version 148 | - Full error message 149 | - Minimal reproduction case 150 | 151 | ### AWS Provider 6.x Benefits 152 | 153 | Upgrading to AWS provider 6.x provides: 154 | 155 | - **Cleaner syntax** for Cognito advanced security 156 | - **Better validation** of configuration parameters 157 | - **Improved error messages** for troubleshooting 158 | - **Latest AWS features** and service improvements 159 | - **Bug fixes** and security updates 160 | -------------------------------------------------------------------------------- /.github/workflows/claude.yml: -------------------------------------------------------------------------------- 1 | name: Claude Code 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_review_comment: 7 | types: [created] 8 | issues: 9 | types: [opened, assigned] 10 | pull_request_review: 11 | types: [submitted] 12 | 13 | jobs: 14 | claude: 15 | if: | 16 | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 | (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 | (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 | (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | pull-requests: read 24 | issues: read 25 | id-token: write 26 | actions: read # Required for Claude to read CI results on PRs 27 | steps: 28 | - name: Checkout repository 29 | # Pin to specific SHA for supply chain security 30 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 31 | with: 32 | fetch-depth: 1 33 | 34 | - name: Run Claude Code 35 | id: claude 36 | # Pin to specific version for supply chain security 37 | uses: anthropics/claude-code-action@v1.0.0 38 | with: 39 | claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 40 | 41 | # This is an optional setting that allows Claude to read CI results on PRs 42 | additional_permissions: | 43 | actions: read 44 | 45 | # MCP Configuration for Terraform and Context7 documentation access 46 | # Dependencies pinned to specific versions for supply chain security 47 | mcp_config: | 48 | { 49 | "mcpServers": { 50 | "terraform": { 51 | "command": "npx", 52 | "args": [ 53 | "-y", 54 | "@modelcontextprotocol/server-terraform@0.6.0" 55 | ] 56 | }, 57 | "context7": { 58 | "command": "npx", 59 | "args": [ 60 | "-y", 61 | "@upstash/context7-mcp@1.0.0" 62 | ] 63 | } 64 | } 65 | } 66 | 67 | # Allow Bash permissions for pre-commit hooks and documentation updates + MCP tools 68 | allowed_tools: "Bash(pre-commit run --files),Bash(terraform fmt),Bash(terraform validate),Bash(terraform-docs),mcp__terraform-server__get_provider_details,mcp__terraform-server__search_providers,mcp__terraform-server__search_modules,mcp__terraform-server__get_module_details,mcp__context7__resolve-library-id,mcp__context7__get-library-docs" 69 | 70 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) 71 | # model: "claude-opus-4-20250514" 72 | 73 | # Optional: Customize the trigger phrase (default: @claude) 74 | # trigger_phrase: "/claude" 75 | 76 | # Optional: Trigger when specific user is assigned to an issue 77 | # assignee_trigger: "claude-bot" 78 | 79 | # Subagent routing for specialized expertise 80 | custom_instructions: | 81 | You have access to specialized subagents for this Terraform AWS Cognito User Pool module. Use them for relevant questions: 82 | 83 | **Subagent Routing Rules:** 84 | - For Cognito User Pool configurations, resource setup, MFA, security features, authentication flows, identity providers: use @terraform-cognito 85 | - For testing questions, Terratest, test development, validation, CI/CD testing: use @terraform-testing 86 | - For security analysis, compliance, vulnerability assessment, hardening recommendations: use @terraform-security 87 | - For migrations, upgrades, version transitions, breaking changes: use @cognito-migration 88 | - For documentation, examples, README updates, user guides: use @module-documentation 89 | 90 | **Auto-routing Keywords:** 91 | - MFA, authentication, password policy, advanced security → @terraform-security 92 | - test, testing, terratest, validation, CI/CD → @terraform-testing 93 | - migration, upgrade, version, breaking change → @cognito-migration 94 | - user pool, client, domain, identity provider, schema → @terraform-cognito 95 | - documentation, example, README, guide → @module-documentation 96 | 97 | Always leverage the specialized knowledge in these subagents for better, more accurate responses. 98 | 99 | # Optional: Custom environment variables for Claude 100 | # claude_env: | 101 | # NODE_ENV: test 102 | -------------------------------------------------------------------------------- /migrate-clients.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Client Migration Helper Script for terraform-aws-cognito-user-pool 4 | # This script helps generate terraform state mv commands for migrating from count to for_each 5 | 6 | set -e 7 | 8 | echo "=== User Pool Client Migration Helper ===" 9 | echo "This script helps migrate from count-based to for_each-based client management." 10 | echo "" 11 | 12 | # Check if terraform is available 13 | if ! command -v terraform &> /dev/null; then 14 | echo "Error: terraform command not found. Please install Terraform first." 15 | exit 1 16 | fi 17 | 18 | # Check if jq is available 19 | if ! command -v jq &> /dev/null; then 20 | echo "Warning: jq not found. Some features may not work properly." 21 | echo "Please install jq for full functionality: https://stedolan.github.io/jq/" 22 | fi 23 | 24 | echo "Step 1: Analyzing current Terraform state..." 25 | 26 | # Check if terraform state exists 27 | if ! terraform state list &> /dev/null; then 28 | echo "Error: No Terraform state found. Please run 'terraform init' and 'terraform apply' first." 29 | exit 1 30 | fi 31 | 32 | # Find existing client resources 33 | CLIENT_RESOURCES=$(terraform state list | grep "aws_cognito_user_pool_client.client\[" || true) 34 | 35 | if [ -z "$CLIENT_RESOURCES" ]; then 36 | echo "No existing client resources found in state." 37 | echo "This might mean:" 38 | echo " 1. You haven't created any clients yet" 39 | echo " 2. You're already using the for_each pattern" 40 | echo " 3. Your clients are managed by a different resource name" 41 | exit 0 42 | fi 43 | 44 | echo "Found existing client resources:" 45 | echo "$CLIENT_RESOURCES" 46 | echo "" 47 | 48 | echo "Step 2: Please provide your client configuration details..." 49 | 50 | # Array to store client info 51 | declare -a CLIENT_NAMES 52 | declare -a CLIENT_INDICES 53 | 54 | # Parse existing resources to get indices 55 | while IFS= read -r resource; do 56 | if [[ $resource =~ aws_cognito_user_pool_client\.client\[([0-9]+)\] ]]; then 57 | index="${BASH_REMATCH[1]}" 58 | CLIENT_INDICES+=("$index") 59 | 60 | echo -n "Enter the name for client[$index] (leave empty if no name is specified): " 61 | read -r client_name 62 | CLIENT_NAMES+=("$client_name") 63 | fi 64 | done <<< "$CLIENT_RESOURCES" 65 | 66 | echo "" 67 | echo "Step 3: Generating migration commands..." 68 | 69 | # Generate migration commands 70 | MIGRATION_COMMANDS=() 71 | 72 | for i in "${!CLIENT_INDICES[@]}"; do 73 | index="${CLIENT_INDICES[$i]}" 74 | name="${CLIENT_NAMES[$i]}" 75 | 76 | if [ -n "$name" ]; then 77 | # Client has a name 78 | new_key="${name}_${index}" 79 | else 80 | # Client has no name 81 | new_key="client_${index}" 82 | fi 83 | 84 | old_resource="aws_cognito_user_pool_client.client[${index}]" 85 | new_resource="aws_cognito_user_pool_client.client[\"${new_key}\"]" 86 | 87 | migration_cmd="terraform state mv '${old_resource}' '${new_resource}'" 88 | MIGRATION_COMMANDS+=("$migration_cmd") 89 | done 90 | 91 | echo "" 92 | echo "=== Generated Migration Commands ===" 93 | echo "" 94 | 95 | for cmd in "${MIGRATION_COMMANDS[@]}"; do 96 | echo "$cmd" 97 | done 98 | 99 | echo "" 100 | echo "=== Migration Instructions ===" 101 | echo "1. Review the commands above carefully" 102 | echo "2. Run each command in order:" 103 | echo "" 104 | 105 | for cmd in "${MIGRATION_COMMANDS[@]}"; do 106 | echo " $cmd" 107 | done 108 | 109 | echo "" 110 | echo "3. After running all commands, verify with:" 111 | echo " terraform plan" 112 | echo "" 113 | echo "4. The plan should show no changes for client resources" 114 | echo "" 115 | 116 | # Offer to run commands automatically 117 | echo -n "Would you like to run these commands automatically? (y/N): " 118 | read -r AUTO_RUN 119 | 120 | if [[ $AUTO_RUN =~ ^[Yy]$ ]]; then 121 | echo "" 122 | echo "Running migration commands..." 123 | 124 | for cmd in "${MIGRATION_COMMANDS[@]}"; do 125 | echo "Executing: $cmd" 126 | if eval "$cmd"; then 127 | echo "✓ Success" 128 | else 129 | echo "✗ Failed" 130 | echo "Please run the remaining commands manually." 131 | exit 1 132 | fi 133 | done 134 | 135 | echo "" 136 | echo "✓ All migration commands completed successfully!" 137 | echo "" 138 | echo "Running terraform plan to verify..." 139 | terraform plan 140 | 141 | echo "" 142 | echo "Migration complete! Check the plan output above." 143 | echo "If you see any client resources being created/destroyed, please review your configuration." 144 | 145 | else 146 | echo "" 147 | echo "Migration commands have been generated above." 148 | echo "Please run them manually and then verify with 'terraform plan'." 149 | fi 150 | 151 | echo "" 152 | echo "For more information, see: MIGRATION_GUIDE.md" 153 | echo "If you encounter issues, please create an issue at:" 154 | echo "https://github.com/lgallard/terraform-aws-cognito-user-pool/issues" 155 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-commit 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | paths: 7 | - '**.tf' 8 | - '**.tfvars' 9 | - '**.md' 10 | - '.pre-commit-config.yaml' 11 | push: 12 | branches: [master] 13 | paths: 14 | - '**.tf' 15 | - '**.tfvars' 16 | - '**.md' 17 | - '.pre-commit-config.yaml' 18 | 19 | jobs: 20 | pre-commit: 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 25 23 | permissions: 24 | contents: read 25 | pull-requests: read 26 | 27 | steps: 28 | - name: Checkout repository 29 | # Pin to specific SHA for supply chain security 30 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Set up Python 35 | # Pin to specific SHA for supply chain security 36 | uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 37 | with: 38 | python-version: '3.13' 39 | 40 | - name: Set up Terraform 41 | # Pin to specific SHA for supply chain security 42 | uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 43 | with: 44 | terraform_version: '1.3.0' 45 | 46 | - name: Install pre-commit 47 | run: | 48 | python -m pip install --upgrade pip 49 | pip install pre-commit 50 | 51 | - name: Install Trivy 52 | run: | 53 | if ! command -v trivy &> /dev/null; then 54 | echo "Installing Trivy..." 55 | sudo apt-get update 56 | sudo apt-get install -y wget apt-transport-https gnupg lsb-release 57 | sudo mkdir -p /etc/apt/keyrings 58 | wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /etc/apt/keyrings/trivy.gpg 59 | echo "deb [signed-by=/etc/apt/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list 60 | sudo apt-get update 61 | sudo apt-get install -y trivy 62 | fi 63 | 64 | - name: Cache pre-commit hooks 65 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 66 | with: 67 | path: ~/.cache/pre-commit 68 | # Include run number to force periodic cache refresh and avoid stale tool databases 69 | # This resolves typos false positives from cached outdated tools 70 | key: pre-commit-${{ runner.os }}-v3-${{ hashFiles('.pre-commit-config.yaml') }}-${{ github.run_number }} 71 | restore-keys: | 72 | pre-commit-${{ runner.os }}-v3-${{ hashFiles('.pre-commit-config.yaml') }}- 73 | pre-commit-${{ runner.os }}-v3- 74 | 75 | - name: Install pre-commit hooks 76 | run: pre-commit install-hooks 77 | 78 | - name: Run pre-commit on all files (push to master) 79 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 80 | run: pre-commit run --all-files 81 | 82 | - name: Run pre-commit on changed files (pull request) 83 | if: github.event_name == 'pull_request' 84 | run: | 85 | # Get the list of changed files 86 | git fetch origin ${{ github.base_ref }} 87 | CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.tf' '*.tfvars' '*.md') 88 | 89 | if [ -n "$CHANGED_FILES" ]; then 90 | echo "Running pre-commit on changed files:" 91 | echo "$CHANGED_FILES" 92 | pre-commit run --files $CHANGED_FILES 93 | else 94 | echo "No relevant files changed, skipping pre-commit checks" 95 | fi 96 | 97 | - name: Pre-commit summary 98 | if: always() 99 | run: | 100 | echo "## 🔍 Pre-commit Results" >> $GITHUB_STEP_SUMMARY 101 | echo "" >> $GITHUB_STEP_SUMMARY 102 | 103 | if [ "${{ job.status }}" == "success" ]; then 104 | echo "✅ All pre-commit checks passed!" >> $GITHUB_STEP_SUMMARY 105 | echo "" >> $GITHUB_STEP_SUMMARY 106 | echo "**Tools verified:**" >> $GITHUB_STEP_SUMMARY 107 | echo "- 🔧 Terraform formatting" >> $GITHUB_STEP_SUMMARY 108 | echo "- ✅ Terraform validation" >> $GITHUB_STEP_SUMMARY 109 | echo "- 🧹 File formatting" >> $GITHUB_STEP_SUMMARY 110 | echo "- ✍️ Typo checking" >> $GITHUB_STEP_SUMMARY 111 | else 112 | echo "❌ Pre-commit checks failed" >> $GITHUB_STEP_SUMMARY 113 | echo "" >> $GITHUB_STEP_SUMMARY 114 | echo "Please check the logs above for specific failures." >> $GITHUB_STEP_SUMMARY 115 | echo "You can run \`pre-commit run --all-files\` locally to fix issues." >> $GITHUB_STEP_SUMMARY 116 | fi 117 | 118 | echo "" >> $GITHUB_STEP_SUMMARY 119 | echo "**Configured hooks:**" >> $GITHUB_STEP_SUMMARY 120 | echo "- trailing-whitespace" >> $GITHUB_STEP_SUMMARY 121 | echo "- end-of-file-fixer" >> $GITHUB_STEP_SUMMARY 122 | echo "- check-yaml" >> $GITHUB_STEP_SUMMARY 123 | echo "- terraform_fmt" >> $GITHUB_STEP_SUMMARY 124 | echo "- terraform_validate" >> $GITHUB_STEP_SUMMARY 125 | echo "- typos" >> $GITHUB_STEP_SUMMARY 126 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-cognito-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 New Cognito User Pool Feature 3 | about: Auto-discovered new AWS Cognito User Pool feature for implementation 4 | title: "feat: Add support for [FEATURE_NAME]" 5 | labels: ["enhancement", "aws-provider-update", "auto-discovered"] 6 | assignees: [] 7 | --- 8 | 9 | ## 🚀 New Cognito User Pool Feature Discovery 10 | 11 | **AWS Provider Version:** v[PROVIDER_VERSION] 12 | **Feature Type:** [Resource/Argument/Data Source] 13 | **Priority:** [P0-Critical/P1-High/P2-Medium/P3-Low] 14 | **Auto-detected:** ✅ `[SCAN_DATE]` 15 | 16 | ### Description 17 | 18 | [FEATURE_DESCRIPTION] 19 | 20 | ### Provider Documentation 21 | - **Provider Docs:** [PROVIDER_DOCS_LINK] 22 | - **AWS Service Docs:** [AWS_DOCS_LINK] 23 | - **Terraform Registry:** [REGISTRY_LINK] 24 | 25 | ### Implementation Requirements 26 | 27 | #### Code Changes 28 | - [ ] Add to `main.tf` or relevant module file 29 | - [ ] Add input variables to `variables.tf` 30 | - [ ] Add outputs to `outputs.tf` (if applicable) 31 | - [ ] Update locals files (if needed) 32 | - [ ] Handle conditional resource creation 33 | - [ ] Update client configurations in `client.tf` (if applicable) 34 | - [ ] Update domain configurations in `domain.tf` (if applicable) 35 | - [ ] Update identity provider configurations in `identity-provider.tf` (if applicable) 36 | 37 | #### Examples & Documentation 38 | - [ ] Create example in `examples/[feature-name]/` 39 | - [ ] Add to `examples/complete/` comprehensive example 40 | - [ ] Update main `README.md` 41 | - [ ] Add to `CHANGELOG.md` (will be automated by release-please) 42 | 43 | #### AI Validation & Quality Checks 44 | - [ ] Request validation from `terraform-cognito` agent for implementation review 45 | - [ ] Request validation from `terraform-security` agent for security analysis 46 | - [ ] Request validation from `module-documentation` agent for examples 47 | - [ ] Validate with `examples/complete` scenario 48 | - [ ] Run `terraform fmt`, `terraform validate` 49 | - [ ] Run `pre-commit run --all-files` 50 | 51 | #### Quality Assurance 52 | - [ ] Follow existing code patterns and conventions 53 | - [ ] Add proper variable validation rules 54 | - [ ] Include appropriate default values 55 | - [ ] Add comprehensive variable descriptions 56 | - [ ] Ensure backward compatibility 57 | 58 | ### Example Configuration 59 | ```hcl 60 | # Auto-generated example from provider documentation 61 | module "cognito_user_pool" { 62 | source = "./terraform-aws-cognito-user-pool" 63 | 64 | user_pool_name = "my-user-pool" 65 | 66 | # New feature implementation 67 | [FEATURE_EXAMPLE] 68 | 69 | tags = { 70 | Environment = "production" 71 | Feature = "[FEATURE_NAME]" 72 | } 73 | } 74 | ``` 75 | 76 | ### Expected Outputs 77 | ```hcl 78 | # If this feature provides new outputs 79 | output "[feature_output]" { 80 | description = "[Output description]" 81 | value = [output_reference] 82 | } 83 | ``` 84 | 85 | ### Validation Commands 86 | ```bash 87 | # Validate the specific example 88 | cd examples/[feature-name] 89 | terraform init 90 | terraform validate 91 | terraform plan 92 | 93 | # Request AI validation 94 | # Use specialized agents for comprehensive validation: 95 | # @claude Use terraform-cognito and terraform-security agents to validate 96 | # the [feature-name] implementation. Check for AWS best practices, 97 | # security concerns, and proper integration with existing module patterns. 98 | ``` 99 | 100 | ### Implementation Notes 101 | 102 | - [ ] **Backward Compatibility**: Ensure changes don't break existing configurations 103 | - [ ] **Default Values**: Use sensible defaults that maintain current behavior 104 | - [ ] **Validation**: Add appropriate variable validation where needed 105 | - [ ] **Dependencies**: Check for new required provider features or versions 106 | - [ ] **Authentication Flows**: Consider impact on existing auth flows 107 | - [ ] **Security**: Evaluate security implications and best practices 108 | 109 | ### Cognito-Specific Considerations 110 | - [ ] **User Pool Impact**: How does this affect user pool configuration? 111 | - [ ] **Client Impact**: Does this require changes to user pool clients? 112 | - [ ] **Authentication**: Impact on authentication and authorization flows 113 | - [ ] **MFA Integration**: Compatibility with MFA configurations 114 | - [ ] **Identity Providers**: Integration with federated identity providers 115 | - [ ] **User Experience**: Impact on login/signup user experience 116 | - [ ] **Security Features**: New security capabilities or requirements 117 | 118 | ### Acceptance Criteria 119 | - [ ] Feature implemented following module patterns 120 | - [ ] AI validation completed with specialized agents 121 | - [ ] Examples work as documented 122 | - [ ] Pre-commit hooks pass 123 | - [ ] Documentation complete and accurate 124 | - [ ] No breaking changes to existing functionality 125 | - [ ] Feature works with all existing examples 126 | - [ ] Cognito-specific patterns maintained 127 | 128 | ### Provider Compatibility 129 | **Minimum AWS Provider Version:** `>= [MIN_VERSION]` 130 | **Terraform Version:** `>= 1.0` (current module requirement) 131 | 132 | --- 133 | 134 | ### 🤖 Automation Details 135 | **Discovery Workflow:** `feature-discovery.yml` 136 | **Scan ID:** `[SCAN_ID]` 137 | **Detection Method:** Terraform MCP Server analysis 138 | **Last Updated:** `[TIMESTAMP]` 139 | 140 | --- 141 | 142 | *This issue was automatically created by the Cognito User Pool Feature Discovery workflow. Please review the auto-generated content and update as needed before implementation.* 143 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | description = "The id of the user pool" 3 | value = local.user_pool_id 4 | } 5 | 6 | output "arn" { 7 | description = "The ARN of the user pool" 8 | value = var.enabled ? local.user_pool.arn : null 9 | } 10 | 11 | output "endpoint" { 12 | description = "The endpoint name of the user pool. Example format: cognito-idp.REGION.amazonaws.com/xxxx_yyyyy" 13 | value = var.enabled ? local.user_pool.endpoint : null 14 | } 15 | 16 | output "creation_date" { 17 | description = "The date the user pool was created" 18 | value = var.enabled ? local.user_pool.creation_date : null 19 | } 20 | 21 | output "last_modified_date" { 22 | description = "The date the user pool was last modified" 23 | value = var.enabled ? local.user_pool.last_modified_date : null 24 | } 25 | 26 | output "name" { 27 | description = "The name of the user pool" 28 | value = var.enabled ? local.user_pool.name : null 29 | } 30 | 31 | # 32 | # aws_cognito_user_pool_domain 33 | # 34 | output "domain_aws_account_id" { 35 | description = "The AWS account ID for the user pool owner" 36 | value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.aws_account_id) : null 37 | } 38 | 39 | output "domain_cloudfront_distribution" { 40 | description = "The name of the CloudFront distribution" 41 | value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.cloudfront_distribution) : null 42 | } 43 | 44 | output "domain_cloudfront_distribution_arn" { 45 | description = "The ARN of the CloudFront distribution" 46 | value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.cloudfront_distribution_arn) : null 47 | } 48 | 49 | output "domain_cloudfront_distribution_zone_id" { 50 | description = "The ZoneID of the CloudFront distribution" 51 | value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.cloudfront_distribution_zone_id) : null 52 | } 53 | 54 | output "domain_s3_bucket" { 55 | description = "The S3 bucket where the static files for this domain are stored" 56 | value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.s3_bucket) : null 57 | } 58 | 59 | output "domain_app_version" { 60 | description = "The app version" 61 | value = var.enabled ? join("", aws_cognito_user_pool_domain.domain.*.version) : null 62 | } 63 | 64 | # 65 | # aws_cognito_user_pool_client 66 | # 67 | output "client_ids" { 68 | description = "The ids of the user pool clients" 69 | value = var.enabled ? values(aws_cognito_user_pool_client.client)[*].id : null 70 | } 71 | 72 | output "client_secrets" { 73 | description = "The client secrets of the user pool clients" 74 | value = var.enabled ? values(aws_cognito_user_pool_client.client)[*].client_secret : null 75 | sensitive = true 76 | } 77 | 78 | output "client_ids_map" { 79 | description = "The ids map of the user pool clients" 80 | value = var.enabled ? { for k, v in aws_cognito_user_pool_client.client : coalesce(v.name, k) => v.id } : null 81 | } 82 | 83 | output "client_secrets_map" { 84 | description = "The client secrets map of the user pool clients" 85 | value = var.enabled ? { for k, v in aws_cognito_user_pool_client.client : coalesce(v.name, k) => v.client_secret } : null 86 | sensitive = true 87 | } 88 | 89 | # 90 | # aws_cognito_user_group 91 | # 92 | output "user_group_ids" { 93 | description = "The ids of the user groups" 94 | value = var.enabled ? values(aws_cognito_user_group.main)[*].id : null 95 | } 96 | 97 | output "user_group_names" { 98 | description = "The names of the user groups" 99 | value = var.enabled ? values(aws_cognito_user_group.main)[*].name : null 100 | } 101 | 102 | output "user_group_arns" { 103 | description = "The ARNs of the user groups" 104 | value = var.enabled ? [ 105 | for group in values(aws_cognito_user_group.main) : "${local.user_pool.arn}/group/${group.name}" 106 | ] : null 107 | } 108 | 109 | output "user_groups_map" { 110 | description = "A map of user group names to their properties" 111 | value = var.enabled ? { 112 | for k, v in aws_cognito_user_group.main : k => { 113 | id = v.id 114 | name = v.name 115 | description = v.description 116 | precedence = v.precedence 117 | role_arn = v.role_arn 118 | arn = "${local.user_pool.arn}/group/${v.name}" 119 | } 120 | } : null 121 | } 122 | 123 | # 124 | # aws_cognito_resource_servers 125 | # 126 | output "resource_servers_scope_identifiers" { 127 | description = "A list of all scopes configured in the format identifier/scope_name" 128 | value = var.enabled ? aws_cognito_resource_server.resource.*.scope_identifiers : null 129 | } 130 | 131 | # 132 | # awscc_cognito_managed_login_branding 133 | # 134 | output "managed_login_branding_details" { 135 | description = "Complete managed login branding details" 136 | value = var.enabled && var.managed_login_branding_enabled ? { 137 | configurations = { 138 | for k, v in awscc_cognito_managed_login_branding.branding : k => { 139 | id = v.id 140 | managed_login_branding_id = v.managed_login_branding_id 141 | client_id = v.client_id 142 | user_pool_id = v.user_pool_id 143 | assets = v.assets 144 | settings = v.settings 145 | } 146 | } 147 | ids = { 148 | for k, v in awscc_cognito_managed_login_branding.branding : k => v.managed_login_branding_id 149 | } 150 | } : { 151 | configurations = {} 152 | ids = {} 153 | } 154 | } 155 | 156 | # Backward compatibility - keep existing outputs 157 | output "managed_login_branding" { 158 | description = "Map of managed login branding configurations (deprecated - use managed_login_branding_details)" 159 | 160 | value = local.managed_login_branding_map 161 | } 162 | 163 | output "managed_login_branding_ids" { 164 | description = "Map of managed login branding IDs (deprecated - use managed_login_branding_details.ids)" 165 | 166 | value = var.enabled && var.managed_login_branding_enabled ? { 167 | for k, v in awscc_cognito_managed_login_branding.branding : k => v.managed_login_branding_id 168 | } : {} 169 | } 170 | -------------------------------------------------------------------------------- /.github/feature-tracker/cognito-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "module_name": "terraform-aws-cognito-user-pool", 4 | "last_scan": "1970-01-01T00:00:00Z", 5 | "provider_version": "0.0.0", 6 | "scan_count": 0 7 | }, 8 | "scan_history": [ 9 | { 10 | "scan_date": "1970-01-01T00:00:00Z", 11 | "provider_version": "0.0.0", 12 | "features_found": 0, 13 | "deprecations_found": 0, 14 | "fixes_found": 0, 15 | "issues_created": 0 16 | } 17 | ], 18 | "current_implementation": { 19 | "resources": { 20 | "aws_cognito_user_pool": { 21 | "implemented": [ 22 | "alias_attributes", 23 | "auto_verified_attributes", 24 | "name", 25 | "email_verification_subject", 26 | "email_verification_message", 27 | "mfa_configuration", 28 | "sms_authentication_message", 29 | "sms_verification_message", 30 | "username_attributes", 31 | "deletion_protection", 32 | "user_pool_tier", 33 | "username_configuration", 34 | "admin_create_user_config", 35 | "device_configuration", 36 | "email_configuration", 37 | "lambda_config", 38 | "password_policy", 39 | "schema", 40 | "sms_configuration", 41 | "software_token_mfa_configuration", 42 | "tags", 43 | "user_attribute_update_settings", 44 | "user_pool_add_ons", 45 | "verification_message_template" 46 | ], 47 | "pending": [], 48 | "deprecated": [] 49 | }, 50 | "aws_cognito_user_pool_client": { 51 | "implemented": [ 52 | "user_pool_id", 53 | "name", 54 | "access_token_validity", 55 | "allowed_oauth_flows", 56 | "allowed_oauth_flows_user_pool_client", 57 | "allowed_oauth_scopes", 58 | "callback_urls", 59 | "default_redirect_uri", 60 | "explicit_auth_flows", 61 | "generate_secret", 62 | "id_token_validity", 63 | "logout_urls", 64 | "prevent_user_existence_errors", 65 | "read_attributes", 66 | "refresh_token_validity", 67 | "supported_identity_providers", 68 | "token_validity_units", 69 | "write_attributes", 70 | "analytics_configuration", 71 | "auth_session_validity" 72 | ], 73 | "pending": [], 74 | "deprecated": [] 75 | }, 76 | "aws_cognito_user_pool_domain": { 77 | "implemented": [ 78 | "domain", 79 | "user_pool_id", 80 | "certificate_arn" 81 | ], 82 | "pending": [], 83 | "deprecated": [] 84 | }, 85 | "aws_cognito_identity_provider": { 86 | "implemented": [ 87 | "user_pool_id", 88 | "provider_name", 89 | "provider_type", 90 | "provider_details", 91 | "attribute_mapping", 92 | "idp_identifiers" 93 | ], 94 | "pending": [], 95 | "deprecated": [] 96 | }, 97 | "aws_cognito_resource_server": { 98 | "implemented": [ 99 | "identifier", 100 | "name", 101 | "user_pool_id", 102 | "scope" 103 | ], 104 | "pending": [], 105 | "deprecated": [] 106 | }, 107 | "aws_cognito_user_group": { 108 | "implemented": [ 109 | "name", 110 | "user_pool_id", 111 | "description", 112 | "precedence", 113 | "role_arn" 114 | ], 115 | "pending": [], 116 | "deprecated": [] 117 | }, 118 | "aws_cognito_managed_login_branding": { 119 | "implemented": [ 120 | "user_pool_id", 121 | "use_cognito_provided_values", 122 | "assets" 123 | ], 124 | "pending": [], 125 | "deprecated": [] 126 | }, 127 | "aws_cognito_user_pool_ui_customization": { 128 | "implemented": [ 129 | "user_pool_id", 130 | "client_id", 131 | "css", 132 | "image_file" 133 | ], 134 | "pending": [], 135 | "deprecated": [] 136 | } 137 | }, 138 | "data_sources": { 139 | "aws_cognito_user_pool": { 140 | "implemented": [ 141 | "user_pool_id", 142 | "name" 143 | ], 144 | "pending": [], 145 | "deprecated": [] 146 | }, 147 | "aws_cognito_user_pool_client": { 148 | "implemented": [ 149 | "client_id", 150 | "user_pool_id" 151 | ], 152 | "pending": [], 153 | "deprecated": [] 154 | }, 155 | "aws_cognito_user_pool_clients": { 156 | "implemented": [ 157 | "user_pool_id" 158 | ], 159 | "pending": [], 160 | "deprecated": [] 161 | } 162 | }, 163 | "examples": { 164 | "simple": { 165 | "resources": [ 166 | "aws_cognito_user_pool", 167 | "aws_cognito_user_pool_client" 168 | ] 169 | }, 170 | "simple_extended": { 171 | "resources": [ 172 | "aws_cognito_user_pool", 173 | "aws_cognito_user_pool_client" 174 | ] 175 | }, 176 | "complete": { 177 | "resources": [ 178 | "aws_cognito_user_pool", 179 | "aws_cognito_user_pool_client", 180 | "aws_cognito_user_pool_domain", 181 | "aws_cognito_identity_provider", 182 | "aws_cognito_user_group", 183 | "aws_cognito_user_pool_ui_customization" 184 | ] 185 | }, 186 | "email_mfa": { 187 | "resources": [ 188 | "aws_cognito_user_pool", 189 | "aws_cognito_user_pool_client" 190 | ] 191 | }, 192 | "with_branding": { 193 | "resources": [ 194 | "aws_cognito_user_pool", 195 | "aws_cognito_user_pool_client", 196 | "aws_cognito_managed_login_branding" 197 | ] 198 | } 199 | } 200 | }, 201 | "discovered_features": { 202 | "new_resources": {}, 203 | "new_arguments": {}, 204 | "new_data_sources": {}, 205 | "deprecated_items": {}, 206 | "bug_fixes": {} 207 | }, 208 | "issues_created": [], 209 | "statistics": { 210 | "total_scans": 0, 211 | "total_features_discovered": 0, 212 | "total_issues_created": 0, 213 | "average_features_per_scan": 0, 214 | "last_feature_discovery": "never" 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /examples/with_branding/README.md: -------------------------------------------------------------------------------- 1 | # Cognito User Pool with Managed Login Branding Example 2 | 3 | This example demonstrates how to create an AWS Cognito User Pool with managed login branding using the `awscc` provider. 4 | 5 | ## Features 6 | 7 | - Cognito User Pool with hosted UI 8 | - Managed login branding with custom assets 9 | - Support for light/dark mode logos 10 | - Custom background images and favicon 11 | - JSON-based settings for colors and typography 12 | - User pool client with OAuth flows 13 | 14 | ## Prerequisites 15 | 16 | 1. **AWS Account** with appropriate permissions 17 | 2. **Terraform** >= 1.3.0 18 | 3. **AWSCC Provider** configured (for managed login branding) 19 | 4. **Branding Assets** (logos, backgrounds, favicon) 20 | 21 | ## Provider Requirements 22 | 23 | This example requires both the `aws` and `awscc` providers: 24 | 25 | ```hcl 26 | terraform { 27 | required_providers { 28 | aws = { 29 | source = "hashicorp/aws" 30 | version = ">= 6.0" 31 | } 32 | awscc = { 33 | source = "hashicorp/awscc" 34 | version = ">= 1.0" 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Usage 41 | 42 | 1. **Prepare Assets**: Add your branding assets to the `assets/` directory: 43 | - `logo-light.png` - Logo for light mode 44 | - `logo-dark.png` - Logo for dark mode 45 | - `background.jpg` - Background image 46 | - `favicon.ico` - Favicon 47 | 48 | 2. **Configure Variables**: Update `terraform.tfvars` with your settings: 49 | ```hcl 50 | user_pool_name = "my-app-user-pool" 51 | domain_name = "my-app-auth" # Optional custom domain 52 | aws_region = "us-east-1" 53 | ``` 54 | 55 | 3. **Deploy**: 56 | ```bash 57 | terraform init 58 | terraform plan 59 | terraform apply 60 | ``` 61 | 62 | ## Branding Configuration 63 | 64 | The example includes comprehensive branding configuration: 65 | 66 | ### Assets 67 | - **Form Logo**: Different logos for light/dark modes 68 | - **Background**: Page background image 69 | - **Favicon**: Browser tab icon 70 | 71 | ### Settings 72 | - **Color Schemes**: Custom colors for light/dark themes 73 | - **Typography**: Font family and sizing 74 | - **Layout**: Border radius and spacing 75 | 76 | ### Asset Categories 77 | 78 | Cognito supports various asset categories: 79 | 80 | | Category | Description | Color Modes | 81 | |----------|-------------|-------------| 82 | | `FORM_LOGO` | Logo on login form | LIGHT, DARK, DYNAMIC | 83 | | `PAGE_BACKGROUND` | Page background | LIGHT, DARK, DYNAMIC | 84 | | `FAVICON_ICO` | Browser favicon | DYNAMIC | 85 | | `PAGE_HEADER_LOGO` | Header logo | LIGHT, DARK, DYNAMIC | 86 | | `PAGE_FOOTER_LOGO` | Footer logo | LIGHT, DARK, DYNAMIC | 87 | 88 | ## Outputs 89 | 90 | The example provides several useful outputs: 91 | 92 | - `user_pool_id` - The Cognito User Pool ID 93 | - `hosted_ui_url` - The hosted UI login URL 94 | - `managed_login_branding` - Complete branding configuration 95 | - `managed_login_branding_ids` - Branding instance IDs 96 | 97 | ## Testing the Branding 98 | 99 | After deployment, you can test the branding by: 100 | 101 | 1. Visit the hosted UI URL (from outputs) 102 | 2. Check the login page for your custom branding 103 | 3. Test both light and dark mode (browser settings) 104 | 4. Verify all assets load correctly 105 | 106 | ## Asset Requirements 107 | 108 | - **File Size**: Maximum 2MB per asset 109 | - **Formats**: PNG, JPG, JPEG, SVG, ICO 110 | - **Encoding**: Base64 (handled automatically by `filebase64()`) 111 | 112 | ## Important Notes 113 | 114 | - **Provider Requirement**: This feature requires the `awscc` provider 115 | - **Regional Support**: Managed login branding is available in most AWS regions 116 | - **Client Association**: Each branding configuration is linked to a specific app client 117 | - **Cost**: Managed login branding may have associated costs 118 | 119 | ## Cleanup 120 | 121 | To destroy the resources: 122 | 123 | ```bash 124 | terraform destroy 125 | ``` 126 | 127 | ## References 128 | 129 | - [AWS Cognito Managed Login Branding](https://docs.aws.amazon.com/cognito/latest/developerguide/managed-login-brandingeditor.html) 130 | - [AWSCC Provider Documentation](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/cognito_managed_login_branding) 131 | 132 | 133 | 134 | 135 | ## Requirements 136 | 137 | | Name | Version | 138 | |------|---------| 139 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 140 | | [aws](#requirement\_aws) | >= 6.0 | 141 | | [awscc](#requirement\_awscc) | >= 1.0 | 142 | 143 | ## Providers 144 | 145 | No providers. 146 | 147 | ## Modules 148 | 149 | | Name | Source | Version | 150 | |------|--------|---------| 151 | | [aws\_cognito\_user\_pool](#module\_aws\_cognito\_user\_pool) | ../../ | n/a | 152 | 153 | ## Resources 154 | 155 | No resources. 156 | 157 | ## Inputs 158 | 159 | | Name | Description | Type | Default | Required | 160 | |------|-------------|------|---------|:--------:| 161 | | [aws\_region](#input\_aws\_region) | AWS region | `string` | `"us-east-1"` | no | 162 | | [domain\_name](#input\_domain\_name) | The domain name for the hosted UI | `string` | `""` | no | 163 | | [enable\_branding](#input\_enable\_branding) | Whether to enable managed login branding | `bool` | `true` | no | 164 | | [tags](#input\_tags) | A map of tags to assign to the resource | `map(string)` |
{
"Environment": "development",
"Project": "cognito-branding-example"
}
| no | 165 | | [user\_pool\_name](#input\_user\_pool\_name) | The name of the user pool | `string` | `"example-pool-with-branding"` | no | 166 | | [user\_pool\_tier](#input\_user\_pool\_tier) | The tier of the user pool | `string` | `"ESSENTIALS"` | no | 167 | 168 | ## Outputs 169 | 170 | | Name | Description | 171 | |------|-------------| 172 | | [client\_ids\_map](#output\_client\_ids\_map) | Map of user pool client IDs | 173 | | [hosted\_ui\_url](#output\_hosted\_ui\_url) | The hosted UI URL | 174 | | [managed\_login\_branding\_details](#output\_managed\_login\_branding\_details) | The managed login branding details from the module | 175 | | [user\_pool\_arn](#output\_user\_pool\_arn) | The ARN of the user pool | 176 | | [user\_pool\_domain](#output\_user\_pool\_domain) | The domain of the user pool | 177 | | [user\_pool\_id](#output\_user\_pool\_id) | The ID of the user pool | 178 | 179 | -------------------------------------------------------------------------------- /client.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cognito_user_pool_client" "client" { 2 | for_each = var.enabled ? { 3 | for idx, client in local.clients : 4 | "${lookup(client, "name", "client")}_${idx}" => client 5 | } : {} 6 | 7 | allowed_oauth_flows = try(each.value.allowed_oauth_flows, null) 8 | allowed_oauth_flows_user_pool_client = try(each.value.allowed_oauth_flows_user_pool_client, null) 9 | allowed_oauth_scopes = try(each.value.allowed_oauth_scopes, null) 10 | auth_session_validity = try(each.value.auth_session_validity, null) 11 | callback_urls = try(each.value.callback_urls, null) 12 | default_redirect_uri = try(each.value.default_redirect_uri, null) 13 | explicit_auth_flows = try(each.value.explicit_auth_flows, null) 14 | generate_secret = try(each.value.generate_secret, null) 15 | logout_urls = try(each.value.logout_urls, null) 16 | name = try(each.value.name, null) 17 | read_attributes = try(each.value.read_attributes, null) 18 | access_token_validity = try(each.value.access_token_validity, null) 19 | id_token_validity = try(each.value.id_token_validity, null) 20 | refresh_token_validity = try(each.value.refresh_token_validity, null) 21 | supported_identity_providers = try(each.value.supported_identity_providers, null) 22 | enable_propagate_additional_user_context_data = try(each.value.enable_propagate_additional_user_context_data, null) 23 | prevent_user_existence_errors = try( 24 | each.value.prevent_user_existence_errors, 25 | try(each.value.client_prevent_user_existence_errors, null) 26 | ) 27 | write_attributes = try(each.value.write_attributes, null) 28 | enable_token_revocation = try(each.value.enable_token_revocation, null) 29 | user_pool_id = local.user_pool_id 30 | 31 | # token_validity_units 32 | dynamic "token_validity_units" { 33 | for_each = can(each.value.token_validity_units) && length(keys(each.value.token_validity_units)) > 0 ? [each.value.token_validity_units] : [] 34 | content { 35 | access_token = try(token_validity_units.value.access_token, null) 36 | id_token = try(token_validity_units.value.id_token, null) 37 | refresh_token = try(token_validity_units.value.refresh_token, null) 38 | } 39 | } 40 | 41 | # refresh_token_rotation 42 | dynamic "refresh_token_rotation" { 43 | for_each = can(each.value.refresh_token_rotation) && length(keys(each.value.refresh_token_rotation)) > 0 ? [each.value.refresh_token_rotation] : [] 44 | content { 45 | feature = try(refresh_token_rotation.value.feature, null) 46 | retry_grace_period_seconds = try(refresh_token_rotation.value.retry_grace_period_seconds, null) 47 | } 48 | } 49 | 50 | depends_on = [ 51 | aws_cognito_resource_server.resource, 52 | aws_cognito_identity_provider.identity_provider 53 | ] 54 | } 55 | 56 | locals { 57 | clients_default = [ 58 | { 59 | allowed_oauth_flows = var.client_allowed_oauth_flows 60 | allowed_oauth_flows_user_pool_client = var.client_allowed_oauth_flows_user_pool_client 61 | allowed_oauth_scopes = var.client_allowed_oauth_scopes 62 | auth_session_validity = var.client_auth_session_validity 63 | callback_urls = var.client_callback_urls 64 | default_redirect_uri = var.client_default_redirect_uri 65 | explicit_auth_flows = var.client_explicit_auth_flows 66 | generate_secret = var.client_generate_secret 67 | logout_urls = var.client_logout_urls 68 | name = var.client_name 69 | read_attributes = var.client_read_attributes 70 | access_token_validity = var.client_access_token_validity 71 | id_token_validity = var.client_id_token_validity 72 | token_validity_units = var.client_token_validity_units 73 | refresh_token_validity = var.client_refresh_token_validity 74 | enable_propagate_additional_user_context_data = var.enable_propagate_additional_user_context_data 75 | supported_identity_providers = var.client_supported_identity_providers 76 | prevent_user_existence_errors = var.client_prevent_user_existence_errors 77 | write_attributes = var.client_write_attributes 78 | enable_token_revocation = var.client_enable_token_revocation 79 | refresh_token_rotation = var.client_refresh_token_rotation 80 | } 81 | ] 82 | 83 | # This parses vars.clients which is a list of objects (map), and transforms it to a tuple of elements to avoid conflict with the ternary and local.clients_default 84 | clients_parsed = [for e in var.clients : { 85 | allowed_oauth_flows = try(e.allowed_oauth_flows, null) 86 | allowed_oauth_flows_user_pool_client = try(e.allowed_oauth_flows_user_pool_client, null) 87 | allowed_oauth_scopes = try(e.allowed_oauth_scopes, null) 88 | auth_session_validity = try(e.auth_session_validity, null) 89 | callback_urls = try(e.callback_urls, null) 90 | default_redirect_uri = try(e.default_redirect_uri, null) 91 | explicit_auth_flows = try(e.explicit_auth_flows, null) 92 | generate_secret = try(e.generate_secret, null) 93 | logout_urls = try(e.logout_urls, null) 94 | name = try(e.name, null) 95 | read_attributes = try(e.read_attributes, null) 96 | access_token_validity = try(e.access_token_validity, null) 97 | id_token_validity = try(e.id_token_validity, null) 98 | refresh_token_validity = try(e.refresh_token_validity, null) 99 | enable_propagate_additional_user_context_data = try(e.enable_propagate_additional_user_context_data, null) 100 | token_validity_units = try(e.token_validity_units, {}) 101 | supported_identity_providers = try(e.supported_identity_providers, null) 102 | prevent_user_existence_errors = try( 103 | e.prevent_user_existence_errors, 104 | try(e.client_prevent_user_existence_errors, null) 105 | ) 106 | write_attributes = try(e.write_attributes, null) 107 | enable_token_revocation = try(e.enable_token_revocation, null) 108 | refresh_token_rotation = try(e.refresh_token_rotation, {}) 109 | } 110 | ] 111 | 112 | clients = length(var.clients) == 0 && (var.client_name == null || var.client_name == "") ? [] : (length(var.clients) > 0 ? local.clients_parsed : local.clients_default) 113 | 114 | } 115 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cognito-deprecation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ⚠️ Cognito User Pool Feature Deprecation 3 | about: Auto-discovered deprecated AWS Cognito User Pool feature requiring action 4 | title: "chore: Handle deprecation of [DEPRECATED_FEATURE]" 5 | labels: ["deprecation", "breaking-change", "aws-provider-update", "auto-discovered"] 6 | assignees: [] 7 | --- 8 | 9 | ## ⚠️ Cognito User Pool Feature Deprecation Notice 10 | 11 | **AWS Provider Version:** v[PROVIDER_VERSION] 12 | **Deprecated Feature:** `[DEPRECATED_FEATURE]` 13 | **Deprecation Date:** `[DEPRECATION_DATE]` 14 | **Planned Removal:** `[REMOVAL_VERSION]` (if known) 15 | **Priority:** P1-High 16 | **Auto-detected:** ✅ `[SCAN_DATE]` 17 | 18 | ### Deprecation Details 19 | 20 | [DEPRECATION_DESCRIPTION] 21 | 22 | ### Current Usage in Module 23 | **Files Affected:** 24 | - [ ] `main.tf` - [USAGE_DETAILS] 25 | - [ ] `client.tf` - [USAGE_DETAILS] 26 | - [ ] `domain.tf` - [USAGE_DETAILS] 27 | - [ ] `identity-provider.tf` - [USAGE_DETAILS] 28 | - [ ] `resource-server.tf` - [USAGE_DETAILS] 29 | - [ ] `user-group.tf` - [USAGE_DETAILS] 30 | - [ ] `variables.tf` - [VARIABLE_REFERENCES] 31 | - [ ] `outputs.tf` - [OUTPUT_REFERENCES] 32 | - [ ] `examples/*/` - [EXAMPLE_USAGE] 33 | 34 | **Impact Assessment:** 35 | - **Severity:** [High/Medium/Low] 36 | - **User Impact:** [Breaking/Non-breaking] 37 | - **Module Components Affected:** [List of components] 38 | 39 | ### Migration Path 40 | 41 | 42 | #### Recommended Replacement 43 | ```hcl 44 | # OLD (Deprecated) 45 | [OLD_CONFIGURATION] 46 | 47 | # NEW (Recommended) 48 | [NEW_CONFIGURATION] 49 | ``` 50 | 51 | ### Action Plan 52 | 53 | #### Phase 1: Assessment & Planning 54 | - [ ] **Audit Current Usage** 55 | - [ ] Search all module files for deprecated feature usage 56 | - [ ] Identify all examples using the deprecated feature 57 | - [ ] Document impact on existing users 58 | - [ ] **Validate Migration** 59 | - [ ] Create validation branch with new implementation 60 | - [ ] Validate functionality with new approach 61 | - [ ] Ensure backward compatibility during transition 62 | 63 | #### Phase 2: Implementation 64 | - [ ] **Update Module Code** 65 | - [ ] Replace deprecated feature with recommended alternative 66 | - [ ] Add conditional logic for smooth transition (if possible) 67 | - [ ] Update variable descriptions and validation 68 | - [ ] Add deprecation warnings in variable descriptions 69 | - [ ] **Update Examples** 70 | - [ ] Modify all affected examples 71 | - [ ] Add migration examples showing both old and new patterns 72 | - [ ] Update example documentation 73 | 74 | #### Phase 3: Documentation & Communication 75 | - [ ] **Update Documentation** 76 | - [ ] Add deprecation notice to README 77 | - [ ] Document migration steps for users 78 | - [ ] Update variable documentation 79 | - [ ] Add to CHANGELOG.md with migration guidance 80 | - [ ] **Add Deprecation Warnings** 81 | - [ ] Add validation warnings for deprecated usage 82 | - [ ] Include migration instructions in validation messages 83 | - [ ] Plan timeline for complete removal 84 | 85 | #### Phase 4: AI Validation & Quality Assurance 86 | - [ ] **Comprehensive Validation** 87 | - [ ] Validate all examples with new implementation 88 | - [ ] Request AI validation with specialized agents 89 | - [ ] Validate backward compatibility 90 | - [ ] Validate upgrade scenarios 91 | - [ ] **Quality Assurance** 92 | - [ ] Run `terraform fmt`, `terraform validate` 93 | - [ ] Run `pre-commit run --all-files` 94 | - [ ] Peer review migration approach 95 | 96 | ### Timeline 97 | - **Deprecation Notice:** `[DEPRECATION_DATE]` 98 | - **Migration Deadline:** `[MIGRATION_DEADLINE]` 99 | - **Planned Removal:** `[REMOVAL_DATE]` 100 | - **Our Target Migration:** `[OUR_TIMELINE]` 101 | 102 | ### Provider Documentation 103 | - **Deprecation Notice:** [DEPRECATION_DOCS_LINK] 104 | - **Migration Guide:** [MIGRATION_GUIDE_LINK] 105 | - **New Feature Docs:** [NEW_FEATURE_DOCS_LINK] 106 | 107 | ### User Communication Strategy 108 | ```markdown 109 | # Deprecation Notice Template for README 110 | 111 | ⚠️ **Deprecation Warning**: The `[DEPRECATED_FEATURE]` feature is deprecated as of AWS Provider v[VERSION]. 112 | 113 | **What's changing:** [BRIEF_DESCRIPTION] 114 | **Timeline:** Deprecated in v[VERSION], will be removed in v[FUTURE_VERSION] 115 | **Action required:** [MIGRATION_STEPS] 116 | 117 | **Migration example:** 118 | ```hcl 119 | # Before (deprecated) 120 | [OLD_CONFIG] 121 | 122 | # After (recommended) 123 | [NEW_CONFIG] 124 | ``` 125 | 126 | For detailed migration instructions, see [MIGRATION_GUIDE_LINK]. 127 | ``` 128 | 129 | ### Cognito-Specific Impact Assessment 130 | - [ ] **User Pool Configuration** 131 | - [ ] Impact on user pool creation and management 132 | - [ ] Changes to authentication settings 133 | - [ ] Effect on user pool policies 134 | - [ ] **Client Applications** 135 | - [ ] Impact on user pool clients 136 | - [ ] Changes to authentication flows 137 | - [ ] Effect on client configurations 138 | - [ ] **Identity Federation** 139 | - [ ] Impact on identity providers 140 | - [ ] Changes to federated authentication 141 | - [ ] Effect on SAML/OIDC configurations 142 | - [ ] **User Experience** 143 | - [ ] Impact on login/signup flows 144 | - [ ] Changes to UI customization 145 | - [ ] Effect on branding configurations 146 | 147 | ### Breaking Change Considerations 148 | - [ ] **Semantic Versioning Impact** 149 | - [ ] Determine if this requires major version bump 150 | - [ ] Plan release strategy (immediate patch vs next major) 151 | - [ ] Consider feature flag approach for transition period 152 | - [ ] **User Migration Support** 153 | - [ ] Provide clear migration examples 154 | - [ ] Consider supporting both approaches temporarily 155 | - [ ] Add helpful validation messages 156 | 157 | ### Example Migration 158 | ```hcl 159 | # Example showing before/after for module users 160 | 161 | # BEFORE (using deprecated feature) 162 | module "cognito_old" { 163 | source = "./terraform-aws-cognito-user-pool" 164 | 165 | user_pool_name = "my-app" 166 | [DEPRECATED_USAGE] 167 | } 168 | 169 | # AFTER (using new approach) 170 | module "cognito_new" { 171 | source = "./terraform-aws-cognito-user-pool" 172 | 173 | user_pool_name = "my-app" 174 | [NEW_APPROACH] 175 | } 176 | ``` 177 | 178 | ### Validation Commands 179 | ```bash 180 | # Validate with deprecated feature (should show warnings) 181 | terraform plan 182 | 183 | # Validate migration path 184 | terraform init -upgrade 185 | terraform validate 186 | terraform plan 187 | 188 | # Request AI validation 189 | # @claude Use cognito-migration and terraform-security agents to validate 190 | # the deprecation migration. Ensure backward compatibility and proper 191 | # migration path for users. 192 | ``` 193 | 194 | ### Acceptance Criteria 195 | - [ ] All deprecated usage removed from module 196 | - [ ] Migration path documented and validated 197 | - [ ] Backward compatibility maintained (if possible) 198 | - [ ] Users have clear migration instructions 199 | - [ ] AI validation completed with specialized agents 200 | - [ ] Documentation updated with migration guidance 201 | - [ ] Deprecation warnings implemented (if gradual migration) 202 | 203 | --- 204 | 205 | ### 🤖 Automation Details 206 | **Discovery Workflow:** `feature-discovery.yml` 207 | **Scan ID:** `[SCAN_ID]` 208 | **Detection Method:** AWS Provider deprecation analysis 209 | **Last Updated:** `[TIMESTAMP]` 210 | 211 | --- 212 | 213 | *This issue was automatically created by the Cognito User Pool Feature Discovery workflow. Please review the auto-generated content and prioritize based on removal timeline.* 214 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cognito-bug-fix.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Cognito User Pool Bug Fix 3 | about: Auto-discovered bug fix in AWS Cognito User Pool provider requiring module updates 4 | title: "fix: Address [BUG_DESCRIPTION]" 5 | labels: ["bug", "aws-provider-update", "auto-discovered"] 6 | assignees: [] 7 | --- 8 | 9 | ## 🐛 Cognito User Pool Provider Bug Fix 10 | 11 | **AWS Provider Version:** v[PROVIDER_VERSION] 12 | **Bug Type:** [Configuration/Behavior/Validation/Performance] 13 | **Priority:** [P0-Critical/P1-High/P2-Medium/P3-Low] 14 | **Auto-detected:** ✅ `[SCAN_DATE]` 15 | 16 | ### Bug Description 17 | 18 | [BUG_DESCRIPTION] 19 | 20 | ### Provider Fix Details 21 | **Fixed in Version:** `[FIXED_VERSION]` 22 | **Issue/PR Reference:** [PROVIDER_ISSUE_LINK] 23 | **Changelog Entry:** [CHANGELOG_LINK] 24 | 25 | ### Impact on Module 26 | **Current Module Behavior:** 27 | [CURRENT_BEHAVIOR_DESCRIPTION] 28 | 29 | **Expected Behavior After Fix:** 30 | [EXPECTED_BEHAVIOR_DESCRIPTION] 31 | 32 | **Files Potentially Affected:** 33 | - [ ] `main.tf` - [POTENTIAL_IMPACT] 34 | - [ ] `client.tf` - [POTENTIAL_IMPACT] 35 | - [ ] `domain.tf` - [POTENTIAL_IMPACT] 36 | - [ ] `identity-provider.tf` - [POTENTIAL_IMPACT] 37 | - [ ] `resource-server.tf` - [POTENTIAL_IMPACT] 38 | - [ ] `user-group.tf` - [POTENTIAL_IMPACT] 39 | - [ ] `variables.tf` - [VARIABLE_CHANGES] 40 | - [ ] `outputs.tf` - [OUTPUT_CHANGES] 41 | - [ ] `examples/*/` - [EXAMPLE_CHANGES] 42 | 43 | ### Analysis Required 44 | 45 | #### Impact Assessment 46 | - [ ] **Verify Current Behavior** 47 | - [ ] Reproduce the original bug with current module version 48 | - [ ] Document current workarounds (if any) 49 | - [ ] Identify users who might be affected 50 | - [ ] **Validate Provider Fix** 51 | - [ ] Update to fixed provider version 52 | - [ ] Verify fix resolves the issue 53 | - [ ] Check for any breaking changes in behavior 54 | - [ ] **Module Compatibility** 55 | - [ ] Ensure module works with both old and new provider versions 56 | - [ ] Update minimum provider version requirements if needed 57 | - [ ] Validate all examples still work correctly 58 | 59 | #### Code Changes Required 60 | - [ ] **Configuration Updates** 61 | ```hcl 62 | # Example of potential changes needed 63 | [CONFIGURATION_CHANGES] 64 | ``` 65 | - [ ] **Variable Updates** 66 | ```hcl 67 | # If variable validation or defaults need updates 68 | [VARIABLE_CHANGES] 69 | ``` 70 | - [ ] **Output Updates** 71 | ```hcl 72 | # If outputs are affected by the fix 73 | [OUTPUT_CHANGES] 74 | ``` 75 | 76 | ### Action Plan 77 | 78 | #### Phase 1: Investigation 79 | - [ ] **Reproduce Original Bug** 80 | - [ ] Create validation scenario demonstrating the bug 81 | - [ ] Document exact conditions that trigger the issue 82 | - [ ] Verify impact on module functionality 83 | - [ ] **Validate Provider Fix** 84 | - [ ] Update validation environment to fixed provider version 85 | - [ ] Confirm bug is resolved 86 | - [ ] Check for any behavioral changes 87 | 88 | #### Phase 2: Module Updates 89 | - [ ] **Code Changes** 90 | - [ ] Update configurations to leverage the fix 91 | - [ ] Remove any workarounds that are no longer needed 92 | - [ ] Update variable validation if applicable 93 | - [ ] Adjust outputs if behavior changed 94 | - [ ] **Version Requirements** 95 | - [ ] Update minimum AWS provider version in `versions.tf` 96 | - [ ] Update README with new requirements 97 | - [ ] Check compatibility matrix 98 | 99 | #### Phase 3: AI Validation & Quality Checks 100 | - [ ] **Comprehensive Validation** 101 | - [ ] Validate all affected examples 102 | - [ ] Request AI validation with specialized agents for new provider version 103 | - [ ] Validate backward compatibility (if maintaining support for older versions) 104 | - [ ] Validate edge cases that might be affected 105 | - [ ] **Example Updates** 106 | - [ ] Update examples to use fixed behavior 107 | - [ ] Remove workaround code from examples 108 | - [ ] Add validation scenario that would have failed before fix 109 | 110 | #### Phase 4: Documentation 111 | - [ ] **Update Documentation** 112 | - [ ] Document the fix in README 113 | - [ ] Update CHANGELOG.md with bug fix details 114 | - [ ] Update variable/output documentation if changed 115 | - [ ] Add any relevant usage notes 116 | 117 | ### Cognito-Specific Impact Areas 118 | - [ ] **User Pool Management** 119 | - [ ] Impact on user pool creation/updates 120 | - [ ] Effect on user pool configuration 121 | - [ ] Changes to user pool policies 122 | - [ ] **Authentication & Authorization** 123 | - [ ] Impact on authentication flows 124 | - [ ] Effect on user pool clients 125 | - [ ] Changes to token handling 126 | - [ ] **Identity Federation** 127 | - [ ] Impact on identity providers 128 | - [ ] Effect on SAML/OIDC integration 129 | - [ ] Changes to attribute mapping 130 | - [ ] **User Management** 131 | - [ ] Impact on user creation/management 132 | - [ ] Effect on user attributes 133 | - [ ] Changes to user group functionality 134 | - [ ] **Security Features** 135 | - [ ] Impact on MFA configurations 136 | - [ ] Effect on advanced security features 137 | - [ ] Changes to risk-based authentication 138 | 139 | ### Validation Strategy 140 | 141 | #### Validation Scenarios to Create/Update 142 | ```bash 143 | # Scenario that would have failed before the fix 144 | [VALIDATION_SCENARIO_EXAMPLE] 145 | 146 | # Regression validation to ensure fix works 147 | [REGRESSION_VALIDATION] 148 | ``` 149 | 150 | #### Validation Commands 151 | ```bash 152 | # Validate with examples 153 | cd examples/[affected-example] 154 | terraform init -upgrade 155 | terraform validate 156 | terraform plan 157 | 158 | # Request AI validation 159 | # @claude Use terraform-cognito and terraform-security agents to validate 160 | # the bug fix. Ensure the fix resolves the issue without introducing 161 | # regressions or security concerns. 162 | ``` 163 | 164 | ### Provider Version Strategy 165 | **Current Requirement:** `>= [CURRENT_VERSION]` 166 | **Recommended Update:** `>= [FIXED_VERSION]` 167 | 168 | **Migration Options:** 169 | - [ ] **Option 1: Hard Requirement** - Update minimum version to fixed version 170 | - [ ] **Option 2: Soft Migration** - Support both versions with conditional logic 171 | - [ ] **Option 3: Gradual Rollout** - Document fix availability, update in next major version 172 | 173 | ### User Impact Assessment 174 | **Breaking Change:** [Yes/No] 175 | **Action Required by Users:** [Description] 176 | 177 | **User Communication:** 178 | ```markdown 179 | # Bug Fix Notice Template 180 | 181 | 🐛 **Bug Fix Available**: [BUG_DESCRIPTION] 182 | 183 | **What was fixed:** [BRIEF_DESCRIPTION] 184 | **AWS Provider Version:** Requires `>= [FIXED_VERSION]` 185 | **Action required:** [USER_ACTION_NEEDED] 186 | 187 | **Before (buggy behavior):** 188 | [BEFORE_EXAMPLE] 189 | 190 | **After (fixed behavior):** 191 | [AFTER_EXAMPLE] 192 | ``` 193 | 194 | ### Example Updates 195 | ```hcl 196 | # Example showing how the fix changes module usage 197 | 198 | # BEFORE (with workaround or buggy behavior) 199 | module "cognito_before" { 200 | source = "./terraform-aws-cognito-user-pool" 201 | 202 | user_pool_name = "my-app" 203 | [OLD_CONFIGURATION_WITH_WORKAROUND] 204 | } 205 | 206 | # AFTER (using fixed provider behavior) 207 | module "cognito_after" { 208 | source = "./terraform-aws-cognito-user-pool" 209 | 210 | user_pool_name = "my-app" 211 | [CLEAN_CONFIGURATION_USING_FIX] 212 | } 213 | ``` 214 | 215 | ### Acceptance Criteria 216 | - [ ] Original bug reproduced and documented 217 | - [ ] Provider fix verified to resolve the issue 218 | - [ ] Module updated to work with fixed provider 219 | - [ ] AI validation completed with specialized agents 220 | - [ ] Documentation updated to reflect changes 221 | - [ ] Examples demonstrate proper usage with fix 222 | - [ ] User migration path documented (if needed) 223 | - [ ] No regression in other functionality 224 | 225 | ### Priority Justification 226 | **P0 - Critical:** Security issues, data corruption, service unavailability 227 | **P1 - High:** Functional bugs affecting core features 228 | **P2 - Medium:** Minor functionality issues, UX problems 229 | **P3 - Low:** Cosmetic issues, edge cases 230 | 231 | **This issue is priority [PRIORITY] because:** [JUSTIFICATION] 232 | 233 | --- 234 | 235 | ### 🤖 Automation Details 236 | **Discovery Workflow:** `feature-discovery.yml` 237 | **Scan ID:** `[SCAN_ID]` 238 | **Detection Method:** Provider changelog analysis 239 | **Last Updated:** `[TIMESTAMP]` 240 | 241 | --- 242 | 243 | *This issue was automatically created by the Cognito User Pool Feature Discovery workflow. Please validate the bug impact and prioritize accordingly.* 244 | -------------------------------------------------------------------------------- /MIGRATION_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | This guide covers migrations from count-based to for_each-based implementations in the terraform-aws-cognito-user-pool module. 4 | 5 | ## User Pool Clients Migration (PR #249) 6 | 7 | ### Overview 8 | This guide covers the migration from count-based to for_each-based user pool client implementation. This change resolves state management issues and improves resource tracking. 9 | 10 | ### What Changed 11 | - **Before**: User pool clients used `count` with list indexes as resource identifiers 12 | - **After**: User pool clients use `for_each` with meaningful keys as stable resource identifiers 13 | 14 | ### Key Generation Pattern 15 | The new for_each implementation uses the following key pattern: 16 | ```hcl 17 | "${lookup(client, "name", "client")}_${idx}" => client 18 | ``` 19 | 20 | This means: 21 | - Clients with names: `"my-app_0"`, `"mobile-client_1"`, etc. 22 | - Clients without names: `"client_0"`, `"client_1"`, etc. 23 | 24 | ### Migration Process 25 | 26 | #### Manual Migration Required 27 | Due to Terraform limitations with dynamic expressions in `moved` blocks, manual state migration is required. 28 | 29 | #### Steps to Upgrade 30 | 31 | ##### Option 1: Using the Migration Helper Script (Recommended) 32 | 33 | 1. **Update the module version** in your Terraform configuration 34 | 2. **Run the migration script**: 35 | ```bash 36 | chmod +x migrate-clients.sh 37 | ./migrate-clients.sh 38 | ``` 39 | 40 | The script will: 41 | - Analyze your current Terraform state 42 | - Prompt you for client names 43 | - Generate the correct migration commands 44 | - Optionally run the commands automatically 45 | - Verify the migration with `terraform plan` 46 | 47 | ##### Option 2: Manual Migration 48 | 49 | 1. **Update the module version** in your Terraform configuration 50 | 2. **Run terraform plan** to see what changes Terraform wants to make 51 | 3. **Identify your existing clients** from the plan output and your configuration 52 | 4. **Run state migration commands** for each client: 53 | 54 | ```bash 55 | # Example 1: Clients with names 56 | # Configuration: clients = [{ name = "web-app" }, { name = "mobile-app" }] 57 | terraform state mv 'aws_cognito_user_pool_client.client[0]' 'aws_cognito_user_pool_client.client["web-app_0"]' 58 | terraform state mv 'aws_cognito_user_pool_client.client[1]' 'aws_cognito_user_pool_client.client["mobile-app_1"]' 59 | 60 | # Example 2: Clients without names 61 | # Configuration: clients = [{}, {}] (no name specified) 62 | terraform state mv 'aws_cognito_user_pool_client.client[0]' 'aws_cognito_user_pool_client.client["client_0"]' 63 | terraform state mv 'aws_cognito_user_pool_client.client[1]' 'aws_cognito_user_pool_client.client["client_1"]' 64 | 65 | # Example 3: Mixed configuration 66 | # Configuration: clients = [{ name = "web-app" }, {}] (first has name, second doesn't) 67 | terraform state mv 'aws_cognito_user_pool_client.client[0]' 'aws_cognito_user_pool_client.client["web-app_0"]' 68 | terraform state mv 'aws_cognito_user_pool_client.client[1]' 'aws_cognito_user_pool_client.client["client_1"]' 69 | ``` 70 | 71 | 5. **Run terraform plan again** to verify no changes are needed 72 | 6. **Run terraform apply** if any configuration changes are required 73 | 74 | #### What to Expect 75 | - Initial `terraform plan` will show clients being destroyed and recreated 76 | - After state migration, `terraform plan` should show no changes 77 | - **No resources will be destroyed or recreated** when migration is done correctly 78 | - Existing client configurations and secrets are preserved 79 | 80 | #### Validation 81 | After migration, you can verify: 82 | - User pool clients still exist in the AWS console 83 | - All client configurations remain unchanged 84 | - Client secrets are preserved (if they were generated) 85 | 86 | #### New Features 87 | - **List order immunity**: Reordering clients in configuration won't cause deletions 88 | - **Better resource tracking**: More meaningful resource identifiers 89 | - **Enhanced outputs**: Outputs now use client names as keys when available 90 | 91 | #### Breaking Changes 92 | - **None for existing configurations** - the migration is backward compatible 93 | - Resource addresses change from indexed to named keys 94 | 95 | #### Troubleshooting 96 | 97 | ##### Finding Your Client Names and Order 98 | To identify your current client names and their order: 99 | 100 | ```bash 101 | # List current clients in your state 102 | terraform state list | grep aws_cognito_user_pool_client 103 | 104 | # Show client details to find names 105 | terraform state show 'aws_cognito_user_pool_client.client[0]' | grep name 106 | terraform state show 'aws_cognito_user_pool_client.client[1]' | grep name 107 | # ... continue for all clients 108 | ``` 109 | 110 | ##### Alternative Migration Approach 111 | If you prefer to avoid manual state migration: 112 | 113 | 1. **Backup client configurations** before the change: 114 | ```bash 115 | # Export client settings 116 | aws cognito-idp describe-user-pool-client --user-pool-id --client-id 117 | ``` 118 | 119 | 2. **Allow Terraform to recreate the clients** (they will be recreated with the same settings) 120 | - **Note**: Client secrets will be regenerated and need to be updated in applications 121 | 122 | 3. **Update applications** with new client secrets if `generate_secret = true` 123 | 124 | --- 125 | 126 | # User Groups Migration Guide 127 | 128 | ## Overview 129 | This guide covers the migration from count-based to for_each-based user group implementation in the terraform-aws-cognito-user-pool module. This change resolves issue #161 where user groups were vulnerable to unintended deletion when list order changed. 130 | 131 | ## What Changed 132 | - **Before**: User groups used `count` with list indexes as resource identifiers 133 | - **After**: User groups use `for_each` with group names as stable resource identifiers 134 | 135 | ## Migration Process 136 | 137 | ### Manual Migration Required 138 | Due to Terraform limitations with dynamic expressions in `moved` blocks, manual state migration is required. 139 | 140 | ### Steps to Upgrade 141 | 142 | 1. **Update the module version** in your Terraform configuration 143 | 2. **Run terraform plan** to see what changes Terraform wants to make 144 | 3. **Identify your existing user groups** from the plan output 145 | 4. **Run state migration commands** for each group: 146 | 147 | ```bash 148 | # Example: If you have groups named "admins", "users", "developers" 149 | terraform state mv 'aws_cognito_user_group.main[0]' 'aws_cognito_user_group.main["admins"]' 150 | terraform state mv 'aws_cognito_user_group.main[1]' 'aws_cognito_user_group.main["users"]' 151 | terraform state mv 'aws_cognito_user_group.main[2]' 'aws_cognito_user_group.main["developers"]' 152 | ``` 153 | 154 | 5. **Run terraform plan again** to verify no changes are needed 155 | 6. **Run terraform apply** if any configuration changes are required 156 | 157 | ### What to Expect 158 | - Initial `terraform plan` will show groups being destroyed and recreated 159 | - After state migration, `terraform plan` should show no changes 160 | - **No resources will be destroyed or recreated** when migration is done correctly 161 | - Existing user group memberships are preserved 162 | 163 | ## Validation 164 | After migration, you can verify: 165 | - User groups still exist in the AWS console 166 | - All group memberships are intact 167 | - Group configurations remain unchanged 168 | 169 | ## New Features 170 | - **List order immunity**: Reordering groups in configuration won't cause deletions 171 | - **Unique name validation**: Duplicate group names are now prevented 172 | - **Enhanced outputs**: New outputs for group IDs, names, ARNs, and detailed mapping 173 | 174 | ## Breaking Changes 175 | - **None for existing configurations** - the migration is backward compatible 176 | - **New validation**: Duplicate group names will now cause validation errors 177 | 178 | ## Troubleshooting 179 | 180 | ### Finding Your Group Names 181 | To identify your current group names and their order: 182 | 183 | ```bash 184 | # List current user groups in your state 185 | terraform state list | grep aws_cognito_user_group 186 | terraform state show 'aws_cognito_user_group.main[0]' | grep name 187 | terraform state show 'aws_cognito_user_group.main[1]' | grep name 188 | # ... continue for all groups 189 | ``` 190 | 191 | ### Alternative Migration Approach 192 | If you prefer to avoid manual state migration, you can: 193 | 194 | 1. **Export user memberships** before the change: 195 | ```bash 196 | aws cognito-idp list-users-in-group --user-pool-id --group-name 197 | ``` 198 | 199 | 2. **Allow Terraform to recreate the groups** (they will be recreated with the same settings) 200 | 201 | 3. **Restore user memberships** after the change: 202 | ```bash 203 | aws cognito-idp admin-add-user-to-group --user-pool-id --group-name --username 204 | ``` 205 | 206 | ## Questions? 207 | If you encounter issues during migration, please create an issue in the repository with: 208 | - Your Terraform version 209 | - Current module version 210 | - Error messages 211 | - Your user group configuration 212 | -------------------------------------------------------------------------------- /.github/scripts/discovery-prompt.md: -------------------------------------------------------------------------------- 1 | # Cognito User Pool Feature Discovery Prompts 2 | 3 | This document contains standardized prompts for Claude Code to ensure consistent feature discovery analysis. 4 | 5 | ## Main Discovery Prompt 6 | 7 | ``` 8 | # Cognito User Pool Feature Discovery Analysis 9 | 10 | You are performing automated feature discovery for the terraform-aws-cognito-user-pool module to identify new AWS Cognito User Pool features, deprecations, and bug fixes. 11 | 12 | ## Context 13 | - Repository: terraform-aws-cognito-user-pool 14 | - Current Provider Support: AWS Provider >= 4.0.0 15 | - Module Structure: Main user pool + client + domain + identity providers + managed login branding 16 | - Examples: 5 comprehensive examples covering different authentication scenarios 17 | 18 | ## Discovery Process 19 | 20 | ### Step 1: Load Current State 21 | Read the feature tracking database to understand what's already implemented: 22 | ```bash 23 | cat .github/feature-tracker/cognito-features.json 24 | ``` 25 | 26 | ### Step 2: Fetch Latest Cognito Documentation 27 | 28 | Use the Terraform MCP server to get comprehensive Cognito documentation: 29 | 30 | **Cognito Resources:** 31 | ``` 32 | mcp__terraform-server__resolveProviderDocID: 33 | - providerName: "aws" 34 | - providerNamespace: "hashicorp" 35 | - serviceSlug: "cognito" 36 | - providerVersion: "latest" 37 | - providerDataType: "resources" 38 | ``` 39 | 40 | **Cognito Data Sources:** 41 | ``` 42 | mcp__terraform-server__resolveProviderDocID: 43 | - providerName: "aws" 44 | - providerNamespace: "hashicorp" 45 | - serviceSlug: "cognito" 46 | - providerVersion: "latest" 47 | - providerDataType: "data-sources" 48 | ``` 49 | 50 | ### Step 3: Analyze Current Implementation 51 | 52 | Examine module structure and extract implementation details: 53 | 54 | **Core Files to Analyze:** 55 | - `main.tf` - Primary Cognito User Pool resource and configuration 56 | - `client.tf` - User pool client configurations 57 | - `domain.tf` - User pool domain configurations 58 | - `identity-provider.tf` - Identity provider configurations 59 | - `resource-server.tf` - Resource server configurations 60 | - `user-group.tf` - User group configurations 61 | - `managed-login-branding.tf` - Managed login branding configurations 62 | - `ui-customization.tf` - UI customization configurations 63 | - `variables.tf` - All input variables and their usage 64 | - `outputs.tf` - Module outputs 65 | 66 | **Examples Directory:** 67 | Check `examples/*/main.tf` files to understand supported patterns: 68 | - simple, simple_extended, complete 69 | - email_mfa, with_branding 70 | 71 | ### Step 4: Intelligent Comparison 72 | 73 | Compare provider documentation against current implementation: 74 | 75 | **New Features to Detect:** 76 | 1. **New Resources:** Any `aws_cognito_*` resources not in module 77 | 2. **New Arguments:** New arguments on existing resources 78 | 3. **New Data Sources:** New `data.aws_cognito_*` sources 79 | 4. **New Authentication Features:** Advanced authentication flows, adaptive authentication 80 | 5. **New Security Features:** Enhanced MFA, risk-based authentication, account takeover protection 81 | 6. **New User Management:** Advanced user pool features, user migration capabilities 82 | 7. **New Branding Features:** Enhanced UI customization, managed login capabilities 83 | 8. **New Federation Features:** Enhanced identity provider integration 84 | 85 | **Deprecations to Find:** 86 | 1. **Deprecated Arguments:** Arguments marked for removal 87 | 2. **Deprecated Resources:** Resources being phased out 88 | 3. **Deprecated Authentication Flows:** Auth flows no longer recommended 89 | 4. **Deprecated Patterns:** Configuration patterns no longer recommended 90 | 91 | **Bug Fixes to Identify:** 92 | Use Context7 MCP server to check AWS provider changelogs: 93 | ``` 94 | mcp__context7__resolve-library-id: "terraform-provider-aws" 95 | mcp__context7__get-library-docs: topic="cognito changes changelog" 96 | ``` 97 | 98 | ### Step 5: Categorize and Prioritize 99 | 100 | **Priority Classification:** 101 | - **P0 (Critical):** Security vulnerabilities, authentication breaking changes 102 | - **P1 (High):** New core authentication resources, major deprecations 103 | - **P2 (Medium):** New arguments, authentication enhancements 104 | - **P3 (Low):** Minor improvements, cosmetic changes 105 | 106 | **Feature Categories:** 107 | - **NEW_RESOURCE:** Entirely new Cognito resource type 108 | - **NEW_ARGUMENT:** New argument on existing resource 109 | - **NEW_DATA_SOURCE:** New data source for Cognito 110 | - **ENHANCEMENT:** Improvement to existing functionality 111 | - **DEPRECATION:** Feature being deprecated 112 | - **BUG_FIX:** Important bug fix from provider 113 | - **SECURITY:** Security-related update 114 | - **AUTHENTICATION:** Authentication flow improvements 115 | 116 | ### Step 6: Issue Creation 117 | 118 | For each significant finding, create GitHub issues using appropriate templates. 119 | 120 | **Check for Duplicates First:** 121 | ```bash 122 | gh issue list --label "auto-discovered" --state open 123 | gh issue list --search "[FEATURE_NAME]" --state all 124 | ``` 125 | 126 | **Create Issues with Proper Data:** 127 | ```bash 128 | # New Feature 129 | gh issue create \ 130 | --template .github/ISSUE_TEMPLATE/new-cognito-feature.md \ 131 | --title "feat: Add support for [FEATURE_NAME]" \ 132 | --label "enhancement,aws-provider-update,auto-discovered" \ 133 | --body "[DETAILED_DESCRIPTION]" 134 | 135 | # Deprecation 136 | gh issue create \ 137 | --template .github/ISSUE_TEMPLATE/cognito-deprecation.md \ 138 | --title "chore: Handle deprecation of [DEPRECATED_FEATURE]" \ 139 | --label "deprecation,breaking-change,auto-discovered" \ 140 | --body "[DEPRECATION_DETAILS]" 141 | 142 | # Bug Fix 143 | gh issue create \ 144 | --template .github/ISSUE_TEMPLATE/cognito-bug-fix.md \ 145 | --title "fix: Address [BUG_DESCRIPTION]" \ 146 | --label "bug,aws-provider-update,auto-discovered" \ 147 | --body "[BUG_FIX_DETAILS]" 148 | ``` 149 | 150 | ### Step 7: Update Tracking Database 151 | 152 | Update `.github/feature-tracker/cognito-features.json`: 153 | 154 | ```json 155 | { 156 | "metadata": { 157 | "last_scan": "[CURRENT_TIMESTAMP]", 158 | "provider_version": "[SCANNED_VERSION]", 159 | "scan_count": "[INCREMENTED_COUNT]" 160 | }, 161 | "scan_history": [ 162 | { 163 | "scan_date": "[CURRENT_TIMESTAMP]", 164 | "provider_version": "[SCANNED_VERSION]", 165 | "features_found": "[COUNT]", 166 | "deprecations_found": "[COUNT]", 167 | "fixes_found": "[COUNT]", 168 | "issues_created": "[ISSUE_NUMBERS]" 169 | } 170 | ], 171 | "discovered_features": { 172 | "new_resources": { 173 | "[RESOURCE_NAME]": { 174 | "discovered_date": "[DATE]", 175 | "provider_version": "[VERSION]", 176 | "issue_number": "[ISSUE_NUM]", 177 | "priority": "[P0/P1/P2/P3]", 178 | "status": "pending" 179 | } 180 | }, 181 | // Similar structure for new_arguments, deprecations, etc. 182 | }, 183 | "issues_created": [ 184 | { 185 | "issue_number": "[NUM]", 186 | "feature_type": "[TYPE]", 187 | "feature_name": "[NAME]", 188 | "created_date": "[DATE]", 189 | "priority": "[PRIORITY]", 190 | "status": "open" 191 | } 192 | ] 193 | } 194 | ``` 195 | 196 | ## Quality Assurance Rules 197 | 198 | **Avoid False Positives:** 199 | 1. Only flag features not already in module 200 | 2. Verify features are Cognito User Pool-specific (not general AWS) 201 | 3. Check if feature is already tracked as "pending" 202 | 4. Ensure feature is stable (not experimental) 203 | 204 | **Issue Content Requirements:** 205 | 1. Include provider documentation links 206 | 2. Provide implementation examples 207 | 3. Add clear acceptance criteria 208 | 4. Estimate complexity/priority correctly 209 | 5. Include testing requirements 210 | 211 | **Tracking Requirements:** 212 | 1. Update scan timestamp 213 | 2. Increment scan counter 214 | 3. Record all findings 215 | 4. Track issue numbers 216 | 5. Maintain history 217 | 218 | ## Cognito-Specific Focus Areas 219 | 220 | **User Pool Management:** 221 | - User pool configuration and settings 222 | - User pool tiers and advanced features 223 | - Account recovery and user migration 224 | 225 | **Authentication & Authorization:** 226 | - Authentication flows and grant types 227 | - OAuth 2.0 and OIDC configurations 228 | - Token validity and session management 229 | 230 | **Security Features:** 231 | - Multi-factor authentication (MFA) 232 | - Advanced security features and risk-based authentication 233 | - Account takeover protection 234 | 235 | **User Management:** 236 | - User attributes and schema customization 237 | - User groups and role-based access 238 | - User invitation and management workflows 239 | 240 | **Federation & Identity Providers:** 241 | - Social identity providers (Google, Facebook, Amazon, etc.) 242 | - SAML and OIDC identity providers 243 | - Attribute mapping and user data synchronization 244 | 245 | **Branding & UI Customization:** 246 | - Hosted UI customization 247 | - Managed login branding 248 | - Custom domains and SSL certificates 249 | 250 | ## Success Metrics 251 | 252 | Track these metrics in your analysis: 253 | - Features discovered vs actually new 254 | - Issues created vs duplicates avoided 255 | - Priority accuracy (validated later) 256 | - Implementation time (tracked over time) 257 | 258 | ## Expected Output Format 259 | 260 | Provide a structured summary: 261 | 262 | ``` 263 | ## Cognito User Pool Feature Discovery Results 264 | 265 | **Scan Details:** 266 | - Timestamp: [TIMESTAMP] 267 | - Provider Version: [VERSION] 268 | - Scan Duration: [DURATION] 269 | 270 | **Findings Summary:** 271 | - New Features: [COUNT] ([P0]: [COUNT], [P1]: [COUNT], [P2]: [COUNT], [P3]: [COUNT]) 272 | - Deprecations: [COUNT] 273 | - Bug Fixes: [COUNT] 274 | - Total Issues Created: [COUNT] 275 | 276 | **New Features Discovered:** 277 | 1. [FEATURE_NAME] (Priority: [P1], Issue: #[NUM]) 278 | - Type: [Resource/Argument/Data Source] 279 | - Description: [BRIEF_DESC] 280 | - Implementation Complexity: [High/Medium/Low] 281 | 282 | **Deprecations Found:** 283 | 1. [DEPRECATED_FEATURE] (Issue: #[NUM]) 284 | - Removal Timeline: [TIMELINE] 285 | - Impact: [High/Medium/Low] 286 | - Migration Path: [AVAILABLE/NEEDS_RESEARCH] 287 | 288 | **Bug Fixes Identified:** 289 | 1. [BUG_DESCRIPTION] (Issue: #[NUM]) 290 | - Fixed in Version: [VERSION] 291 | - Module Impact: [DESCRIPTION] 292 | 293 | **Actions Taken:** 294 | - Updated feature tracking database 295 | - Created [COUNT] GitHub issues 296 | - No duplicates found 297 | - All issues properly labeled and categorized 298 | 299 | **Recommendations:** 300 | - [RECOMMENDATION_1] 301 | - [RECOMMENDATION_2] 302 | ``` 303 | 304 | ## Error Handling 305 | 306 | If any step fails: 307 | 1. Log the specific error 308 | 2. Continue with remaining steps 309 | 3. Report partial results 310 | 4. Suggest manual review for failed areas 311 | ``` 312 | 313 | Use this prompt structure consistently for all feature discovery runs to ensure comprehensive and accurate analysis. 314 | -------------------------------------------------------------------------------- /examples/complete/README.md: -------------------------------------------------------------------------------- 1 | # This is a complete example 2 | 3 | This example demonstrates a comprehensive AWS Cognito User Pool configuration including custom schemas, clients, domains, resource servers, and identity providers. 4 | 5 | ## Important: Schema Management 6 | 7 | ⚠️ **This example uses custom schemas** (`schemas`, `string_schemas`, `number_schemas`). The `ignore_schema_changes = true` variable is **essential** to prevent perpetual diffs and AWS API errors, since Cognito schema attributes cannot be modified or removed after creation. 8 | 9 | ``` 10 | module "aws_cognito_user_pool_complete_example" { 11 | 12 | source = "lgallard/cognito-user-pool/aws" 13 | 14 | user_pool_name = "mypool_complete" 15 | alias_attributes = ["email", "phone_number"] 16 | auto_verified_attributes = ["email"] 17 | sms_authentication_message = "Your username is {username} and temporary password is {####}." 18 | sms_verification_message = "This is the verification message {####}." 19 | 20 | deletion_protection = "ACTIVE" 21 | 22 | # IMPORTANT: Enable schema ignore changes to prevent perpetual diffs with custom schemas 23 | # This is ESSENTIAL for this example since it uses custom schemas (schemas, string_schemas, number_schemas) 24 | # Without this, Terraform will attempt to recreate schemas on every plan, causing AWS API errors 25 | ignore_schema_changes = true 26 | 27 | mfa_configuration = "OPTIONAL" 28 | software_token_mfa_configuration = { 29 | enabled = true 30 | } 31 | 32 | admin_create_user_config = { 33 | email_message = "Dear {username}, your verification code is {####}." 34 | email_subject = "Here, your verification code baby" 35 | sms_message = "Your username is {username} and temporary password is {####}." 36 | } 37 | 38 | device_configuration = { 39 | challenge_required_on_new_device = true 40 | device_only_remembered_on_user_prompt = true 41 | } 42 | 43 | email_configuration = { 44 | email_sending_account = "DEVELOPER" 45 | reply_to_email_address = "email@mydomain.com" 46 | source_arn = "arn:aws:ses:us-east-1:123456789012:identity/myemail@mydomain.com" 47 | from_email_address = "noreply@mydomain.com" 48 | configuration_set = "my-configuration-set" 49 | } 50 | 51 | lambda_config = { 52 | create_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:create_auth_challenge" 53 | custom_message = "arn:aws:lambda:us-east-1:123456789012:function:custom_message" 54 | define_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:define_auth_challenge" 55 | post_authentication = "arn:aws:lambda:us-east-1:123456789012:function:post_authentication" 56 | post_confirmation = "arn:aws:lambda:us-east-1:123456789012:function:post_confirmation" 57 | pre_authentication = "arn:aws:lambda:us-east-1:123456789012:function:pre_authentication" 58 | pre_sign_up = "arn:aws:lambda:us-east-1:123456789012:function:pre_sign_up" 59 | pre_token_generation = "arn:aws:lambda:us-east-1:123456789012:function:pre_token_generation" 60 | user_migration = "arn:aws:lambda:us-east-1:123456789012:function:user_migration" 61 | verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:verify_auth_challenge_response" 62 | } 63 | 64 | password_policy = { 65 | minimum_length = 10 66 | require_lowercase = false 67 | require_numbers = true 68 | require_symbols = true 69 | require_uppercase = true 70 | temporary_password_validity_days = 120 71 | 72 | } 73 | 74 | user_pool_add_ons = { 75 | advanced_security_mode = "ENFORCED" 76 | } 77 | 78 | verification_message_template = { 79 | default_email_option = "CONFIRM_WITH_CODE" 80 | } 81 | 82 | schemas = [ 83 | { 84 | attribute_data_type = "Boolean" 85 | developer_only_attribute = false 86 | mutable = true 87 | name = "available" 88 | required = false 89 | }, 90 | { 91 | attribute_data_type = "Boolean" 92 | developer_only_attribute = true 93 | mutable = true 94 | name = "registered" 95 | required = false 96 | } 97 | ] 98 | 99 | string_schemas = [ 100 | { 101 | attribute_data_type = "String" 102 | developer_only_attribute = false 103 | mutable = false 104 | name = "email" 105 | required = true 106 | 107 | string_attribute_constraints = { 108 | min_length = 7 109 | max_length = 15 110 | } 111 | }, 112 | { 113 | attribute_data_type = "String" 114 | developer_only_attribute = false 115 | mutable = false 116 | name = "gender" 117 | required = true 118 | 119 | string_attribute_constraints = { 120 | min_length = 7 121 | max_length = 15 122 | } 123 | }, 124 | ] 125 | 126 | number_schemas = [ 127 | { 128 | attribute_data_type = "Number" 129 | developer_only_attribute = true 130 | mutable = true 131 | name = "mynumber1" 132 | required = false 133 | 134 | number_attribute_constraints = { 135 | min_value = 2 136 | max_value = 6 137 | } 138 | }, 139 | { 140 | attribute_data_type = "Number" 141 | developer_only_attribute = true 142 | mutable = true 143 | name = "mynumber2" 144 | required = false 145 | 146 | number_attribute_constraints = { 147 | min_value = 2 148 | max_value = 6 149 | } 150 | }, 151 | ] 152 | 153 | # user_pool_domain 154 | domain = "mydomain-com" 155 | 156 | # clients 157 | clients = [ 158 | { 159 | allowed_oauth_flows = [] 160 | allowed_oauth_flows_user_pool_client = false 161 | allowed_oauth_scopes = [] 162 | callback_urls = ["https://mydomain.com/callback"] 163 | default_redirect_uri = "https://mydomain.com/callback" 164 | explicit_auth_flows = [] 165 | generate_secret = true 166 | logout_urls = [] 167 | name = "test1" 168 | read_attributes = ["email"] 169 | supported_identity_providers = [] 170 | write_attributes = [] 171 | access_token_validity = 1 172 | id_token_validity = 1 173 | refresh_token_validity = 60 174 | token_validity_units = { 175 | access_token = "hours" 176 | id_token = "hours" 177 | refresh_token = "days" 178 | } 179 | }, 180 | { 181 | allowed_oauth_flows = [] 182 | allowed_oauth_flows_user_pool_client = false 183 | allowed_oauth_scopes = [] 184 | callback_urls = ["https://mydomain.com/callback"] 185 | default_redirect_uri = "https://mydomain.com/callback" 186 | explicit_auth_flows = [] 187 | generate_secret = false 188 | logout_urls = [] 189 | name = "test2" 190 | read_attributes = [] 191 | supported_identity_providers = [] 192 | write_attributes = [] 193 | refresh_token_validity = 30 194 | }, 195 | { 196 | allowed_oauth_flows = ["code", "implicit"] 197 | allowed_oauth_flows_user_pool_client = true 198 | allowed_oauth_scopes = ["email", "openid"] 199 | callback_urls = ["https://mydomain.com/callback"] 200 | default_redirect_uri = "https://mydomain.com/callback" 201 | explicit_auth_flows = ["CUSTOM_AUTH_FLOW_ONLY", "ADMIN_NO_SRP_AUTH"] 202 | generate_secret = false 203 | logout_urls = ["https://mydomain.com/logout"] 204 | name = "test3" 205 | read_attributes = ["email", "phone_number"] 206 | supported_identity_providers = [] 207 | write_attributes = ["email", "gender", "locale", ] 208 | refresh_token_validity = 30 209 | prevent_user_existence_errors = "ENABLED" 210 | } 211 | ] 212 | 213 | # user_group 214 | user_groups = [ 215 | { name = "mygroup1" 216 | description = "My group 1" 217 | }, 218 | { name = "mygroup2" 219 | description = "My group 2" 220 | }, 221 | ] 222 | 223 | # resource_servers 224 | resource_servers = [ 225 | { 226 | identifier = "https://mydomain.com" 227 | name = "mydomain" 228 | scope = [ 229 | { 230 | scope_name = "sample-scope-1" 231 | scope_description = "A sample Scope Description for mydomain.com" 232 | }, 233 | { 234 | scope_name = "sample-scope-2" 235 | scope_description = "Another sample Scope Description for mydomain.com" 236 | }, 237 | ] 238 | }, 239 | { 240 | identifier = "https://weather-read-app.com" 241 | name = "weather-read" 242 | scope = [ 243 | { 244 | scope_name = "weather.read" 245 | scope_description = "Read weather forecasts" 246 | } 247 | ] 248 | } 249 | ] 250 | 251 | # identity_providers 252 | identity_providers = [ 253 | { 254 | provider_name = "Google" 255 | provider_type = "Google" 256 | 257 | provider_details = { 258 | authorize_scopes = "email" 259 | client_id = "your client_id" 260 | client_secret = "your client_secret" 261 | } 262 | 263 | attribute_mapping = { 264 | email = "email" 265 | username = "sub" 266 | gender = "gender" 267 | } 268 | } 269 | ] 270 | 271 | # tags 272 | tags = { 273 | Owner = "infra" 274 | Environment = "production" 275 | Terraform = true 276 | } 277 | } 278 | ``` 279 | 280 | 281 | 282 | 283 | ## Requirements 284 | 285 | No requirements. 286 | 287 | ## Providers 288 | 289 | | Name | Version | 290 | |------|---------| 291 | | [aws](#provider\_aws) | 6.2.0 | 292 | 293 | ## Modules 294 | 295 | | Name | Source | Version | 296 | |------|--------|---------| 297 | | [aws\_cognito\_user\_pool\_complete\_example](#module\_aws\_cognito\_user\_pool\_complete\_example) | ../../ | n/a | 298 | 299 | ## Resources 300 | 301 | | Name | Type | 302 | |------|------| 303 | | [aws_kms_key.lambda-custom-sender](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 304 | 305 | ## Inputs 306 | 307 | | Name | Description | Type | Default | Required | 308 | |------|-------------|------|---------|:--------:| 309 | | [env](#input\_env) | n/a | `map(any)` | `{}` | no | 310 | 311 | ## Outputs 312 | 313 | No outputs. 314 | 315 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/complete/main.tf: -------------------------------------------------------------------------------- 1 | module "aws_cognito_user_pool_complete_example" { 2 | 3 | source = "../../" 4 | 5 | user_pool_name = "mypool_complete" 6 | alias_attributes = ["email", "phone_number"] 7 | auto_verified_attributes = ["email"] 8 | sms_authentication_message = "Your username is {username} and temporary password is {####}." 9 | sms_verification_message = "This is the verification message {####}." 10 | 11 | deletion_protection = "ACTIVE" 12 | 13 | # IMPORTANT: Enable schema ignore changes to prevent perpetual diffs with custom schemas 14 | # This is ESSENTIAL for this example since it uses custom schemas (schemas, string_schemas, number_schemas) 15 | # Without this, Terraform will attempt to recreate schemas on every plan, causing AWS API errors 16 | ignore_schema_changes = true 17 | 18 | mfa_configuration = "OPTIONAL" 19 | software_token_mfa_configuration = { 20 | enabled = true 21 | } 22 | 23 | admin_create_user_config = { 24 | email_message = "Dear {username}, your verification code is {####}." 25 | email_subject = "Here, your verification code baby" 26 | sms_message = "Your username is {username} and temporary password is {####}." 27 | } 28 | 29 | device_configuration = { 30 | challenge_required_on_new_device = true 31 | device_only_remembered_on_user_prompt = true 32 | } 33 | 34 | email_configuration = { 35 | email_sending_account = "DEVELOPER" 36 | reply_to_email_address = "email@mydomain.com" 37 | source_arn = "arn:aws:ses:us-east-1:123456789012:identity/myemail@mydomain.com" 38 | } 39 | 40 | lambda_config = { 41 | create_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:create_auth_challenge" 42 | custom_message = "arn:aws:lambda:us-east-1:123456789012:function:custom_message" 43 | define_auth_challenge = "arn:aws:lambda:us-east-1:123456789012:function:define_auth_challenge" 44 | post_authentication = "arn:aws:lambda:us-east-1:123456789012:function:post_authentication" 45 | post_confirmation = "arn:aws:lambda:us-east-1:123456789012:function:post_confirmation" 46 | pre_authentication = "arn:aws:lambda:us-east-1:123456789012:function:pre_authentication" 47 | pre_sign_up = "arn:aws:lambda:us-east-1:123456789012:function:pre_sign_up" 48 | pre_token_generation = "arn:aws:lambda:us-east-1:123456789012:function:pre_token_generation" 49 | user_migration = "arn:aws:lambda:us-east-1:123456789012:function:user_migration" 50 | verify_auth_challenge_response = "arn:aws:lambda:us-east-1:123456789012:function:verify_auth_challenge_response" 51 | kms_key_id = aws_kms_key.lambda-custom-sender.arn 52 | pre_token_generation_config = { 53 | lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:pre_token_generation_config" 54 | lambda_version = "V1_0" 55 | } 56 | custom_email_sender = { 57 | lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:custom_email_sender" 58 | lambda_version = "V1_0" 59 | } 60 | custom_sms_sender = { 61 | lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:custom_sms_sender" 62 | lambda_version = "V1_0" 63 | } 64 | } 65 | 66 | password_policy = { 67 | minimum_length = 10 68 | require_lowercase = false 69 | require_numbers = true 70 | require_symbols = true 71 | require_uppercase = true 72 | temporary_password_validity_days = 120 73 | password_history_size = 5 74 | } 75 | 76 | user_pool_add_ons = { 77 | advanced_security_mode = "ENFORCED" 78 | } 79 | 80 | # Enable advanced security for custom auth flows 81 | user_pool_add_ons_advanced_security_additional_flows = "AUDIT" 82 | 83 | verification_message_template = { 84 | default_email_option = "CONFIRM_WITH_CODE" 85 | } 86 | 87 | schemas = [ 88 | { 89 | attribute_data_type = "Boolean" 90 | developer_only_attribute = false 91 | mutable = true 92 | name = "available" 93 | required = false 94 | }, 95 | { 96 | attribute_data_type = "Boolean" 97 | developer_only_attribute = true 98 | mutable = true 99 | name = "registered" 100 | required = false 101 | } 102 | ] 103 | 104 | string_schemas = [ 105 | { 106 | attribute_data_type = "String" 107 | developer_only_attribute = false 108 | mutable = false 109 | name = "email" 110 | required = true 111 | 112 | string_attribute_constraints = { 113 | min_length = 7 114 | max_length = 15 115 | } 116 | }, 117 | { 118 | attribute_data_type = "String" 119 | developer_only_attribute = false 120 | mutable = false 121 | name = "gender" 122 | required = true 123 | 124 | string_attribute_constraints = { 125 | min_length = 7 126 | max_length = 15 127 | } 128 | }, 129 | ] 130 | 131 | number_schemas = [ 132 | { 133 | attribute_data_type = "Number" 134 | developer_only_attribute = true 135 | mutable = true 136 | name = "mynumber1" 137 | required = false 138 | 139 | number_attribute_constraints = { 140 | min_value = 2 141 | max_value = 6 142 | } 143 | }, 144 | { 145 | attribute_data_type = "Number" 146 | developer_only_attribute = true 147 | mutable = true 148 | name = "mynumber2" 149 | required = false 150 | 151 | number_attribute_constraints = { 152 | min_value = 2 153 | max_value = 6 154 | } 155 | }, 156 | ] 157 | 158 | # user_pool_domain 159 | domain = "mydomain-com" 160 | 161 | # clients 162 | clients = [ 163 | { 164 | allowed_oauth_flows = [] 165 | allowed_oauth_flows_user_pool_client = false 166 | allowed_oauth_scopes = [] 167 | callback_urls = ["https://mydomain.com/callback"] 168 | default_redirect_uri = "https://mydomain.com/callback" 169 | explicit_auth_flows = [] 170 | generate_secret = true 171 | logout_urls = [] 172 | name = "test1" 173 | read_attributes = ["email"] 174 | supported_identity_providers = [] 175 | write_attributes = [] 176 | access_token_validity = 1 177 | id_token_validity = 1 178 | refresh_token_validity = 60 179 | client_prevent_user_existence_errors = "ENABLED" 180 | token_validity_units = { 181 | access_token = "hours" 182 | id_token = "hours" 183 | refresh_token = "days" 184 | } 185 | ui_customization_css = file("custom_style.css") 186 | ui_customization_image_file = filebase64("logo.png") 187 | }, 188 | { 189 | allowed_oauth_flows = [] 190 | allowed_oauth_flows_user_pool_client = false 191 | allowed_oauth_scopes = [] 192 | callback_urls = ["https://mydomain.com/callback"] 193 | default_redirect_uri = "https://mydomain.com/callback" 194 | explicit_auth_flows = [] 195 | generate_secret = false 196 | logout_urls = [] 197 | name = "test2" 198 | read_attributes = [] 199 | supported_identity_providers = [] 200 | write_attributes = [] 201 | refresh_token_validity = 30 202 | refresh_token_rotation = { 203 | type = "rotate" 204 | retry_grace_period_seconds = 300 205 | } 206 | }, 207 | { 208 | allowed_oauth_flows = ["code", "implicit"] 209 | allowed_oauth_flows_user_pool_client = true 210 | allowed_oauth_scopes = ["email", "openid"] 211 | callback_urls = ["https://mydomain.com/callback"] 212 | default_redirect_uri = "https://mydomain.com/callback" 213 | explicit_auth_flows = ["CUSTOM_AUTH_FLOW_ONLY", "ADMIN_NO_SRP_AUTH"] 214 | generate_secret = false 215 | logout_urls = ["https://mydomain.com/logout"] 216 | name = "test3" 217 | read_attributes = ["email", "phone_number"] 218 | supported_identity_providers = [] 219 | write_attributes = ["email", "gender", "locale", ] 220 | refresh_token_validity = 30 221 | prevent_user_existence_errors = "ENABLED" 222 | } 223 | ] 224 | 225 | # user_group 226 | user_groups = [ 227 | { name = "mygroup1" 228 | description = "My group 1" 229 | }, 230 | { name = "mygroup2" 231 | description = "My group 2" 232 | }, 233 | ] 234 | 235 | # resource_servers 236 | resource_servers = [ 237 | { 238 | identifier = "https://mydomain.com" 239 | name = "mydomain" 240 | scope = [ 241 | { 242 | scope_name = "sample-scope-1" 243 | scope_description = "A sample Scope Description for mydomain.com" 244 | }, 245 | { 246 | scope_name = "sample-scope-2" 247 | scope_description = "Another sample Scope Description for mydomain.com" 248 | }, 249 | ] 250 | }, 251 | { 252 | identifier = "https://weather-read-app.com" 253 | name = "weather-read" 254 | scope = [ 255 | { 256 | scope_name = "weather.read" 257 | scope_description = "Read weather forecasts" 258 | } 259 | ] 260 | } 261 | ] 262 | 263 | # identity_providers 264 | # Note: For SAML providers, AWS automatically manages signing and encryption certificates 265 | # For OAuth providers, AWS may auto-manage OIDC discovery fields and handle sensitive values differently 266 | # The module includes lifecycle ignore_changes to prevent drift for both SAML and OAuth providers 267 | identity_providers = [ 268 | { 269 | provider_name = "Google" 270 | provider_type = "Google" 271 | 272 | provider_details = { 273 | authorize_scopes = "email" 274 | client_id = "your client_id" # This should be retrieved from AWS Secret Manager, otherwise Terraform will force an in-place replacement because it is treated as a sensitive value 275 | client_secret = "your client_secret" # This should be retrieved from AWS Secret Manager, otherwise Terraform will force an in-place replacement because it is treated as a sensitive value 276 | attributes_url_add_attributes = "true" 277 | authorize_url = "https://accounts.google.com/o/oauth2/v2/auth" 278 | oidc_issuer = "https://accounts.google.com" 279 | token_request_method = "POST" 280 | token_url = "https://www.googleapis.com/oauth2/v4/token" 281 | } 282 | 283 | attribute_mapping = { 284 | email = "email" 285 | username = "sub" 286 | gender = "gender" 287 | } 288 | } 289 | ] 290 | 291 | # tags 292 | tags = { 293 | Owner = "infra" 294 | Environment = "production" 295 | Terraform = true 296 | } 297 | } 298 | 299 | 300 | # KMS key for lambda custom sender config 301 | resource "aws_kms_key" "lambda-custom-sender" { 302 | description = "KMS key for lambda custom sender config" 303 | } 304 | --------------------------------------------------------------------------------