From e9783d7cbcd7f363327999d45e5f11d37cb34971 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Fri, 1 Nov 2024 12:08:42 -0400 Subject: [PATCH 01/11] JavaScript (v3): Cognito - Add CDK base for pools and triggers workflow. --- .../wkflw-pools-triggers/cdk/.gitignore | 8 +++ .../wkflw-pools-triggers/cdk/.npmignore | 6 ++ .../wkflw-pools-triggers/cdk/README.md | 14 ++++ .../wkflw-pools-triggers/cdk/bin/cdk.ts | 18 +++++ .../wkflw-pools-triggers/cdk/cdk.json | 72 +++++++++++++++++++ .../cdk/lib/pools-and-triggers-base.ts | 70 ++++++++++++++++++ .../cdk/lib/pools-and-triggers-stack.ts | 14 ++++ .../wkflw-pools-triggers/cdk/package.json | 24 +++++++ .../wkflw-pools-triggers/cdk/tsconfig.json | 31 ++++++++ .../wkflw-pools-triggers/package.json | 8 +++ javascriptv3/package.json | 1 + 11 files changed, 266 insertions(+) create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.gitignore create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/.npmignore create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/README.md create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/bin/cdk.ts create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/cdk.json create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-base.ts create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-stack.ts create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json 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..e2fa12a5a34 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/bin/cdk.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { CdkStack } from "../lib/pools-and-triggers-stack"; + +const app = new cdk.App(); +new CdkStack(app, "CdkStack", { + /* 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/pools-and-triggers-base.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-base.ts new file mode 100644 index 00000000000..95e42d0a725 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-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/pools-and-triggers-stack.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-stack.ts new file mode 100644 index 00000000000..53dd18dccff --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-stack.ts @@ -0,0 +1,14 @@ +import * as cdk from "aws-cdk-lib"; +import type { Construct } from "constructs"; +// import * as sqs from 'aws-cdk-lib/aws-sqs'; + +export class CdkStack extends cdk.Stack { + // constructor(scope: Construct, id: string, props?: cdk.StackProps) { + // super(scope, id, props); + // // The code that defines your stack goes here + // // example resource + // // const queue = new sqs.Queue(this, 'CdkQueue', { + // // visibilityTimeout: cdk.Duration.seconds(300) + // // }); + // } +} 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..a1876bb50e4 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json @@ -0,0 +1,24 @@ +{ + "name": "@aws-doc-examples/pools-triggers-cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/cdk.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/node": "22.5.4", + "aws-cdk": "2.158.0", + "ts-node": "^10.9.2", + "typescript": "~5.6.2" + }, + "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/tsconfig.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json new file mode 100644 index 00000000000..aaa7dc510f1 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "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/package.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json new file mode 100644 index 00000000000..dd86cea31a8 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json @@ -0,0 +1,8 @@ +{ + "name": "@aws-doc-examples/wkflw-pools-triggers", + "version": "1.0.0", + "main": "index.js", + "author": "Corey Pyle ", + "license": "Apache-2.0", + "description": "" +} diff --git a/javascriptv3/package.json b/javascriptv3/package.json index 97e25baa84b..eaa908c2482 100644 --- a/javascriptv3/package.json +++ b/javascriptv3/package.json @@ -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", From bbd03a9590831a5f2023c99ffa1784c25cb20b74 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Mon, 4 Nov 2024 16:30:10 -0500 Subject: [PATCH 02/11] JavaScript (v3): Cognito - Add auto confirm handler and stub scenarios. --- .../wkflw-pools-triggers/cdk/bin/cdk.ts | 2 +- .../{pools-and-triggers-base.ts => base.ts} | 0 .../cdk/lib/pools-and-triggers-stack.ts | 14 --- .../cdk/lib/stack.autoConfirmHandler.ts | 74 +++++++++++++ .../wkflw-pools-triggers/cdk/lib/stack.ts | 37 +++++++ .../cdk/lib/user-repository.ts | 35 ++++++ .../wkflw-pools-triggers/cdk/package.json | 8 +- .../cdk/tests/cdk.unit.test.ts | 101 ++++++++++++++++++ .../wkflw-pools-triggers/index.js | 33 ++++++ .../wkflw-pools-triggers/package.json | 10 +- .../scenario-auto-confirm.js | 7 ++ .../wkflw-pools-triggers/scenario-deploy.js | 6 ++ .../libs/scenario/scenario-parser.js | 2 +- 13 files changed, 309 insertions(+), 20 deletions(-) rename javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/{pools-and-triggers-base.ts => base.ts} (100%) delete mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-stack.ts create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.autoConfirmHandler.ts create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.ts create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/user-repository.ts create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tests/cdk.unit.test.ts create mode 100755 javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-auto-confirm.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-deploy.js 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 index e2fa12a5a34..91b3c9e5c7f 100644 --- 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 @@ -1,7 +1,7 @@ #!/usr/bin/env node import "source-map-support/register"; import * as cdk from "aws-cdk-lib"; -import { CdkStack } from "../lib/pools-and-triggers-stack"; +import { CdkStack } from "../lib/stack"; const app = new cdk.App(); new CdkStack(app, "CdkStack", { diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-base.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/base.ts similarity index 100% rename from javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-base.ts rename to javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/base.ts diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-stack.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-stack.ts deleted file mode 100644 index 53dd18dccff..00000000000 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/pools-and-triggers-stack.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import type { Construct } from "constructs"; -// import * as sqs from 'aws-cdk-lib/aws-sqs'; - -export class CdkStack extends cdk.Stack { - // constructor(scope: Construct, id: string, props?: cdk.StackProps) { - // super(scope, id, props); - // // The code that defines your stack goes here - // // example resource - // // const queue = new sqs.Queue(this, 'CdkQueue', { - // // visibilityTimeout: cdk.Duration.seconds(300) - // // }); - // } -} 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..473d9f62289 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.autoConfirmHandler.ts @@ -0,0 +1,74 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +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); +}; 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..bea26b957c4 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/lib/stack.ts @@ -0,0 +1,37 @@ +// 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, { + environment: { + TABLE_NAME: this.poolsAndTriggersBase.tableName, + }, + role: this.poolsAndTriggersBase.lambdaRole, + runtime: Runtime.NODEJS_20_X, + }); + fn.grantInvoke(this.cognitoPrincipal); + new CfnOutput(this, 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 index a1876bb50e4..9071b90ce5b 100644 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json @@ -7,14 +7,18 @@ "scripts": { "build": "tsc", "watch": "tsc -w", - "test": "jest", + "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" + "typescript": "~5.6.2", + "vitest": "^2.1.4" }, "dependencies": { "aws-cdk-lib": "2.158.0", 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/index.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js new file mode 100755 index 00000000000..0636fad0777 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AutoConfirm } from "./scenario-auto-confirm.js"; +import { Deploy } from "./scenario-deploy.js"; +/** + * The context is passed to every scenario. Scenario steps + * will modify the context. + */ +const context = {}; + +/** + * Three Scenarios are created for the workflow. A Scenario is an orchestration class + * that simplifies running a series of steps. + */ +export const scenarios = { + deploy: Deploy(context), + // 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.", + }); +} diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json index dd86cea31a8..7488e4b74a1 100644 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json @@ -1,8 +1,14 @@ { "name": "@aws-doc-examples/wkflw-pools-triggers", "version": "1.0.0", - "main": "index.js", "author": "Corey Pyle ", "license": "Apache-2.0", - "description": "" + "type": "module", + "scripts": { + "test": "npm run cdk-test", + "cdk-test": "npm run test --prefix ./cdk" + }, + "engines": { + "node": "^20.17.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..45f055cd6b6 --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-auto-confirm.js @@ -0,0 +1,7 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Scenario } from "@aws-doc-sdk-examples/lib/scenario/scenario.js"; + +export const AutoConfirm = (context) => + new Scenario("AutoConfirm", [], context); diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-deploy.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-deploy.js new file mode 100644 index 00000000000..1fed5bcc16a --- /dev/null +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-deploy.js @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Scenario } from "@aws-doc-sdk-examples/lib/scenario/scenario.js"; + +export const Deploy = (context) => new Scenario("Deploy", [], context); 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: { From 8a822191d06b106ce5c50444f40cf1bafe9e1601 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Mon, 11 Nov 2024 16:29:12 -0500 Subject: [PATCH 03/11] JavaScript (v3): Cognito - Complete functionality and README for AutoConfirm workflow. --- .../cognito-identity-provider_metadata.yaml | 17 + .doc_gen/metadata/cross_metadata.yaml | 24 ++ .../wkflw-pools-triggers/README.md | 99 +++++ .../actions/cloudwatch-logs-actions.js | 69 ++++ .../actions/cognito-actions.js | 140 +++++++ .../actions/dynamodb-actions.js | 34 ++ .../wkflw-pools-triggers/cdk/bin/cdk.ts | 4 +- .../cdk/lib/stack.autoConfirmHandler.ts | 2 + .../wkflw-pools-triggers/cdk/lib/stack.ts | 7 +- .../wkflw-pools-triggers/cdk/stack.yaml | 288 ++++++++++++++ .../wkflw-pools-triggers/index.js | 23 +- .../wkflw-pools-triggers/package.json | 9 + .../scenario-auto-confirm.js | 353 +++++++++++++++++- .../wkflw-pools-triggers/scenario-deploy.js | 6 - .../wkflw-pools-triggers/steps-common.js | 49 +++ .../scenario-auto-confirm.integration.test.js | 81 ++++ .../wkflw-pools-triggers/vite.config.js | 13 + javascriptv3/example_code/libs/prompter.js | 9 +- .../example_code/libs/scenario/scenario.js | 25 +- .../libs/scenario/steps-common.js | 11 + .../user_pools_and_lambda_triggers/README.md | 1 + 21 files changed, 1246 insertions(+), 18 deletions(-) create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/README.md create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cloudwatch-logs-actions.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/dynamodb-actions.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/stack.yaml delete mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-deploy.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/steps-common.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/tests/scenario-auto-confirm.integration.test.js create mode 100644 javascriptv3/example_code/cross-services/wkflw-pools-triggers/vite.config.js 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..e10dd0877fa 100644 --- a/.doc_gen/metadata/cross_metadata.yaml +++ b/.doc_gen/metadata/cross_metadata.yaml @@ -822,6 +822,30 @@ 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: Run an interactive scenario at a command prompt. + snippet_tags: + - javascript.v3.wkflw.pools-triggers.index + - javascript.v3.wkflw.pools-triggers.AutoConfirm + - javascript.v3.wkflw.pools-triggers.common + - description: Handle the PreSignUp trigger with a &LAM; function. + snippet_tags: + - javascript.v3.wkflw.pools-triggers.handler.AutoConfirm + - description: &CWL; actions + snippet_files: + - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cloudwatch-logs-actions.js + - description: &COG; actions + snippet_files: + - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js + - description: &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/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/bin/cdk.ts b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/bin/cdk.ts index 91b3c9e5c7f..e4669d689e3 100644 --- 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 @@ -1,10 +1,10 @@ #!/usr/bin/env node import "source-map-support/register"; import * as cdk from "aws-cdk-lib"; -import { CdkStack } from "../lib/stack"; +import { PoolsAndTriggersStack } from "../lib/stack"; const app = new cdk.App(); -new CdkStack(app, "CdkStack", { +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. */ 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 index 473d9f62289..b4f8106ff67 100644 --- 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 @@ -1,6 +1,7 @@ // 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"; @@ -72,3 +73,4 @@ 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 index bea26b957c4..cec1696724c 100644 --- 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 @@ -17,12 +17,15 @@ export class PoolsAndTriggersStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - this.createFunction("autoConfirmHandler"); + 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, }, @@ -30,7 +33,7 @@ export class PoolsAndTriggersStack extends Stack { runtime: Runtime.NODEJS_20_X, }); fn.grantInvoke(this.cognitoPrincipal); - new CfnOutput(this, name, { value: fn.functionName }); + 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/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/index.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js index 0636fad0777..64486cc04e0 100755 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/index.js @@ -2,20 +2,36 @@ // 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"; -import { Deploy } from "./scenario-deploy.js"; + /** * The context is passed to every scenario. Scenario steps * will modify the context. */ -const 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 = { - deploy: Deploy(context), // Demonstrate automatically confirming known users in a database. "auto-confirm": AutoConfirm(context), }; @@ -31,3 +47,4 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) { "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 index 7488e4b74a1..f05bd2054c7 100644 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json @@ -6,9 +6,18 @@ "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 index 45f055cd6b6..3a8eceac6aa 100644 --- 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 @@ -1,7 +1,356 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Scenario } from "@aws-doc-sdk-examples/lib/scenario/scenario.js"; +// 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", [], 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/scenario-deploy.js b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-deploy.js deleted file mode 100644 index 1fed5bcc16a..00000000000 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/scenario-deploy.js +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { Scenario } from "@aws-doc-sdk-examples/lib/scenario/scenario.js"; - -export const Deploy = (context) => new Scenario("Deploy", [], context); 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/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.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/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 From 0d9099d4a985b2c6585978c5125d7118c8e992af Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 09:18:30 -0500 Subject: [PATCH 04/11] JavaScript (v3): Cognito - Fix invalid metadata. --- .doc_gen/metadata/cross_metadata.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.doc_gen/metadata/cross_metadata.yaml b/.doc_gen/metadata/cross_metadata.yaml index e10dd0877fa..b44140d3921 100644 --- a/.doc_gen/metadata/cross_metadata.yaml +++ b/.doc_gen/metadata/cross_metadata.yaml @@ -836,16 +836,15 @@ cross_CognitoAutoConfirmUser: - description: Handle the PreSignUp trigger with a &LAM; function. snippet_tags: - javascript.v3.wkflw.pools-triggers.handler.AutoConfirm - - description: &CWL; actions + - description: Module of &CWL; actions snippet_files: - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cloudwatch-logs-actions.js - - description: &COG; actions + - description: Module of &COG; actions snippet_files: - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js - - description: &DDB; actions + - 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: {} From 90049a7c3ce7f06b11948ae91f67fbb03835a396 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 09:46:51 -0500 Subject: [PATCH 05/11] JavaScript (v3): Cognito - Split snippets into more readable chunks for the auto confirm workflow. --- .doc_gen/metadata/cross_metadata.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.doc_gen/metadata/cross_metadata.yaml b/.doc_gen/metadata/cross_metadata.yaml index b44140d3921..e6dce456516 100644 --- a/.doc_gen/metadata/cross_metadata.yaml +++ b/.doc_gen/metadata/cross_metadata.yaml @@ -828,10 +828,19 @@ cross_CognitoAutoConfirmUser: github: javascriptv3/example_code/cross-services/wkflw-pools-triggers sdk_guide: excerpts: - - description: Run an interactive scenario at a command prompt. + - 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: Handle the PreSignUp trigger with a &LAM; function. snippet_tags: From e9bb14522808f6637541299d2ecc16b6e26d18ea Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 10:04:51 -0500 Subject: [PATCH 06/11] JavaScript (v3): Cognito - Reword some snippet descriptions. --- .doc_gen/metadata/cross_metadata.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.doc_gen/metadata/cross_metadata.yaml b/.doc_gen/metadata/cross_metadata.yaml index e6dce456516..f350838f387 100644 --- a/.doc_gen/metadata/cross_metadata.yaml +++ b/.doc_gen/metadata/cross_metadata.yaml @@ -842,16 +842,16 @@ cross_CognitoAutoConfirmUser: - description: These are steps that are shared with other Scenarios. snippet_tags: - javascript.v3.wkflw.pools-triggers.common - - description: Handle the PreSignUp trigger with a &LAM; function. + - 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 + - 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 + - description: Module of &COG; actions. snippet_files: - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/cognito-actions.js - - description: Module of &DDB; actions + - description: Module of &DDB; actions. snippet_files: - javascriptv3/example_code/cross-services/wkflw-pools-triggers/actions/dynamodb-actions.js services: From dbd8f9d33c26f1c829e2830782c22553d6126c51 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 10:24:54 -0500 Subject: [PATCH 07/11] JavaScript (v3): Cognito - Add SPDX header to pools/triggers CDK. --- .../cross-services/wkflw-pools-triggers/cdk/bin/cdk.ts | 3 +++ 1 file changed, 3 insertions(+) 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 index e4669d689e3..d69e111e17a 100644 --- 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 @@ -1,4 +1,7 @@ #!/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"; From 90f547ebd7de1f682009cb104aed5cb4b9820e88 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 10:28:30 -0500 Subject: [PATCH 08/11] JavaScript (v3): Cognito - Run writeme and fix package name issue for Pools/Triggers Wkflw --- .../cognito-identity-provider/README.md | 19 +++++++++++++++ .../wkflw-pools-triggers/cdk/package.json | 2 +- javascriptv3/example_code/lambda/README.md | 23 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) 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/cdk/package.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json index 9071b90ce5b..1811921dfff 100644 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/package.json @@ -1,5 +1,5 @@ { - "name": "@aws-doc-examples/pools-triggers-cdk", + "name": "@aws-doc-sdk-examples/pools-triggers-cdk", "version": "0.1.0", "bin": { "cdk": "bin/cdk.js" 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. From 85fe7ae64d51b7eaca8d896ea403ee973ba3b99b Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 10:32:57 -0500 Subject: [PATCH 09/11] JavaScript (v3): Cognito - Fix package name issue for Pools/Triggers Wkflw --- .../cross-services/wkflw-pools-triggers/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json index f05bd2054c7..9b3196d9b06 100644 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/package.json @@ -1,5 +1,5 @@ { - "name": "@aws-doc-examples/wkflw-pools-triggers", + "name": "@aws-doc-sdk-examples/wkflw-pools-triggers", "version": "1.0.0", "author": "Corey Pyle ", "license": "Apache-2.0", From a71b58f18ee91036b4bbf56884ef1fc5da09d142 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 10:38:48 -0500 Subject: [PATCH 10/11] JavaScript (v3): Add JSON to Biome formatter and ignore cdk files. --- javascriptv3/biome.json | 4 +++- javascriptv3/package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) 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/package.json b/javascriptv3/package.json index eaa908c2482..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", From b936aff51fba1491d4d2126e42e8223600f101d5 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Tue, 12 Nov 2024 10:42:58 -0500 Subject: [PATCH 11/11] JavaScript (v3): Format pools/triggers cdk tsconfig.json --- .../wkflw-pools-triggers/cdk/tsconfig.json | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) 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 index aaa7dc510f1..464ed774ba8 100644 --- a/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json +++ b/javascriptv3/example_code/cross-services/wkflw-pools-triggers/cdk/tsconfig.json @@ -2,10 +2,7 @@ "compilerOptions": { "target": "ES2020", "module": "commonjs", - "lib": [ - "es2020", - "dom" - ], + "lib": ["es2020", "dom"], "declaration": true, "strict": true, "noImplicitAny": true, @@ -20,12 +17,7 @@ "inlineSources": true, "experimentalDecorators": true, "strictPropertyInitialization": false, - "typeRoots": [ - "./node_modules/@types" - ] + "typeRoots": ["./node_modules/@types"] }, - "exclude": [ - "node_modules", - "cdk.out" - ] + "exclude": ["node_modules", "cdk.out"] }