feat(cognito): fixed pool/client ids and seeded authenticator secrets

Merged
ZE
14d ago
+1625-110

Summary

Closes #2702.

  • CreateUserPool takes the pool id from a _custom_id_ UserPoolTags entry and CreateUserPoolClient takes the client id from a _custom_id_:<id> ClientName, the same convention as LocalStack. The pool id must be <region>_<alphanumeric id> in the request's region (the ARN, issuer and discovery URLs read the region back out of the id) and at most 55 characters; the client id must match the ClientIdType pattern ([\w+]+, 1-128). An id already used in any account (the JWKS, discovery and OAuth2 routes look pools and clients up across accounts) fails with InvalidParameterException. Without the tag or prefix nothing changes. The tag stays in the pool's tags and the client name is stored as sent.
  • POST /_fakecloud/cognito/software-token with {"userPoolId", "username", "secretCode"} gives the user a verified TOTP secret, the state AssociateSoftwareToken + VerifySoftwareToken leave behind. 400 for a secret that is not base32, 404 for an unknown pool or user. Like the other state-changing Cognito endpoints that need it (authorization-codes), it writes the snapshot through, so the secret survives a restart in persistent mode.
  • Rust SDK: cognito().set_software_token(...) with SetSoftwareTokenRequest / SetSoftwareTokenResponse. I didn't add helpers to the other six SDKs; happy to add them here if you want them in this PR.
  • Docs: docs/services/cognito.md and docs/reference/introspection.md.

Usage from an init script:

aws cognito-idp create-user-pool --pool-name local --user-pool-tags _custom_id_=us-east-1_Local
aws cognito-idp create-user-pool-client --user-pool-id us-east-1_Local --client-name _custom_id_:localclient
curl -X POST http://localhost:4566/_fakecloud/cognito/software-token \
  -H 'Content-Type: application/json' \
  -d '{"userPoolId":"us-east-1_Local","username":"dev","secretCode":"JBSWY3DPEHPK3PXP"}'

Test plan

  • Unit (fakecloud-cognito, 419 pass): custom pool id taken, reused and invalid ids (other region, empty, non-alphanumeric, missing region) rejected; custom client id taken, reused and invalid ids rejected; set_software_token stores a verified secret and rejects bad secrets and unknown pools/users.
  • E2E cognito_custom_ids_and_seeded_software_token: pool and client created with the AWS CLI using the fixed ids, the duplicate pool rejected, secret seeded through the endpoint (plus 400/404 cases), then AdminInitiateAuth on an MFA ON pool gets SOFTWARE_TOKEN_MFA and a code from the seeded secret returns tokens.
  • E2E persistence_custom_ids_and_seeded_software_token: the same sign-in works after a restart, with seeding as the last write before it.
  • cognito, cognito_persistence, cognito_authorize, cognito_dispatch, cfn_cognito, cloudformation_cognito e2e pass; fakecloud-sdk tests pass; cargo fmt --all --check and scripts/check-doc-counts.sh clean.
  • cargo clippy --workspace --all-targets -- -D warnings is clean for the changed crates. With my local clippy (1.96), three existing nonminimal_bool errors in fakecloud-ec2 (image.rs:294, snapshot.rs:198, volume.rs:456) stop the workspace run; they are not touched here.

Summary by cubic

Lets local setups create Cognito pools and clients with predetermined ids and seed verified authenticator-app secrets, so sign-in flows can be scripted without inspecting generated ids.

  • CreateUserPool takes the pool id from a _custom_id_ tag (<region>_<alphanumeric id> in the request's region) and CreateUserPoolClient from a _custom_id_:<id> client name, matching LocalStack's convention; CloudFormation UserPool/UserPoolClient resources honor the same convention. Ids already in use in any account are rejected with InvalidParameterException (a duplicate pool is rejected before RSA keygen), an id is fixed at creation, and without the tag or prefix behavior is unchanged.
  • New POST /_fakecloud/cognito/software-token gives a user a verified TOTP secret (400 for non-base32 secrets, including non-ASCII; 404 for unknown pools/users), resolving alias usernames on UsernameAttributes pools; it persists through restarts in persistent mode.
  • Deleting a pool or client now purges all related state — tokens, sessions, authorization codes, tags, UI/risk/log config, brandings, terms, WebAuthn credentials, auth events and PreTokenGeneration invocations — so a resource recreated under the same custom id starts clean; a pool with DeletionProtection ACTIVE cannot be deleted.
  • Adds set_software_token to all seven SDKs and documents both features.