Skip to content

feat(aws-cognito): users, admin user management and pool-delete parity (C1) - #1378

Merged
NitinKumar004 merged 4 commits into
developmentfrom
feat/aws-cognito-c1
Sep 27, 2026
Merged

NitinKumar004 merged 4 commits into
developmentfrom
feat/aws-cognito-c1

Conversation

@NitinKumar004

@NitinKumar004 NitinKumar004 commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

C1 of the Cognito build-out: pool users, the admin user-management calls, AddCustomAttributes, and DeleteUserPool behaving like real Cognito.

What changed

Users (new)

  • AdminCreateUser. The user starts in FORCE_CHANGE_PASSWORD with a generated sub. TemporaryPassword is checked against the pool policy, and one is generated when it's left out. MessageAction SUPPRESS is accepted. RESEND re-invites a FORCE_CHANGE_PASSWORD user, and fails with UserNotFoundException for an unknown user or UnsupportedUserStateException for any other state.
  • AdminGetUser, AdminDeleteUser, AdminEnableUser, AdminDisableUser.
  • AdminUpdateUserAttributes and AdminDeleteUserAttributes, both validated against the pool schema. Unknown names give "Attributes did not conform to the schema: X: Attribute does not exist in the schema.". Immutable attributes and sub can't be changed. Changing email or phone_number without its _verified flag marks it unverified.
  • AdminSetUserPassword (Permanent → CONFIRMED, otherwise FORCE_CHANGE_PASSWORD) and AdminResetUserPassword (→ RESET_REQUIRED). A FORCE_CHANGE_PASSWORD user gets NotAuthorizedException "User password cannot be reset in the current state.".
  • ListUsers takes a Filter (= and ^= on the searchable attributes only), AttributesToGet (null returns everything, [] returns nothing), a Limit of at most 60, and a PaginationToken. It returns "Invalid search attribute: X" and "Error while parsing filter." for bad filters.
  • Pools using email or phone as the username: the username has to be an email or phone number ("Username should be an email."), the stored username is the sub, and lookups by email or phone work. Alias pools reject an email-shaped username and resolve verified aliases.
  • Sign-in values stay unique, and email is compared without case:
    • In username-attribute pools, a duplicate email or phone number fails with UsernameExistsException on create and AliasExistsException on update ("An account with the given email already exists.").
    • In alias pools, a verified email or phone number, or a preferred_username, that another user already holds fails with AliasExistsException on create and on update. Setting email_verified=true on a taken address counts too.
    • With ForceAliasCreation=true, AdminCreateUser moves an email or phone alias to the new user, and the previous holder keeps the value but is marked unverified.
    • An unverified email or phone number is not an alias, so it never conflicts.
  • Passwords are checked with the real InvalidPasswordException messages and stored only as a salted SHA-256 digest.
  • EstimatedNumberOfUsers in DescribeUserPool is computed from the actual users.

AddCustomAttributes (new)

  • Names get the custom: prefix and show up in DescribeUserPool SchemaAttributes. You can pass tier or custom:tier, and names must be 1 to 20 characters. A name that already exists is rejected with "Existing attribute already has name custom:x.", and a pool can have at most 50. Terraform uses this when a schema block is added to an existing aws_cognito_user_pool, and that update path failed before.

DeleteUserPool parity

  • It used to cascade-delete an attached domain. Real Cognito refuses instead: "User pool cannot be deleted. It has a domain configured that should be deleted first." The emulator now does the same.
  • It used to ignore DeletionProtection=ACTIVE. It now refuses with "The user pool cannot be deleted because deletion protection is activated. Deletion protection must be inactivated first."
  • Otherwise it still removes the pool's clients and tags, and now its users too.

Other

  • ListUserPools rejects MaxResults over 60.
  • A CreateUserPool Schema entry for a standard attribute, such as email with Required=true, now changes that attribute's Required flag and constraints. Before, the entry was dropped.
  • Users are included in the Cognito snapshot, so --persist and snapshot save/load keep them.
  • Regenerated docs/coverage.

Tests

  • Provider tests cover every op, their errors, email-username and alias pools, pagination past 60 users, ListUserPools with 61 pools, the delete blocks, the user cascade, and snapshot round-trip. The delete-block tests fail on the old code.
  • SDK wire tests cover the full user lifecycle with typed errors, the ListUsers and ListUserPools paginators, the domain delete block, and AddCustomAttributes. They also pin exact {} bodies for TagResource/UntagResource, and check that CloudTrail LookupEvents records CreateUserPool and AdminCreateUser from cognito-idp.amazonaws.com.

E2E against cloudemu serve

  • aws CLI: I ran every C1 op plus each error case above, the domain and deletion-protection delete blocks, and an email-username pool.
  • Terraform (aws provider 6.66.0) with aws_cognito_user_pool, aws_cognito_user_pool_domain, and aws_cognito_user_pool_client (generate_secret). Apply, then plan (clean). Then an update that changes the password policy, adds a custom schema attribute, and renames the client, followed by another plan (clean). Destroy then removes the domain before the pool. A second config sets alias_attributes and a standard email schema block with required = true. Apply and plan are clean, and destroy works.
  • Repros from review: the duplicate email in a username-attribute pool (update and create), the duplicate verified email in an alias pool, ForceAliasCreation, the email_verified flip, custom:t3, the 21-character name, and the email required override. Each one returns the error or result listed above.

Not in this PR

Groups and sign-up are C2. Tokens, JWKS and sign-in are C3. The username case-sensitivity setting is not modeled, so usernames are always case-sensitive.

Deferred:

  • A standard-attribute Schema entry can't make the attribute immutable. The wire decodes an omitted Mutable as false, so honoring it would turn email immutable for any SDK caller that leaves the field out. The fix is *bool for Mutable on the schema wire and driver types (server/aws/cognito/types.go schemaAttributeJSON, services/cognito/driver/types.go SchemaAttribute), and then applying it in overrideStandard (providers/aws/cognito/user_pools.go).
  • AdminCreateUser does not enforce required attributes, on purpose. The AWS guide ("Working with user attributes") says: "To create users and not give values for required attributes, administrators can use the AdminCreateUser API." The required check belongs to SignUp in C2.

C5 note: the password is stored as PasswordSalt and PasswordHash (hex salted SHA-256) on userRecord (providers/aws/cognito/users.go). SRP in C5 needs a verifier derived from the password at set time, so C5 should add a verifier field next to them (and a salt, if SRP's salt differs), filled in by hashPassword's callers: AdminCreateUser, RESEND and AdminSetUserPassword. It should not replace the digest.

}

func digest(salt, pw string) string {
sum := sha256.Sum256([]byte(salt + pw))
@NitinKumar004
NitinKumar004 marked this pull request as ready for review September 27, 2026 16:46
@NitinKumar004
NitinKumar004 merged commit 7cb6ec1 into development Sep 27, 2026
22 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants