diff --git a/.doc_gen/metadata/cognito-identity-provider_metadata.yaml b/.doc_gen/metadata/cognito-identity-provider_metadata.yaml
index 4194c6d20e3..c53fb773998 100644
--- a/.doc_gen/metadata/cognito-identity-provider_metadata.yaml
+++ b/.doc_gen/metadata/cognito-identity-provider_metadata.yaml
@@ -753,6 +753,14 @@ cognito-identity-provider_DeleteUser:
snippet_tags:
- gov2.cognito-identity-provider.CognitoActions.struct
- gov2.cognito-identity-provider.DeleteUser
+ JavaScript:
+ versions:
+ - sdk_version: 3
+ github: javascriptv3/example_code/cross-services/wkflw-pools-triggers
+ excerpts:
+ - description:
+ snippet_tags:
+ - javascript.v3.cognito-idp.actions.DeleteUser
services:
cognito-identity-provider: {DeleteUser}
cognito-identity-provider_CreateUserPool:
@@ -793,6 +801,15 @@ cognito-identity-provider_UpdateUserPool:
snippet_tags:
- gov2.cognito-identity-provider.CognitoActions.struct
- gov2.cognito-identity-provider.UpdateUserPool
+ JavaScript:
+ versions:
+ - sdk_version: 3
+ github: javascriptv3/example_code/cross-services/wkflw-pools-triggers
+ sdkguide:
+ excerpts:
+ - description:
+ snippet_tags:
+ - javascript.v3.cognito-idp.actions.UpdateUserPool
services:
cognito-identity-provider: {UpdateUserPool}
cognito-identity-provider_ForgotPassword:
diff --git a/.doc_gen/metadata/cross_metadata.yaml b/.doc_gen/metadata/cross_metadata.yaml
index cd51d9f6e0e..f350838f387 100644
--- a/.doc_gen/metadata/cross_metadata.yaml
+++ b/.doc_gen/metadata/cross_metadata.yaml
@@ -822,6 +822,38 @@ cross_CognitoAutoConfirmUser:
- description: Clean up resources.
snippet_tags:
- gov2.cognito-identity-provider.Resources.complete
+ JavaScript:
+ versions:
+ - sdk_version: 3
+ github: javascriptv3/example_code/cross-services/wkflw-pools-triggers
+ sdk_guide:
+ excerpts:
+ - description: |
+ Configure an interactive "Scenario" run. The JavaScript (v3) examples
+ share a Scenario runner to streamline complex examples. The complete
+ source code is on GitHub.
+ snippet_tags:
+ - javascript.v3.wkflw.pools-triggers.index
+ - description: |
+ This Scenario demonstrates auto-confirming a known user.
+ It orchestrates the example steps.
+ snippet_tags:
+ - javascript.v3.wkflw.pools-triggers.AutoConfirm
+ - description: These are steps that are shared with other Scenarios.
+ snippet_tags:
+ - javascript.v3.wkflw.pools-triggers.common
+ - description: A handler for the PreSignUp trigger with a &LAM; function.
+ snippet_tags:
+ - javascript.v3.wkflw.pools-triggers.handler.AutoConfirm
+ - description: Module of &CWL; actions.
+ snippet_files:
+ - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cloudwatch-logs-actions.js
+ - description: Module of &COG; actions.
+ snippet_files:
+ - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js
+ - description: Module of &DDB; actions.
+ snippet_files:
+ - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/dynamodb-actions.js
services:
cognito-identity-provider: {UpdateUserPool, SignUp, InitiateAuth, DeleteUser}
lambda: {}
diff --git a/javascriptv3/biome.json b/javascriptv3/biome.json
index d23d6c62596..6735ea6f258 100644
--- a/javascriptv3/biome.json
+++ b/javascriptv3/biome.json
@@ -13,7 +13,9 @@
"./example_code/kinesis/kinesis-cdk",
"./example_code/medical-imaging/scenarios/health-image-sets/pixel-data-verification/openjphjs/openjphjs.js",
"./example_code/cross-services/textract-react",
- "**/dist"
+ "**/dist",
+ "**/cdk.out",
+ "cdk.json"
]
},
"formatter": {
diff --git a/javascriptv3/example_code/cognito-identity-provider/README.md b/javascriptv3/example_code/cognito-identity-provider/README.md
index 009ef4b4d54..fa0787fde32 100644
--- a/javascriptv3/example_code/cognito-identity-provider/README.md
+++ b/javascriptv3/example_code/cognito-identity-provider/README.md
@@ -44,11 +44,13 @@ Code excerpts that show you how to call individual service functions.
- [AssociateSoftwareToken](actions/associate-software-token.js#L9)
- [ConfirmDevice](actions/confirm-device.js#L9)
- [ConfirmSignUp](actions/confirm-sign-up.js#L9)
+- [DeleteUser](../cross-services/wkflw-pools-triggers/actions/cognito-actions.js#L122)
- [InitiateAuth](actions/initiate-auth.js#L10)
- [ListUsers](actions/list-users.js#L9)
- [ResendConfirmationCode](actions/resend-confirmation-code.js#L9)
- [RespondToAuthChallenge](actions/respond-to-auth-challenge.js#L10)
- [SignUp](actions/sign-up.js#L9)
+- [UpdateUserPool](../cross-services/wkflw-pools-triggers/actions/cognito-actions.js#L13)
- [VerifySoftwareToken](actions/verify-software-token.js#L9)
### Scenarios
@@ -56,6 +58,7 @@ Code excerpts that show you how to call individual service functions.
Code examples that show you how to accomplish a specific task by calling multiple
functions within the same service.
+- [Automatically confirm known users with a Lambda function](../cross-services/wkflw-pools-triggers/index.js)
- [Sign up a user with a user pool that requires MFA](actions/admin-initiate-auth.js)
@@ -104,6 +107,22 @@ node ./hello.js
```
+#### Automatically confirm known users with a Lambda function
+
+This example shows you how to automatically confirm known Amazon Cognito users with a Lambda function.
+
+- Configure a user pool to call a Lambda function for the PreSignUp trigger.
+- Sign up a user with Amazon Cognito.
+- The Lambda function scans a DynamoDB table and automatically confirms known users.
+- Sign in as the new user, then clean up resources.
+
+
+
+
+
+
+
+
#### Sign up a user with a user pool that requires MFA
This example shows you how to do the following:
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/README.md b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/README.md
new file mode 100644
index 00000000000..1cbf7d4472d
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/README.md
@@ -0,0 +1,99 @@
+# Customize Amazon Cognito authentication behavior with Lambda functions
+
+## Overview
+
+This example shows how to use AWS SDKs to customize Amazon Cognito authentication behavior. You can configure
+your Amazon Cognito user pool to automatically invoke AWS Lambda functions at various points in the authentication
+process, such as before sign-up, during sign-in, and after authentication.
+
+There are three workflows demonstrated by this example:
+
+* Automatically confirm and verify the email of known users by using a pre sign-up trigger.
+* [Not yet implemented] Automatically add known users at sign-in by using a migrate user trigger.
+* [Not yet implemented] Write custom information to an Amazon DynamoDB table after users are authenticated by using a post authentication trigger.
+
+These workflows are described in more detail in the main [README](../../../../workflows/user_pools_and_lambda_triggers/README.md)
+for these examples.
+
+## Automatically confirm known users
+
+A [pre sign-up Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html)
+is invoked when a user starts the sign-up process and lets your Lambda function
+take action before Amazon Cognito adds the user to the user pool.
+
+## Automatically migrate known users
+
+A [migrate user Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html)
+is invoked when a user doesn't exist in the user pool at sign-in with a password.
+After the Lambda function returns successfully, Amazon Cognito creates the user in the user pool.
+
+## Write custom activity after authentication
+
+A [post authentication Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-authentication.html)
+is invoked after signing in a user, so you can add custom logic after Amazon Cognito authenticates the user.
+
+## ⚠ Important
+
+* Running this code might result in charges to your AWS account.
+* Running the tests might result in charges to your AWS account.
+* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).
+* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services).
+
+## Run the examples
+
+### Prerequisites
+
+For general prerequisites, see the [README](../../../README.md#prerequisites) in the `javascriptv3` folder.
+
+### Setup
+
+This example deploys several resources by using an AWS CloudFormation stack. This stack
+deploys the following resources:
+
+* An Amazon DynamoDB table named `doc-example-custom-users` that has a `UserEmail` primary key.
+ This table functions as an external user store.
+* An Amazon Cognito user pool that requires an email, sends an email with verification code
+ when a new user is added, does not require MFA, and allows account recovery with a verified email.
+* An Amazon Cognito client application. This is required for client calls to sign-up and
+ authentication users.
+* An AWS Identity and Access Management (IAM) role that can be assumed by Lambda.
+ This role grants permission to Lambda to read from and write to the DynamoDB table and
+ write to CloudWatch Logs.
+
+### Deploy AWS resources
+
+The AWS resources for this example are deployed by using the AWS Cloud Development Kit (AWS CDK).
+
+To install the AWS CDK, follow the instructions in the
+[Developer Guide](https://docs.aws.amazon.com/cdk/v2/guide/home.html).
+
+Deploy resources at a command prompt from the [cdk](./cdk/) folder:
+
+```
+npm install
+cdk deploy
+```
+
+### Instructions
+
+Run `./index --help` for instructions on running a scenario.
+
+### Cleanup
+
+Delete resources deployed for this example by deleting the stack.
+
+Delete the stack at a command prompt from the [cdk](./cdk/) folder:
+
+```
+cdk destroy
+```
+
+## Additional resources
+
+- [Amazon Cognito Identity Provider Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html)
+- [Amazon Cognito Identity Provider API Reference](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/Welcome.html)
+- [SDK for JavaScript V3 Amazon Cognito Identity Provider reference](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/cognito-identity-provider/)
+
+---
+
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cloudwatch-logs-actions.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cloudwatch-logs-actions.js
new file mode 100644
index 00000000000..7f84b829384
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cloudwatch-logs-actions.js
@@ -0,0 +1,69 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ CloudWatchLogsClient,
+ GetLogEventsCommand,
+ OrderBy,
+ paginateDescribeLogStreams,
+} from "@aws-sdk/client-cloudwatch-logs";
+
+/**
+ * Get the latest log stream for a Lambda function.
+ * @param {{ functionName: string, region: string }} config
+ * @returns {Promise<[import("@aws-sdk/client-cloudwatch-logs").LogStream | null, unknown]>}
+ */
+export const getLatestLogStreamForLambda = async ({ functionName, region }) => {
+ try {
+ const logGroupName = `/aws/lambda/${functionName}`;
+ const cwlClient = new CloudWatchLogsClient({ region });
+ const paginator = paginateDescribeLogStreams(
+ { client: cwlClient },
+ {
+ descending: true,
+ limit: 1,
+ orderBy: OrderBy.LastEventTime,
+ logGroupName,
+ },
+ );
+
+ for await (const page of paginator) {
+ return [page.logStreams[0], null];
+ }
+ } catch (err) {
+ return [null, err];
+ }
+};
+
+/**
+ * Get the log events for a Lambda function's log stream.
+ * @param {{
+ * functionName: string,
+ * logStreamName: string,
+ * eventCount: number,
+ * region: string
+ * }} config
+ * @returns {Promise<[import("@aws-sdk/client-cloudwatch-logs").OutputLogEvent[] | null, unknown]>}
+ */
+export const getLogEvents = async ({
+ functionName,
+ logStreamName,
+ eventCount,
+ region,
+}) => {
+ try {
+ const cwlClient = new CloudWatchLogsClient({ region });
+ const logGroupName = `/aws/lambda/${functionName}`;
+ const response = await cwlClient.send(
+ new GetLogEventsCommand({
+ logStreamName: logStreamName,
+ limit: eventCount,
+ logGroupName: logGroupName,
+ }),
+ );
+
+ return [response.events, null];
+ } catch (err) {
+ return [null, err];
+ }
+};
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js
new file mode 100644
index 00000000000..15d2d969ea2
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js
@@ -0,0 +1,140 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+ AdminGetUserCommand,
+ CognitoIdentityProviderClient,
+ DeleteUserCommand,
+ InitiateAuthCommand,
+ SignUpCommand,
+ UpdateUserPoolCommand,
+} from "@aws-sdk/client-cognito-identity-provider";
+
+// snippet-start:[javascript.v3.cognito-idp.actions.UpdateUserPool]
+/**
+ * Connect a Lambda function to the PreSignUp trigger for a Cognito user pool
+ * @param {{ region: string, userPoolId: string, handlerArn: string }} config
+ * @returns {Promise<[import("@aws-sdk/client-cognito-identity-provider").UpdateUserPoolCommandOutput | null, unknown]>}
+ */
+export const addPreSignUpHandler = async ({
+ region,
+ userPoolId,
+ handlerArn,
+}) => {
+ try {
+ const cognitoClient = new CognitoIdentityProviderClient({
+ region,
+ });
+
+ const command = new UpdateUserPoolCommand({
+ UserPoolId: userPoolId,
+ LambdaConfig: {
+ PreSignUp: handlerArn,
+ },
+ });
+
+ const response = await cognitoClient.send(command);
+ return [response, null];
+ } catch (err) {
+ return [null, err];
+ }
+};
+// snippet-end:[javascript.v3.cognito-idp.actions.UpdateUserPool]
+
+/**
+ * Attempt to register a user to a user pool with a given username and password.
+ * @param {{
+ * region: string,
+ * userPoolClientId: string,
+ * username: string,
+ * email: string,
+ * password: string
+ * }} config
+ * @returns {Promise<[import("@aws-sdk/client-cognito-identity-provider").SignUpCommandOutput | null, unknown]>}
+ */
+export const signUpUser = async ({
+ region,
+ userPoolClientId,
+ username,
+ email,
+ password,
+}) => {
+ try {
+ const cognitoClient = new CognitoIdentityProviderClient({
+ region,
+ });
+
+ const response = await cognitoClient.send(
+ new SignUpCommand({
+ ClientId: userPoolClientId,
+ Username: username,
+ Password: password,
+ UserAttributes: [{ Name: "email", Value: email }],
+ }),
+ );
+ return [response, null];
+ } catch (err) {
+ return [null, err];
+ }
+};
+
+/**
+ * Sign in a user to Amazon Cognito using a username and password authentication flow.
+ * @param {{ region: string, clientId: string, username: string, password: string }} config
+ * @returns {Promise<[import("@aws-sdk/client-cognito-identity-provider").InitiateAuthCommandOutput | null, unknown]>}
+ */
+export const signIn = async ({ region, clientId, username, password }) => {
+ try {
+ const cognitoClient = new CognitoIdentityProviderClient({ region });
+ const response = await cognitoClient.send(
+ new InitiateAuthCommand({
+ AuthFlow: "USER_PASSWORD_AUTH",
+ ClientId: clientId,
+ AuthParameters: { USERNAME: username, PASSWORD: password },
+ }),
+ );
+ return [response, null];
+ } catch (err) {
+ return [null, err];
+ }
+};
+
+/**
+ * Retrieve an existing user from a user pool.
+ * @param {{ region: string, userPoolId: string, username: string }} config
+ * @returns {Promise<[import("@aws-sdk/client-cognito-identity-provider").AdminGetUserCommandOutput | null, unknown]>}
+ */
+export const getUser = async ({ region, userPoolId, username }) => {
+ try {
+ const cognitoClient = new CognitoIdentityProviderClient({ region });
+ const response = await cognitoClient.send(
+ new AdminGetUserCommand({
+ UserPoolId: userPoolId,
+ Username: username,
+ }),
+ );
+ return [response, null];
+ } catch (err) {
+ return [null, err];
+ }
+};
+
+// snippet-start:[javascript.v3.cognito-idp.actions.DeleteUser]
+/**
+ * Delete the signed-in user. Useful for allowing a user to delete their
+ * own profile.
+ * @param {{ region: string, accessToken: string }} config
+ * @returns {Promise<[import("@aws-sdk/client-cognito-identity-provider").DeleteUserCommandOutput | null, unknown]>}
+ */
+export const deleteUser = async ({ region, accessToken }) => {
+ try {
+ const client = new CognitoIdentityProviderClient({ region });
+ const response = await client.send(
+ new DeleteUserCommand({ AccessToken: accessToken }),
+ );
+ return [response, null];
+ } catch (err) {
+ return [null, err];
+ }
+};
+// snippet-end:[javascript.v3.cognito-idp.actions.DeleteUser]
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/dynamodb-actions.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/dynamodb-actions.js
new file mode 100644
index 00000000000..32c2608a73d
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/dynamodb-actions.js
@@ -0,0 +1,34 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
+import {
+ BatchWriteCommand,
+ DynamoDBDocumentClient,
+} from "@aws-sdk/lib-dynamodb";
+
+/**
+ * Populate a DynamoDB table with provide items.
+ * @param {{ region: string, tableName: string, items: Record[] }} config
+ * @returns {Promise<[import("@aws-sdk/lib-dynamodb").BatchWriteCommandOutput | null, unknown]>}
+ */
+export const populateTable = async ({ region, tableName, items }) => {
+ try {
+ const ddbClient = new DynamoDBClient({ region });
+ const docClient = DynamoDBDocumentClient.from(ddbClient);
+ const response = await docClient.send(
+ new BatchWriteCommand({
+ RequestItems: {
+ [tableName]: items.map((item) => ({
+ PutRequest: {
+ Item: item,
+ },
+ })),
+ },
+ }),
+ );
+ return [response, null];
+ } catch (err) {
+ return [null, err];
+ }
+};
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.gitignore b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.gitignore
new file mode 100644
index 00000000000..f60797b6a91
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.gitignore
@@ -0,0 +1,8 @@
+*.js
+!jest.config.js
+*.d.ts
+node_modules
+
+# CDK asset staging directory
+.cdk.staging
+cdk.out
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.npmignore b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.npmignore
new file mode 100644
index 00000000000..c1d6d45dcf3
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.npmignore
@@ -0,0 +1,6 @@
+*.ts
+!*.d.ts
+
+# CDK asset staging directory
+.cdk.staging
+cdk.out
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/README.md b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/README.md
new file mode 100644
index 00000000000..9315fe5b9fc
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/README.md
@@ -0,0 +1,14 @@
+# Welcome to your CDK TypeScript project
+
+This is a blank project for CDK development with TypeScript.
+
+The `cdk.json` file tells the CDK Toolkit how to execute your app.
+
+## Useful commands
+
+* `npm run build` compile typescript to js
+* `npm run watch` watch for changes and compile
+* `npm run test` perform the jest unit tests
+* `npx cdk deploy` deploy this stack to your default AWS account/region
+* `npx cdk diff` compare deployed stack with current state
+* `npx cdk synth` emits the synthesized CloudFormation template
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/bin/cdk.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/bin/cdk.ts
new file mode 100644
index 00000000000..d69e111e17a
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/bin/cdk.ts
@@ -0,0 +1,21 @@
+#!/usr/bin/env node
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import "source-map-support/register";
+import * as cdk from "aws-cdk-lib";
+import { PoolsAndTriggersStack } from "../lib/stack";
+
+const app = new cdk.App();
+new PoolsAndTriggersStack(app, "PoolsAndTriggersStack", {
+ /* If you don't specify 'env', this stack will be environment-agnostic.
+ * Account/Region-dependent features and context lookups will not work,
+ * but a single synthesized template can be deployed anywhere. */
+ /* Uncomment the next line to specialize this stack for the AWS Account
+ * and Region that are implied by the current CLI configuration. */
+ // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
+ /* Uncomment the next line if you know exactly what Account and Region you
+ * want to deploy the stack to. */
+ // env: { account: '123456789012', region: 'us-east-1' },
+ /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
+});
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/cdk.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/cdk.json
new file mode 100644
index 00000000000..75c84117e08
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/cdk.json
@@ -0,0 +1,72 @@
+{
+ "app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
+ "watch": {
+ "include": [
+ "**"
+ ],
+ "exclude": [
+ "README.md",
+ "cdk*.json",
+ "**/*.d.ts",
+ "**/*.js",
+ "tsconfig.json",
+ "package*.json",
+ "yarn.lock",
+ "node_modules",
+ "test"
+ ]
+ },
+ "context": {
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
+ "@aws-cdk/core:checkSecretUsage": true,
+ "@aws-cdk/core:target-partitions": [
+ "aws",
+ "aws-cn"
+ ],
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
+ "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
+ "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
+ "@aws-cdk/aws-iam:minimizePolicies": true,
+ "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
+ "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
+ "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
+ "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
+ "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
+ "@aws-cdk/core:enablePartitionLiterals": true,
+ "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
+ "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
+ "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
+ "@aws-cdk/aws-route53-patters:useCertificate": true,
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
+ "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
+ "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
+ "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
+ "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
+ "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
+ "@aws-cdk/aws-redshift:columnId": true,
+ "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
+ "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
+ "@aws-cdk/aws-kms:aliasNameRef": true,
+ "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
+ "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
+ "@aws-cdk/aws-efs:denyAnonymousAccess": true,
+ "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
+ "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
+ "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
+ "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
+ "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
+ "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
+ "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
+ "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
+ "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
+ "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
+ "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
+ "@aws-cdk/aws-eks:nodegroupNameAttribute": true,
+ "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
+ "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
+ "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
+ "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false
+ }
+}
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/base.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/base.ts
new file mode 100644
index 00000000000..95e42d0a725
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/base.ts
@@ -0,0 +1,70 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { CfnOutput, RemovalPolicy } from "aws-cdk-lib";
+import { Construct } from "constructs";
+import * as dynamo from "aws-cdk-lib/aws-dynamodb";
+import * as cognito from "aws-cdk-lib/aws-cognito";
+import * as iam from "aws-cdk-lib/aws-iam";
+
+export class PoolsAndTriggersBase extends Construct {
+ readonly tableName: string;
+ readonly userPoolId: string;
+ readonly userPoolArn: string;
+ readonly userPoolClientId: string;
+ readonly lambdaRole: iam.Role;
+
+ constructor(scope: Construct, id: string) {
+ super(scope, id);
+ this.tableName = "doc-example-custom-users";
+
+ const table = new dynamo.Table(this, "doc-example-custom-users", {
+ tableName: this.tableName,
+ partitionKey: { name: "UserEmail", type: dynamo.AttributeType.STRING },
+ removalPolicy: RemovalPolicy.DESTROY,
+ });
+
+ const pool = new cognito.UserPool(this, "doc-example-pools-and-triggers", {
+ userPoolName: "doc-examples-pools-and-triggers",
+ selfSignUpEnabled: true,
+ standardAttributes: { email: { mutable: false, required: true } },
+ accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
+ removalPolicy: RemovalPolicy.DESTROY,
+ deletionProtection: false,
+ });
+ const poolClient = new cognito.UserPoolClient(
+ this,
+ "doc-example-pools-and-triggers-client",
+ {
+ userPool: pool,
+ userPoolClientName: "doc-example-pools-and-triggers-client",
+ authFlows: { userPassword: true },
+ },
+ );
+ this.userPoolId = pool.userPoolId;
+ this.userPoolArn = pool.userPoolArn;
+ this.userPoolClientId = poolClient.userPoolClientId;
+
+ this.lambdaRole = new iam.Role(
+ this,
+ "doc-example-pools-and-triggers-lambda-role",
+ {
+ roleName: "doc-example-pools-and-triggers-lambda-role",
+ assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
+ managedPolicies: [
+ iam.ManagedPolicy.fromAwsManagedPolicyName(
+ "service-role/AWSLambdaBasicExecutionRole",
+ ),
+ ],
+ },
+ );
+ table.grantReadWriteData(this.lambdaRole);
+ }
+
+ outputs(scope: Construct) {
+ new CfnOutput(scope, "TableName", { value: this.tableName });
+ new CfnOutput(scope, "UserPoolId", { value: this.userPoolId });
+ new CfnOutput(scope, "UserPoolArn", { value: this.userPoolArn });
+ new CfnOutput(scope, "UserPoolClientId", { value: this.userPoolClientId });
+ }
+}
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.autoConfirmHandler.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.autoConfirmHandler.ts
new file mode 100644
index 00000000000..b4f8106ff67
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.autoConfirmHandler.ts
@@ -0,0 +1,76 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// snippet-start:[javascript.v3.wkflw.pools-triggers.handler.AutoConfirm]
+import type { PreSignUpTriggerEvent, Handler } from "aws-lambda";
+import type { UserRepository } from "./user-repository";
+import { DynamoDBUserRepository } from "./user-repository";
+
+export class PreSignUpHandler {
+ private userRepository: UserRepository;
+
+ constructor(userRepository: UserRepository) {
+ this.userRepository = userRepository;
+ }
+
+ private isPreSignUpTriggerSource(event: PreSignUpTriggerEvent): boolean {
+ return event.triggerSource === "PreSignUp_SignUp";
+ }
+
+ private getEventUserEmail(event: PreSignUpTriggerEvent): string {
+ return event.request.userAttributes.email;
+ }
+
+ async handlePreSignUpTriggerEvent(
+ event: PreSignUpTriggerEvent,
+ ): Promise {
+ console.log(
+ `Received presignup from ${event.triggerSource} for user '${event.userName}'`,
+ );
+
+ if (!this.isPreSignUpTriggerSource(event)) {
+ return event;
+ }
+
+ const eventEmail = this.getEventUserEmail(event);
+ console.log(`Looking up email ${eventEmail}.`);
+ const storedUserInfo =
+ await this.userRepository.getUserInfoByEmail(eventEmail);
+
+ if (!storedUserInfo) {
+ console.log(
+ `Email ${eventEmail} not found. Email verification is required.`,
+ );
+ return event;
+ }
+
+ if (storedUserInfo.UserName !== event.userName) {
+ console.log(
+ `UserEmail ${eventEmail} found, but stored UserName '${storedUserInfo.UserName}' does not match supplied UserName '${event.userName}'. Verification is required.`,
+ );
+ } else {
+ console.log(
+ `UserEmail ${eventEmail} found with matching UserName ${storedUserInfo.UserName}. User is confirmed.`,
+ );
+ event.response.autoConfirmUser = true;
+ event.response.autoVerifyEmail = true;
+ }
+ return event;
+ }
+}
+
+const createPreSignUpHandler = (): PreSignUpHandler => {
+ const tableName = process.env.TABLE_NAME;
+ if (!tableName) {
+ throw new Error("TABLE_NAME environment variable is not set");
+ }
+
+ const userRepository = new DynamoDBUserRepository(tableName);
+ return new PreSignUpHandler(userRepository);
+};
+
+export const handler: Handler = async (event: PreSignUpTriggerEvent) => {
+ const preSignUpHandler = createPreSignUpHandler();
+ return preSignUpHandler.handlePreSignUpTriggerEvent(event);
+};
+// snippet-end:[javascript.v3.wkflw.pools-triggers.handler.AutoConfirm]
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.ts
new file mode 100644
index 00000000000..cec1696724c
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.ts
@@ -0,0 +1,40 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import type { StackProps } from "aws-cdk-lib";
+import { CfnOutput, Stack } from "aws-cdk-lib";
+import { ServicePrincipal } from "aws-cdk-lib/aws-iam";
+import { Runtime } from "aws-cdk-lib/aws-lambda";
+import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
+import type { Construct } from "constructs";
+
+import { PoolsAndTriggersBase } from "./base";
+
+export class PoolsAndTriggersStack extends Stack {
+ functions: Record;
+ poolsAndTriggersBase = new PoolsAndTriggersBase(this, "PoolsAndTriggersBase");
+ cognitoPrincipal = new ServicePrincipal("cognito-idp.amazonaws.com");
+
+ constructor(scope: Construct, id: string, props?: StackProps) {
+ super(scope, id, props);
+ this.createFunction("AutoConfirmHandler");
+ this.poolsAndTriggersBase.outputs(this);
+ }
+
+ createFunction(name: string): NodejsFunction {
+ const fn = new NodejsFunction(this, name, {
+ // An "entry" property is optional. By default, NodejsFunction
+ // will look for a file named `${this.stackName}.${name}.ts`
+ // entry: "",
+ environment: {
+ TABLE_NAME: this.poolsAndTriggersBase.tableName,
+ },
+ role: this.poolsAndTriggersBase.lambdaRole,
+ runtime: Runtime.NODEJS_20_X,
+ });
+ fn.grantInvoke(this.cognitoPrincipal);
+ new CfnOutput(this, `${name}Name`, { value: fn.functionName });
+ new CfnOutput(this, `${name}Arn`, { value: fn.functionArn });
+ return fn;
+ }
+}
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/user-repository.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/user-repository.ts
new file mode 100644
index 00000000000..3dc74c56b13
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/user-repository.ts
@@ -0,0 +1,35 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
+import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
+
+export interface UserRepository {
+ getUserInfoByEmail(
+ userEmail: string,
+ ): Promise | undefined>;
+}
+
+export class DynamoDBUserRepository implements UserRepository {
+ private ddbDocClient: DynamoDBDocumentClient;
+ private tableName: string;
+
+ constructor(tableName: string) {
+ const dynamoDBClient = new DynamoDBClient({});
+ const ddbDocClient = DynamoDBDocumentClient.from(dynamoDBClient);
+ this.ddbDocClient = ddbDocClient;
+ this.tableName = tableName;
+ }
+
+ async getUserInfoByEmail(
+ userEmail: string,
+ ): Promise | undefined> {
+ const getItemCommand = new GetCommand({
+ TableName: this.tableName,
+ Key: { UserEmail: userEmail },
+ });
+
+ const { Item } = await this.ddbDocClient.send(getItemCommand);
+ return Item;
+ }
+}
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json
new file mode 100644
index 00000000000..1811921dfff
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@aws-doc-sdk-examples/pools-triggers-cdk",
+ "version": "0.1.0",
+ "bin": {
+ "cdk": "bin/cdk.js"
+ },
+ "scripts": {
+ "build": "tsc",
+ "watch": "tsc -w",
+ "test": "vitest run **/*.unit.test.ts",
+ "cdk": "cdk"
+ },
+ "devDependencies": {
+ "@aws-sdk/client-dynamodb": "^3.682.0",
+ "@aws-sdk/lib-dynamodb": "^3.685.0",
+ "@types/aws-lambda": "^8.10.145",
+ "@types/node": "22.5.4",
+ "aws-cdk": "2.158.0",
+ "ts-node": "^10.9.2",
+ "typescript": "~5.6.2",
+ "vitest": "^2.1.4"
+ },
+ "dependencies": {
+ "aws-cdk-lib": "2.158.0",
+ "constructs": "^10.0.0",
+ "source-map-support": "^0.5.21"
+ }
+}
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/stack.yaml b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/stack.yaml
new file mode 100644
index 00000000000..08854bbaccb
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/stack.yaml
@@ -0,0 +1,288 @@
+Resources:
+ PoolsAndTriggersBasedocexamplecustomusers6450C4F6:
+ Type: AWS::DynamoDB::Table
+ Properties:
+ AttributeDefinitions:
+ - AttributeName: UserEmail
+ AttributeType: S
+ KeySchema:
+ - AttributeName: UserEmail
+ KeyType: HASH
+ ProvisionedThroughput:
+ ReadCapacityUnits: 5
+ WriteCapacityUnits: 5
+ TableName: doc-example-custom-users
+ UpdateReplacePolicy: Delete
+ DeletionPolicy: Delete
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/PoolsAndTriggersBase/doc-example-custom-users/Resource
+ PoolsAndTriggersBasedocexamplepoolsandtriggers537A7FEC:
+ Type: AWS::Cognito::UserPool
+ Properties:
+ AccountRecoverySetting:
+ RecoveryMechanisms:
+ - Name: verified_email
+ Priority: 1
+ AdminCreateUserConfig:
+ AllowAdminCreateUserOnly: false
+ DeletionProtection: INACTIVE
+ EmailVerificationMessage: The verification code to your new account is {####}
+ EmailVerificationSubject: Verify your new account
+ Schema:
+ - Mutable: false
+ Name: email
+ Required: true
+ SmsVerificationMessage: The verification code to your new account is {####}
+ UserPoolName: doc-examples-pools-and-triggers
+ VerificationMessageTemplate:
+ DefaultEmailOption: CONFIRM_WITH_CODE
+ EmailMessage: The verification code to your new account is {####}
+ EmailSubject: Verify your new account
+ SmsMessage: The verification code to your new account is {####}
+ UpdateReplacePolicy: Delete
+ DeletionPolicy: Delete
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/PoolsAndTriggersBase/doc-example-pools-and-triggers/Resource
+ PoolsAndTriggersBasedocexamplepoolsandtriggersclientDC762CAB:
+ Type: AWS::Cognito::UserPoolClient
+ Properties:
+ AllowedOAuthFlows:
+ - implicit
+ - code
+ AllowedOAuthFlowsUserPoolClient: true
+ AllowedOAuthScopes:
+ - profile
+ - phone
+ - email
+ - openid
+ - aws.cognito.signin.user.admin
+ CallbackURLs:
+ - https://example.com
+ ClientName: doc-example-pools-and-triggers-client
+ ExplicitAuthFlows:
+ - ALLOW_USER_PASSWORD_AUTH
+ - ALLOW_REFRESH_TOKEN_AUTH
+ SupportedIdentityProviders:
+ - COGNITO
+ UserPoolId:
+ Ref: PoolsAndTriggersBasedocexamplepoolsandtriggers537A7FEC
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/PoolsAndTriggersBase/doc-example-pools-and-triggers-client/Resource
+ PoolsAndTriggersBasedocexamplepoolsandtriggerslambdaroleE5B33257:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Statement:
+ - Action: sts:AssumeRole
+ Effect: Allow
+ Principal:
+ Service: lambda.amazonaws.com
+ Version: "2012-10-17"
+ ManagedPolicyArns:
+ - Fn::Join:
+ - ""
+ - - "arn:"
+ - Ref: AWS::Partition
+ - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+ RoleName: doc-example-pools-and-triggers-lambda-role
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/PoolsAndTriggersBase/doc-example-pools-and-triggers-lambda-role/Resource
+ PoolsAndTriggersBasedocexamplepoolsandtriggerslambdaroleDefaultPolicy7DBD0FD6:
+ Type: AWS::IAM::Policy
+ Properties:
+ PolicyDocument:
+ Statement:
+ - Action:
+ - dynamodb:BatchGetItem
+ - dynamodb:BatchWriteItem
+ - dynamodb:ConditionCheckItem
+ - dynamodb:DeleteItem
+ - dynamodb:DescribeTable
+ - dynamodb:GetItem
+ - dynamodb:GetRecords
+ - dynamodb:GetShardIterator
+ - dynamodb:PutItem
+ - dynamodb:Query
+ - dynamodb:Scan
+ - dynamodb:UpdateItem
+ Effect: Allow
+ Resource:
+ - Fn::GetAtt:
+ - PoolsAndTriggersBasedocexamplecustomusers6450C4F6
+ - Arn
+ - Ref: AWS::NoValue
+ Version: "2012-10-17"
+ PolicyName: PoolsAndTriggersBasedocexamplepoolsandtriggerslambdaroleDefaultPolicy7DBD0FD6
+ Roles:
+ - Ref: PoolsAndTriggersBasedocexamplepoolsandtriggerslambdaroleE5B33257
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/PoolsAndTriggersBase/doc-example-pools-and-triggers-lambda-role/DefaultPolicy/Resource
+ AutoConfirmHandlerE436DFD8:
+ Type: AWS::Lambda::Function
+ Properties:
+ Code:
+ S3Bucket:
+ Fn::Sub: cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}
+ S3Key: 84a8b65cf18df89d90819bf96e9edd69bdc3aca847cf6cdbd6bb9825907809ae.zip
+ Environment:
+ Variables:
+ TABLE_NAME: doc-example-custom-users
+ Handler: index.handler
+ Role:
+ Fn::GetAtt:
+ - PoolsAndTriggersBasedocexamplepoolsandtriggerslambdaroleE5B33257
+ - Arn
+ Runtime: nodejs20.x
+ DependsOn:
+ - PoolsAndTriggersBasedocexamplepoolsandtriggerslambdaroleDefaultPolicy7DBD0FD6
+ - PoolsAndTriggersBasedocexamplepoolsandtriggerslambdaroleE5B33257
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/AutoConfirmHandler/Resource
+ aws:asset:path: asset.84a8b65cf18df89d90819bf96e9edd69bdc3aca847cf6cdbd6bb9825907809ae
+ aws:asset:is-bundled: true
+ aws:asset:property: Code
+ AutoConfirmHandlerInvokeJ9h1Fmrpvqfr9TPEEx65FOCskl35ksvNASPE0Ak2kIDCC10771:
+ Type: AWS::Lambda::Permission
+ Properties:
+ Action: lambda:InvokeFunction
+ FunctionName:
+ Fn::GetAtt:
+ - AutoConfirmHandlerE436DFD8
+ - Arn
+ Principal: cognito-idp.amazonaws.com
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/AutoConfirmHandler/InvokeJ9h1Fmrpvqfr9TPEEx6+5FOCskl35ksvNASPE0Ak2kI=
+ CDKMetadata:
+ Type: AWS::CDK::Metadata
+ Properties:
+ Analytics: v2:deflate64:H4sIAAAAAAAA/01Oy07DMBD8lt6dJW2F4EojcYQqhXPk2E61ibOLsg5VZfnfkROIOM3szkNzgP3jM5Q7fZPC2KHw2EK8BG0GpW/SRHsnPbJtIX7o1jtVdbSQpAxfCQND/BQ3nZl91jb+RyqPjsJ/af0khXqEWPPaueCZPZp7PleWlNdja3VDbF0v8LbA60wmIJOSY6NFXBB4yaDkCKfZDC6ctLjfKMSqoy2Rm900oggypaRqJzxPxqml4BL0FemaXe9z+JqX1Zul6qhispiLksqLoJeH70MJ+ycod70gFtNMAUcH9Yo/prhxn1wBAAA=
+ Metadata:
+ aws:cdk:path: PoolsAndTriggersStack/CDKMetadata/Default
+ Condition: CDKMetadataAvailable
+Outputs:
+ AutoConfirmHandlerName:
+ Value:
+ Ref: AutoConfirmHandlerE436DFD8
+ AutoConfirmHandlerArn:
+ Value:
+ Fn::GetAtt:
+ - AutoConfirmHandlerE436DFD8
+ - Arn
+ TableName:
+ Value: doc-example-custom-users
+ UserPoolId:
+ Value:
+ Ref: PoolsAndTriggersBasedocexamplepoolsandtriggers537A7FEC
+ UserPoolArn:
+ Value:
+ Fn::GetAtt:
+ - PoolsAndTriggersBasedocexamplepoolsandtriggers537A7FEC
+ - Arn
+ UserPoolClientId:
+ Value:
+ Ref: PoolsAndTriggersBasedocexamplepoolsandtriggersclientDC762CAB
+Conditions:
+ CDKMetadataAvailable:
+ Fn::Or:
+ - Fn::Or:
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - af-south-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-east-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-northeast-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-northeast-2
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-northeast-3
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-south-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-south-2
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-southeast-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-southeast-2
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-southeast-3
+ - Fn::Or:
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-southeast-4
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ca-central-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ca-west-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - cn-north-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - cn-northwest-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-central-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-central-2
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-north-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-south-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-south-2
+ - Fn::Or:
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-west-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-west-2
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - eu-west-3
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - il-central-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - me-central-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - me-south-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - sa-east-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - us-east-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - us-east-2
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - us-west-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - us-west-2
+Parameters:
+ BootstrapVersion:
+ Type: AWS::SSM::Parameter::Value
+ Default: /cdk-bootstrap/hnb659fds/version
+ Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
+
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tests/cdk.unit.test.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tests/cdk.unit.test.ts
new file mode 100644
index 00000000000..b73d30a8118
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tests/cdk.unit.test.ts
@@ -0,0 +1,101 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { describe, test, expect } from "vitest";
+import { PreSignUpHandler } from "../lib/stack.autoConfirmHandler";
+import type { UserRepository } from "../lib/user-repository";
+import type { PreSignUpTriggerEvent } from "aws-lambda";
+
+class FakeUserRepository implements UserRepository {
+ private userInfo: Record | undefined;
+
+ setUserInfo(userInfo: Record | undefined) {
+ this.userInfo = userInfo;
+ }
+
+ async getUserInfoByEmail(
+ userEmail: string,
+ ): Promise | undefined> {
+ return this.userInfo;
+ }
+}
+
+describe("PreSignUpHandler", () => {
+ test("should auto-confirm user if email and username match", async () => {
+ const fakeUserRepo = new FakeUserRepository();
+ fakeUserRepo.setUserInfo({
+ UserName: "testuser",
+ UserEmail: "test@example.com",
+ });
+
+ const preSignUpHandler = new PreSignUpHandler(fakeUserRepo);
+ const event = {
+ triggerSource: "PreSignUp_SignUp",
+ userName: "testuser",
+ request: {
+ userAttributes: {
+ email: "test@example.com",
+ },
+ },
+ response: {
+ autoConfirmUser: false,
+ autoVerifyEmail: false,
+ },
+ } as unknown as PreSignUpTriggerEvent;
+
+ const result = await preSignUpHandler.handlePreSignUpTriggerEvent(event);
+ expect(result.response.autoConfirmUser).toBe(true);
+ expect(result.response.autoVerifyEmail).toBe(true);
+ });
+
+ test("should not auto-confirm user if email and username do not match", async () => {
+ const fakeUserRepo = new FakeUserRepository();
+ fakeUserRepo.setUserInfo({
+ UserName: "differentuser",
+ UserEmail: "test@example.com",
+ });
+
+ const preSignUpHandler = new PreSignUpHandler(fakeUserRepo);
+ const event = {
+ triggerSource: "PreSignUp_SignUp",
+ userName: "testuser",
+ request: {
+ userAttributes: {
+ email: "test@example.com",
+ },
+ },
+ response: {
+ autoConfirmUser: false,
+ autoVerifyEmail: false,
+ },
+ } as unknown as PreSignUpTriggerEvent;
+
+ const result = await preSignUpHandler.handlePreSignUpTriggerEvent(event);
+ expect(result.response.autoConfirmUser).toBe(false);
+ expect(result.response.autoVerifyEmail).toBe(false);
+ });
+
+ test("should not auto-confirm user if email is not found", async () => {
+ const fakeUserRepo = new FakeUserRepository();
+ fakeUserRepo.setUserInfo(undefined);
+
+ const preSignUpHandler = new PreSignUpHandler(fakeUserRepo);
+ const event = {
+ triggerSource: "PreSignUp_SignUp",
+ userName: "testuser",
+ request: {
+ userAttributes: {
+ email: "test@example.com",
+ },
+ },
+ response: {
+ autoConfirmUser: false,
+ autoVerifyEmail: false,
+ },
+ } as unknown as PreSignUpTriggerEvent;
+
+ const result = await preSignUpHandler.handlePreSignUpTriggerEvent(event);
+ expect(result.response.autoConfirmUser).toBe(false);
+ expect(result.response.autoVerifyEmail).toBe(false);
+ });
+});
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json
new file mode 100644
index 00000000000..464ed774ba8
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "commonjs",
+ "lib": ["es2020", "dom"],
+ "declaration": true,
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": false,
+ "inlineSourceMap": true,
+ "inlineSources": true,
+ "experimentalDecorators": true,
+ "strictPropertyInitialization": false,
+ "typeRoots": ["./node_modules/@types"]
+ },
+ "exclude": ["node_modules", "cdk.out"]
+}
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js
new file mode 100755
index 00000000000..64486cc04e0
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// snippet-start:[javascript.v3.wkflw.pools-triggers.index]
+import { AutoConfirm } from "./scenario-auto-confirm.js";
+
+/**
+ * The context is passed to every scenario. Scenario steps
+ * will modify the context.
+ */
+const context = {
+ errors: [],
+ users: [
+ {
+ UserName: "test_user_1",
+ UserEmail: "test_email_1@example.com",
+ },
+ {
+ UserName: "test_user_2",
+ UserEmail: "test_email_2@example.com",
+ },
+ {
+ UserName: "test_user_3",
+ UserEmail: "test_email_3@example.com",
+ },
+ ],
+};
+
+/**
+ * Three Scenarios are created for the workflow. A Scenario is an orchestration class
+ * that simplifies running a series of steps.
+ */
+export const scenarios = {
+ // Demonstrate automatically confirming known users in a database.
+ "auto-confirm": AutoConfirm(context),
+};
+
+// Call function if run directly
+import { fileURLToPath } from "node:url";
+import { parseScenarioArgs } from "@aws-doc-sdk-examples/lib/scenario/index.js";
+
+if (process.argv[1] === fileURLToPath(import.meta.url)) {
+ parseScenarioArgs(scenarios, {
+ name: "Cognito user pools and triggers",
+ description:
+ "Demonstrate how to use the AWS SDKs to customize Amazon Cognito authentication behavior.",
+ });
+}
+// snippet-end:[javascript.v3.wkflw.pools-triggers.index]
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json
new file mode 100644
index 00000000000..9b3196d9b06
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@aws-doc-sdk-examples/wkflw-pools-triggers",
+ "version": "1.0.0",
+ "author": "Corey Pyle ",
+ "license": "Apache-2.0",
+ "type": "module",
+ "scripts": {
+ "test": "npm run cdk-test",
+ "integration-test": "vitest run **/*.integration.test.js --reporter=junit --outputFile=test_results/$npm_package_name.junit.xml",
+ "cdk-test": "npm run test --prefix ./cdk"
+ },
+ "engines": {
+ "node": "^20.17.0"
+ },
+ "dependencies": {
+ "@aws-sdk/client-cloudwatch-logs": "^3.686.0",
+ "@aws-sdk/client-dynamodb": "^3.682.0",
+ "@aws-sdk/lib-dynamodb": "^3.685.0"
+ },
+ "devDependencies": {
+ "@aws-sdk/client-cloudformation": "^3.687.0"
+ }
+}
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-auto-confirm.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-auto-confirm.js
new file mode 100644
index 00000000000..3a8eceac6aa
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-auto-confirm.js
@@ -0,0 +1,356 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// snippet-start:[javascript.v3.wkflw.pools-triggers.AutoConfirm]
+import { wait } from "@aws-doc-sdk-examples/lib/utils/util-timers.js";
+import {
+ Scenario,
+ ScenarioAction,
+ ScenarioInput,
+ ScenarioOutput,
+} from "@aws-doc-sdk-examples/lib/scenario/scenario.js";
+
+import {
+ getStackOutputs,
+ logCleanUpReminder,
+ promptForStackName,
+ promptForStackRegion,
+ skipWhenErrors,
+} from "./steps-common.js";
+import { populateTable } from "./actions/dynamodb-actions.js";
+import {
+ addPreSignUpHandler,
+ deleteUser,
+ getUser,
+ signIn,
+ signUpUser,
+} from "./actions/cognito-actions.js";
+import {
+ getLatestLogStreamForLambda,
+ getLogEvents,
+} from "./actions/cloudwatch-logs-actions.js";
+
+/**
+ * @typedef {{
+ * errors: Error[],
+ * password: string,
+ * users: { UserName: string, UserEmail: string }[],
+ * selectedUser?: string,
+ * stackName?: string,
+ * stackRegion?: string,
+ * token?: string,
+ * confirmDeleteSignedInUser?: boolean,
+ * TableName?: string,
+ * UserPoolClientId?: string,
+ * UserPoolId?: string,
+ * UserPoolArn?: string,
+ * AutoConfirmHandlerArn?: string,
+ * AutoConfirmHandlerName?: string
+ * }} State
+ */
+
+const greeting = new ScenarioOutput(
+ "greeting",
+ (/** @type {State} */ state) => `This demo will populate some users into the \
+database created as part of the "${state.stackName}" stack. \
+Then the autoConfirmHandler will be linked to the PreSignUp \
+trigger from Cognito. Finally, you will choose a user to sign up.`,
+ { skipWhen: skipWhenErrors },
+);
+
+const logPopulatingUsers = new ScenarioOutput(
+ "logPopulatingUsers",
+ "Populating the DynamoDB table with some users.",
+ { skipWhenErrors: skipWhenErrors },
+);
+
+const logPopulatingUsersComplete = new ScenarioOutput(
+ "logPopulatingUsersComplete",
+ "Done populating users.",
+ { skipWhen: skipWhenErrors },
+);
+
+const populateUsers = new ScenarioAction(
+ "populateUsers",
+ async (/** @type {State} */ state) => {
+ const [_, err] = await populateTable({
+ region: state.stackRegion,
+ tableName: state.TableName,
+ items: state.users,
+ });
+ if (err) {
+ state.errors.push(err);
+ }
+ },
+ {
+ skipWhen: skipWhenErrors,
+ },
+);
+
+const logSetupSignUpTrigger = new ScenarioOutput(
+ "logSetupSignUpTrigger",
+ "Setting up the PreSignUp trigger for the Cognito User Pool.",
+ { skipWhen: skipWhenErrors },
+);
+
+const setupSignUpTrigger = new ScenarioAction(
+ "setupSignUpTrigger",
+ async (/** @type {State} */ state) => {
+ const [_, err] = await addPreSignUpHandler({
+ region: state.stackRegion,
+ userPoolId: state.UserPoolId,
+ handlerArn: state.AutoConfirmHandlerArn,
+ });
+ if (err) {
+ state.errors.push(err);
+ }
+ },
+ {
+ skipWhen: skipWhenErrors,
+ },
+);
+
+const logSetupSignUpTriggerComplete = new ScenarioOutput(
+ "logSetupSignUpTriggerComplete",
+ (
+ /** @type {State} */ state,
+ ) => `The lambda function "${state.AutoConfirmHandlerName}" \
+has been configured as the PreSignUp trigger handler for the user pool "${state.UserPoolId}".`,
+ { skipWhen: skipWhenErrors },
+);
+
+const selectUser = new ScenarioInput(
+ "selectedUser",
+ "Select a user to sign up.",
+ {
+ type: "select",
+ choices: (/** @type {State} */ state) => state.users.map((u) => u.UserName),
+ skipWhen: skipWhenErrors,
+ default: (/** @type {State} */ state) => state.users[0].UserName,
+ },
+);
+
+const checkIfUserAlreadyExists = new ScenarioAction(
+ "checkIfUserAlreadyExists",
+ async (/** @type {State} */ state) => {
+ const [user, err] = await getUser({
+ region: state.stackRegion,
+ userPoolId: state.UserPoolId,
+ username: state.selectedUser,
+ });
+
+ if (err?.name === "UserNotFoundException") {
+ // Do nothing. We're not expecting the user to exist before
+ // sign up is complete.
+ return;
+ }
+
+ if (err) {
+ state.errors.push(err);
+ return;
+ }
+
+ if (user) {
+ state.errors.push(
+ new Error(
+ `The user "${state.selectedUser}" already exists in the user pool "${state.UserPoolId}".`,
+ ),
+ );
+ }
+ },
+ {
+ skipWhen: skipWhenErrors,
+ },
+);
+
+const createPassword = new ScenarioInput(
+ "password",
+ "Enter a password that has at least eight characters, uppercase, lowercase, numbers and symbols.",
+ { type: "password", skipWhen: skipWhenErrors, default: "Abcd1234!" },
+);
+
+const logSignUpExistingUser = new ScenarioOutput(
+ "logSignUpExistingUser",
+ (/** @type {State} */ state) => `Signing up user "${state.selectedUser}".`,
+ { skipWhen: skipWhenErrors },
+);
+
+const signUpExistingUser = new ScenarioAction(
+ "signUpExistingUser",
+ async (/** @type {State} */ state) => {
+ const signUp = (password) =>
+ signUpUser({
+ region: state.stackRegion,
+ userPoolClientId: state.UserPoolClientId,
+ username: state.selectedUser,
+ email: state.users.find((u) => u.UserName === state.selectedUser)
+ .UserEmail,
+ password,
+ });
+
+ let [_, err] = await signUp(state.password);
+
+ while (err?.name === "InvalidPasswordException") {
+ console.warn("The password you entered was invalid.");
+ await createPassword.handle(state);
+ [_, err] = await signUp(state.password);
+ }
+
+ if (err) {
+ state.errors.push(err);
+ }
+ },
+ { skipWhen: skipWhenErrors },
+);
+
+const logSignUpExistingUserComplete = new ScenarioOutput(
+ "logSignUpExistingUserComplete",
+ (/** @type {State} */ state) =>
+ `"${state.selectedUser} was signed up successfully.`,
+ { skipWhen: skipWhenErrors },
+);
+
+const logLambdaLogs = new ScenarioAction(
+ "logLambdaLogs",
+ async (/** @type {State} */ state) => {
+ console.log(
+ "Waiting a few seconds to let Lambda write to CloudWatch Logs...\n",
+ );
+ await wait(10);
+
+ const [logStream, logStreamErr] = await getLatestLogStreamForLambda({
+ functionName: state.AutoConfirmHandlerName,
+ region: state.stackRegion,
+ });
+ if (logStreamErr) {
+ state.errors.push(logStreamErr);
+ return;
+ }
+
+ console.log(
+ `Getting some recent events from log stream "${logStream.logStreamName}"`,
+ );
+ const [logEvents, logEventsErr] = await getLogEvents({
+ functionName: state.AutoConfirmHandlerName,
+ region: state.stackRegion,
+ eventCount: 10,
+ logStreamName: logStream.logStreamName,
+ });
+ if (logEventsErr) {
+ state.errors.push(logEventsErr);
+ return;
+ }
+
+ console.log(logEvents.map((ev) => `\t${ev.message}`).join(""));
+ },
+ { skipWhen: skipWhenErrors },
+);
+
+const logSignInUser = new ScenarioOutput(
+ "logSignInUser",
+ (/** @type {State} */ state) => `Let's sign in as ${state.selectedUser}`,
+ { skipWhen: skipWhenErrors },
+);
+
+const signInUser = new ScenarioAction(
+ "signInUser",
+ async (/** @type {State} */ state) => {
+ const [response, err] = await signIn({
+ region: state.stackRegion,
+ clientId: state.UserPoolClientId,
+ username: state.selectedUser,
+ password: state.password,
+ });
+
+ if (err?.name === "PasswordResetRequiredException") {
+ state.errors.push(new Error("Please reset your password."));
+ return;
+ }
+
+ if (err) {
+ state.errors.push(err);
+ return;
+ }
+
+ state.token = response?.AuthenticationResult?.AccessToken;
+ },
+ { skipWhen: skipWhenErrors },
+);
+
+const logSignInUserComplete = new ScenarioOutput(
+ "logSignInUserComplete",
+ (/** @type {State} */ state) =>
+ `Successfully signed in. Your access token starts with: ${state.token.slice(0, 11)}`,
+ { skipWhen: skipWhenErrors },
+);
+
+const confirmDeleteSignedInUser = new ScenarioInput(
+ "confirmDeleteSignedInUser",
+ "Do you want to delete the currently signed in user?",
+ { type: "confirm", skipWhen: skipWhenErrors },
+);
+
+const deleteSignedInUser = new ScenarioAction(
+ "deleteSignedInUser",
+ async (/** @type {State} */ state) => {
+ const [_, err] = await deleteUser({
+ region: state.stackRegion,
+ accessToken: state.token,
+ });
+
+ if (err) {
+ state.errors.push(err);
+ }
+ },
+ {
+ skipWhen: (/** @type {State} */ state) =>
+ skipWhenErrors(state) || !state.confirmDeleteSignedInUser,
+ },
+);
+
+const logErrors = new ScenarioOutput(
+ "logErrors",
+ (/** @type {State}*/ state) => {
+ const errorList = state.errors
+ .map((err) => ` - ${err.name}: ${err.message}`)
+ .join("\n");
+ return `Scenario errors found:\n${errorList}`;
+ },
+ {
+ // Don't log errors when there aren't any!
+ skipWhen: (/** @type {State} */ state) => state.errors.length === 0,
+ },
+);
+
+export const AutoConfirm = (context) =>
+ new Scenario(
+ "AutoConfirm",
+ [
+ promptForStackName,
+ promptForStackRegion,
+ getStackOutputs,
+ greeting,
+ logPopulatingUsers,
+ populateUsers,
+ logPopulatingUsersComplete,
+ logSetupSignUpTrigger,
+ setupSignUpTrigger,
+ logSetupSignUpTriggerComplete,
+ selectUser,
+ checkIfUserAlreadyExists,
+ createPassword,
+ logSignUpExistingUser,
+ signUpExistingUser,
+ logSignUpExistingUserComplete,
+ logLambdaLogs,
+ logSignInUser,
+ signInUser,
+ logSignInUserComplete,
+ confirmDeleteSignedInUser,
+ deleteSignedInUser,
+ logCleanUpReminder,
+ logErrors,
+ ],
+ context,
+ );
+// snippet-end:[javascript.v3.wkflw.pools-triggers.AutoConfirm]
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/steps-common.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/steps-common.js
new file mode 100644
index 00000000000..9b60d3f9a41
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/steps-common.js
@@ -0,0 +1,49 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// snippet-start:[javascript.v3.wkflw.pools-triggers.common]
+import {
+ ScenarioAction,
+ ScenarioInput,
+ ScenarioOutput,
+} from "@aws-doc-sdk-examples/lib/scenario/scenario.js";
+import { getCfnOutputs } from "@aws-doc-sdk-examples/lib/sdk/cfn-outputs.js";
+
+export const skipWhenErrors = (state) => state.errors.length > 0;
+
+export const getStackOutputs = new ScenarioAction(
+ "getStackOutputs",
+ async (state) => {
+ if (!state.stackName || !state.stackRegion) {
+ state.errors.push(
+ new Error(
+ "No stack name or region provided. The stack name and \
+region are required to fetch CFN outputs relevant to this example.",
+ ),
+ );
+ return;
+ }
+
+ const outputs = await getCfnOutputs(state.stackName, state.stackRegion);
+ Object.assign(state, outputs);
+ },
+);
+
+export const promptForStackName = new ScenarioInput(
+ "stackName",
+ "Enter the name of the stack you deployed earlier.",
+ { type: "input", default: "PoolsAndTriggersStack" },
+);
+
+export const promptForStackRegion = new ScenarioInput(
+ "stackRegion",
+ "Enter the region of the stack you deployed earlier.",
+ { type: "input", default: "us-east-1" },
+);
+
+export const logCleanUpReminder = new ScenarioOutput(
+ "logCleanUpReminder",
+ "All done. Remember to run 'cdk destroy' to teardown the stack.",
+ { skipWhen: skipWhenErrors },
+);
+// snippet-end:[javascript.v3.wkflw.pools-triggers.common]
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/tests/scenario-auto-confirm.integration.test.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/tests/scenario-auto-confirm.integration.test.js
new file mode 100644
index 00000000000..d509da4e181
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/tests/scenario-auto-confirm.integration.test.js
@@ -0,0 +1,81 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import { readFile } from "node:fs/promises";
+import { dirname, join } from "node:path";
+
+import { describe, it, expect, beforeAll, afterAll } from "vitest";
+import {
+ Capability,
+ CloudFormationClient,
+ CreateStackCommand,
+ DeleteStackCommand,
+ DescribeStacksCommand,
+} from "@aws-sdk/client-cloudformation";
+import { fileURLToPath } from "node:url";
+import { retry } from "@aws-doc-sdk-examples/lib/utils/util-timers.js";
+import { AutoConfirm } from "../scenario-auto-confirm.js";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+describe("Scenario - AutoConfirm", () => {
+ const cloudformationClient = new CloudFormationClient({
+ region: "us-east-1",
+ });
+ const stackName = "PoolsAndTriggersStack";
+
+ beforeAll(async () => {
+ const path = join(__dirname, "../cdk/stack.yaml");
+ const stack = await readFile(path, { encoding: "utf8" });
+
+ await cloudformationClient.send(
+ new CreateStackCommand({
+ StackName: stackName,
+ TemplateBody: stack,
+ Capabilities: [Capability.CAPABILITY_NAMED_IAM],
+ }),
+ );
+ await retry({ intervalInMs: 2000, maxRetries: 60 }, async () => {
+ const { Stacks } = await cloudformationClient.send(
+ new DescribeStacksCommand({ StackName: stackName }),
+ );
+ const stack = Stacks[0];
+ if (stack.StackStatus !== "CREATE_COMPLETE") {
+ throw new Error("Stack creation incomplete.");
+ }
+ });
+ });
+
+ afterAll(async () => {
+ await cloudformationClient.send(
+ new DeleteStackCommand({
+ StackName: stackName,
+ }),
+ );
+
+ await retry({ intervalInMs: 2000, maxRetries: 60 }, async () => {
+ const { Stacks } = await cloudformationClient.send(
+ new DescribeStacksCommand(),
+ );
+
+ const targetStack = Stacks.find((s) => s.StackName === stackName);
+
+ if (targetStack) {
+ throw new Error("Stack not deleted yet.");
+ }
+ });
+ });
+
+ it("should complete without error", async () => {
+ const context = {
+ errors: [],
+ users: [
+ {
+ UserName: "test_user_1",
+ UserEmail: "test_email_1@example.com",
+ },
+ ],
+ };
+ const autoConfirmScenario = AutoConfirm(context);
+ await autoConfirmScenario.run({ confirmAll: true });
+ });
+});
diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/vite.config.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/vite.config.js
new file mode 100644
index 00000000000..16124a6fd9b
--- /dev/null
+++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/vite.config.js
@@ -0,0 +1,13 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ testTimeout: 3600000, // 1 hour timeout
+ hookTimeout: 3600000, // 1 hour timeout
+ sequence: {
+ concurrent: false, // Run tests sequentially
+ },
+ },
+});
diff --git a/javascriptv3/example_code/lambda/README.md b/javascriptv3/example_code/lambda/README.md
index fbfa12d2768..d5ce1292494 100644
--- a/javascriptv3/example_code/lambda/README.md
+++ b/javascriptv3/example_code/lambda/README.md
@@ -53,6 +53,13 @@ Code excerpts that show you how to call individual service functions.
- [UpdateFunctionCode](actions/update-function-code.js#L15)
- [UpdateFunctionConfiguration](actions/update-function-configuration.js#L12)
+### Scenarios
+
+Code examples that show you how to accomplish a specific task by calling multiple
+functions within the same service.
+
+- [Automatically confirm known users with a Lambda function](../cross-services/wkflw-pools-triggers/index.js)
+
@@ -116,6 +123,22 @@ This example shows you how to do the following:
+#### Automatically confirm known users with a Lambda function
+
+This example shows you how to automatically confirm known Amazon Cognito users with a Lambda function.
+
+- Configure a user pool to call a Lambda function for the PreSignUp trigger.
+- Sign up a user with Amazon Cognito.
+- The Lambda function scans a DynamoDB table and automatically confirms known users.
+- Sign in as the new user, then clean up resources.
+
+
+
+
+
+
+
+
### Tests
⚠ Running tests might result in charges to your AWS account.
diff --git a/javascriptv3/example_code/libs/prompter.js b/javascriptv3/example_code/libs/prompter.js
index 62374631b39..568fad9c81b 100644
--- a/javascriptv3/example_code/libs/prompter.js
+++ b/javascriptv3/example_code/libs/prompter.js
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// snippet-start:[javascript.v3.utils.prompter]
-import { select, input, confirm, checkbox } from "@inquirer/prompts";
+import { select, input, confirm, checkbox, password } from "@inquirer/prompts";
export class Prompter {
/**
@@ -19,6 +19,13 @@ export class Prompter {
return input(options);
}
+ /**
+ * @param {{ message: string }} options
+ */
+ password(options) {
+ return password({ ...options, mask: true });
+ }
+
/**
* @param {string} prompt
*/
diff --git a/javascriptv3/example_code/libs/scenario/scenario-parser.js b/javascriptv3/example_code/libs/scenario/scenario-parser.js
index bd1825a973b..823f495cd6a 100644
--- a/javascriptv3/example_code/libs/scenario/scenario-parser.js
+++ b/javascriptv3/example_code/libs/scenario/scenario-parser.js
@@ -11,7 +11,7 @@ import { logger } from "../utils/util-log.js";
*/
export const parseScenarioArgs = (
scenarios,
- { name = "", synopsis = "", description = "" } = {},
+ { name = "", synopsis, description = "" } = {},
) => {
const options = {
help: {
diff --git a/javascriptv3/example_code/libs/scenario/scenario.js b/javascriptv3/example_code/libs/scenario/scenario.js
index 05e6cd2daee..45c7912db40 100644
--- a/javascriptv3/example_code/libs/scenario/scenario.js
+++ b/javascriptv3/example_code/libs/scenario/scenario.js
@@ -110,7 +110,7 @@ export class ScenarioOutput extends Step {
/**
* @typedef {{
- * type: "confirm" | "input" | "multi-select" | "select",
+ * type: "confirm" | "input" | "multi-select" | "select" | "password",
* choices: (string | { name: string, value: string })[] | () => (string | { name: string, value: string })[],
* default: string | string[] | boolean | () => string | string[] | boolean }
* } ScenarioInputOptions
@@ -153,7 +153,7 @@ export class ScenarioInput extends Step {
stepHandlerOptions.confirmAll &&
this.stepOptions.default !== undefined
) {
- state[this.name] = this.stepOptions.default;
+ state[this.name] = this.default;
return state[this.name];
}
if (stepHandlerOptions.confirmAll) {
@@ -179,6 +179,9 @@ export class ScenarioInput extends Step {
case "confirm":
await this._handleConfirm(state);
break;
+ case "password":
+ await this._handlePassword(state);
+ break;
default:
throw new Error(
`Error handling ScenarioInput, ${this.stepOptions?.type} is not supported.`,
@@ -313,6 +316,24 @@ export class ScenarioInput extends Step {
state[this.name] = result;
}
+
+ /**
+ * @param {*} state
+ */
+ async _handlePassword(state) {
+ const message =
+ typeof this.prompt === "function" ? this.prompt(state) : this.prompt;
+
+ if (!message) {
+ throw new Error("Error handling ScenarioInput. Missing prompt.");
+ }
+
+ const result = await this.prompter.password({
+ message,
+ });
+
+ state[this.name] = result;
+ }
}
/**
diff --git a/javascriptv3/example_code/libs/scenario/steps-common.js b/javascriptv3/example_code/libs/scenario/steps-common.js
index c4ce4e268eb..a134e26840c 100644
--- a/javascriptv3/example_code/libs/scenario/steps-common.js
+++ b/javascriptv3/example_code/libs/scenario/steps-common.js
@@ -30,6 +30,17 @@ export const loadState = new Scenarios.ScenarioAction(
},
);
+export const deleteState = new Scenarios.ScenarioAction(
+ "deleteState",
+ async (state) => {
+ try {
+ await fs.rm("state.json");
+ } catch (err) {
+ console.warn("Failed to delete state:", err);
+ }
+ },
+);
+
/**
* Step factories.
*/
diff --git a/javascriptv3/package.json b/javascriptv3/package.json
index 97e25baa84b..3eb1986b962 100644
--- a/javascriptv3/package.json
+++ b/javascriptv3/package.json
@@ -11,7 +11,7 @@
"prepare": "cd .. && husky javascriptv3/.husky"
},
"lint-staged": {
- "*.{js,ts,jsx,tsx}": ["biome format --write", "biome lint"]
+ "*.{js,ts,jsx,tsx,json}": ["biome format --write", "biome lint"]
},
"workspaces": [
"example_code/auto-scaling",
@@ -34,6 +34,7 @@
"example_code/cross-services/photo_analyzer",
"example_code/cross-services/photo-asset-manager",
"example_code/cross-services/transcribe-streaming-app",
+ "example_code/cross-services/wkflw-pools-triggers",
"example_code/cross-services/wkflw-topics-queues",
"example_code/cross-services/wkflw-resilient-service",
"example_code/dynamodb",
diff --git a/workflows/user_pools_and_lambda_triggers/README.md b/workflows/user_pools_and_lambda_triggers/README.md
index a63ee7cd932..3e6f73cb436 100644
--- a/workflows/user_pools_and_lambda_triggers/README.md
+++ b/workflows/user_pools_and_lambda_triggers/README.md
@@ -90,6 +90,7 @@ This workflow demonstrates the following steps and tasks:
This example is implemented in the following languages:
- [Go](../../gov2/workflows/user_pools_and_lambda_triggers/README.md)
+- [JavaScript](../../javascriptv3/example_code/cross-services/wkflw-pools-triggers/README.md)
## Additional reading