From 5687b347b295585ce96f38269a2ddb6f974401fc Mon Sep 17 00:00:00 2001 From: Vidit Shah Date: Fri, 4 Apr 2025 15:20:19 +0530 Subject: [PATCH 1/5] working with tests --- cognito-lambda-dynamodb/README.md | 60 ++++++++++ cognito-lambda-dynamodb/cdk/.gitignore | 8 ++ cognito-lambda-dynamodb/cdk/.npmignore | 6 + cognito-lambda-dynamodb/cdk/README.md | 14 +++ .../cdk/__tests__/constants.ts | 4 + .../e2e/confirm-user-sign-up.test.ts | 23 ++++ .../cdk/__tests__/steps/given.ts | 113 ++++++++++++++++++ .../cdk/__tests__/steps/then.ts | 33 +++++ .../cdk/__tests__/steps/when.ts | 51 ++++++++ cognito-lambda-dynamodb/cdk/bin/cdk.ts | 6 + cognito-lambda-dynamodb/cdk/cdk.json | 88 ++++++++++++++ cognito-lambda-dynamodb/cdk/jest.config.js | 9 ++ cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts | 109 +++++++++++++++++ .../AddUserPostConfirmation/handler.ts | 49 ++++++++ cognito-lambda-dynamodb/cdk/package.json | 34 ++++++ cognito-lambda-dynamodb/cdk/test/cdk.test.ts | 17 +++ cognito-lambda-dynamodb/cdk/tsconfig.json | 24 ++++ cognito-lambda-dynamodb/example-pattern.json | 59 +++++++++ cognito-lambda-dynamodb/src/app.js | 10 ++ cognito-lambda-dynamodb/template.yaml | 16 +++ 20 files changed, 733 insertions(+) create mode 100644 cognito-lambda-dynamodb/README.md create mode 100644 cognito-lambda-dynamodb/cdk/.gitignore create mode 100644 cognito-lambda-dynamodb/cdk/.npmignore create mode 100644 cognito-lambda-dynamodb/cdk/README.md create mode 100644 cognito-lambda-dynamodb/cdk/__tests__/constants.ts create mode 100644 cognito-lambda-dynamodb/cdk/__tests__/e2e/confirm-user-sign-up.test.ts create mode 100644 cognito-lambda-dynamodb/cdk/__tests__/steps/given.ts create mode 100644 cognito-lambda-dynamodb/cdk/__tests__/steps/then.ts create mode 100644 cognito-lambda-dynamodb/cdk/__tests__/steps/when.ts create mode 100644 cognito-lambda-dynamodb/cdk/bin/cdk.ts create mode 100644 cognito-lambda-dynamodb/cdk/cdk.json create mode 100644 cognito-lambda-dynamodb/cdk/jest.config.js create mode 100644 cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts create mode 100644 cognito-lambda-dynamodb/cdk/lib/functions/AddUserPostConfirmation/handler.ts create mode 100644 cognito-lambda-dynamodb/cdk/package.json create mode 100644 cognito-lambda-dynamodb/cdk/test/cdk.test.ts create mode 100644 cognito-lambda-dynamodb/cdk/tsconfig.json create mode 100644 cognito-lambda-dynamodb/example-pattern.json create mode 100644 cognito-lambda-dynamodb/src/app.js create mode 100644 cognito-lambda-dynamodb/template.yaml diff --git a/cognito-lambda-dynamodb/README.md b/cognito-lambda-dynamodb/README.md new file mode 100644 index 000000000..3ca759d23 --- /dev/null +++ b/cognito-lambda-dynamodb/README.md @@ -0,0 +1,60 @@ +# AWS Service 1 to AWS Service 2 + +This pattern << explain usage >> + +Learn more about this pattern at Serverless Land Patterns: << Add the live URL here >> + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +1. Change directory to the pattern directory: + ``` + cd _patterns-model + ``` +1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file: + ``` + sam deploy --guided + ``` +1. During the prompts: + * Enter a stack name + * Enter the desired AWS Region + * Allow SAM CLI to create IAM roles with the required permissions. + + Once you have run `sam deploy --guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults. + +1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing. + +## How it works + +Explain how the service interaction works. + +## Testing + +Provide steps to trigger the integration and show what should be observed if successful. + +## Cleanup + +1. Delete the stack + ```bash + aws cloudformation delete-stack --stack-name STACK_NAME + ``` +1. Confirm the stack has been deleted + ```bash + aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus" + ``` +---- +Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/cognito-lambda-dynamodb/cdk/.gitignore b/cognito-lambda-dynamodb/cdk/.gitignore new file mode 100644 index 000000000..f60797b6a --- /dev/null +++ b/cognito-lambda-dynamodb/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/cognito-lambda-dynamodb/cdk/.npmignore b/cognito-lambda-dynamodb/cdk/.npmignore new file mode 100644 index 000000000..c1d6d45dc --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/cognito-lambda-dynamodb/cdk/README.md b/cognito-lambda-dynamodb/cdk/README.md new file mode 100644 index 000000000..9315fe5b9 --- /dev/null +++ b/cognito-lambda-dynamodb/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/cognito-lambda-dynamodb/cdk/__tests__/constants.ts b/cognito-lambda-dynamodb/cdk/__tests__/constants.ts new file mode 100644 index 000000000..31133ae1f --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/__tests__/constants.ts @@ -0,0 +1,4 @@ +export const REGION = ""; +export const USER_POOL_ID = ""; +export const CLIENT_USER_POOL_ID = ""; +export const TABLE_NAME = "Users"; diff --git a/cognito-lambda-dynamodb/cdk/__tests__/e2e/confirm-user-sign-up.test.ts b/cognito-lambda-dynamodb/cdk/__tests__/e2e/confirm-user-sign-up.test.ts new file mode 100644 index 000000000..5586fb0d0 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/__tests__/e2e/confirm-user-sign-up.test.ts @@ -0,0 +1,23 @@ +import * as given from "../steps/given"; +import * as then from "../steps/then"; +import * as when from "../steps/when"; + +describe("When a user Signs up ", () => { + it("Users profile should be saved in DynamoDB", async () => { + const { password, given_name, family_name, email } = given.a_random_user(); + console.log("name: ", given_name, family_name); + const userSub = await when.a_user_signs_up( + password, + email, + given_name, + family_name + ); + + console.log("user: ", userSub); + const ddbUser = await then.user_exists_in_UsersTable(userSub); + + console.log("ddbUser: ", ddbUser); + + expect(ddbUser.UserID).toMatch(userSub); + }); +}); diff --git a/cognito-lambda-dynamodb/cdk/__tests__/steps/given.ts b/cognito-lambda-dynamodb/cdk/__tests__/steps/given.ts new file mode 100644 index 000000000..6db2f21a2 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/__tests__/steps/given.ts @@ -0,0 +1,113 @@ +import * as cognito from "@aws-sdk/client-cognito-identity-provider"; + +import Chance from "chance"; +import { REGION, USER_POOL_ID, CLIENT_USER_POOL_ID } from "../constants"; +const cognitoClient = new cognito.CognitoIdentityProviderClient({ + region: REGION, +}); + +const chance = new Chance(); + +const userpool = USER_POOL_ID; +const userpoolClient = CLIENT_USER_POOL_ID; + +export const a_random_user = () => { + const given_name = chance.first({ nationality: "en" }); + const family_name = chance.first({ nationality: "en" }); + const password = ensurePasswordPolicy( + chance.string({ + length: 12, + pool: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+", + }) + ); + console.log("password: ", password); + const email = `${given_name}-${family_name}@dev.com`; + return { given_name, family_name, password, email }; +}; + +function ensurePasswordPolicy(password: string): string { + let newPassword = password; + if (!/[a-z]/.test(newPassword)) + newPassword += chance.letter({ casing: "lower" }); + if (!/[A-Z]/.test(newPassword)) + newPassword += chance.letter({ casing: "upper" }); + if (!/[0-9]/.test(newPassword)) + newPassword += chance.integer({ min: 0, max: 9 }).toString(); + if (!/[!@#$%^&*()_+]/.test(newPassword)) + newPassword += chance.pickone([ + "!", + "@", + "#", + "$", + "%", + "^", + "&", + "*", + "(", + ")", + "_", + "+", + ]); + return newPassword; // Ensure the password is still 12 characters long +} + +export const an_authenticated_user = async (): Promise => { + const { given_name, family_name, email, password } = a_random_user(); + + const userPoolId = userpool; + const clientId = userpoolClient; + console.log("userPoolId", userPoolId); + console.log("clientId", clientId); + + console.log(`[${email}] - signing up...`); + + const command = new cognito.SignUpCommand({ + ClientId: clientId, + Username: email, + Password: password, + UserAttributes: [ + { Name: "firstName", Value: given_name }, + { + Name: "lastName", + Value: family_name, + }, + ], + }); + + const signUpResponse = await cognitoClient.send(command); + const userSub = signUpResponse.UserSub; + + console.log(`${userSub} - confirming sign up`); + + const adminCommand: cognito.AdminConfirmSignUpCommandInput = { + UserPoolId: userPoolId as string, + Username: userSub as string, + }; + + await cognitoClient.send(new cognito.AdminConfirmSignUpCommand(adminCommand)); + + console.log(`[${email}] - confirmed sign up`); + + const authRequest: cognito.InitiateAuthCommandInput = { + ClientId: process.env.CLIENT_USER_POOL_ID as string, + AuthFlow: "USER_PASSWORD_AUTH", + AuthParameters: { + USERNAME: email, + PASSWORD: password, + }, + }; + + const authResponse = await cognitoClient.send( + new cognito.InitiateAuthCommand(authRequest) + ); + + console.log(`${email} - signed in`); + + return { + username: userSub as string, + name: `${given_name} ${family_name}`, + email, + idToken: authResponse.AuthenticationResult?.IdToken as string, + accessToken: authResponse.AuthenticationResult?.AccessToken as string, + }; +}; diff --git a/cognito-lambda-dynamodb/cdk/__tests__/steps/then.ts b/cognito-lambda-dynamodb/cdk/__tests__/steps/then.ts new file mode 100644 index 000000000..9c0fc57b5 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/__tests__/steps/then.ts @@ -0,0 +1,33 @@ +import { + DynamoDBClient, + GetItemCommand, + ScanCommand, +} from "@aws-sdk/client-dynamodb"; +import { unmarshall, marshall } from "@aws-sdk/util-dynamodb"; +import { REGION, TABLE_NAME } from "../constants"; +const ddbClient = new DynamoDBClient({ region: REGION }); +export const user_exists_in_UsersTable = async ( + userSub: string +): Promise => { + let Item: unknown; + console.log(`looking for user [${userSub}] in table [${TABLE_NAME}]`); + + // search for UserID in dynamoDb] Get Item Command + const getItemCommand = new GetItemCommand({ + TableName: TABLE_NAME, + Key: { + UserID: { S: userSub }, + }, + }); + + const getItemResponse = await ddbClient.send(getItemCommand); + console.log("Get Item Command Response ....", getItemResponse); + + if (getItemResponse.Item) { + Item = unmarshall(getItemResponse.Item); // Get the first matching item + } + + console.log("found item:", Item); + expect(Item).toBeTruthy(); + return Item; +}; diff --git a/cognito-lambda-dynamodb/cdk/__tests__/steps/when.ts b/cognito-lambda-dynamodb/cdk/__tests__/steps/when.ts new file mode 100644 index 000000000..97c7e0a8e --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/__tests__/steps/when.ts @@ -0,0 +1,51 @@ +import * as cognito from "@aws-sdk/client-cognito-identity-provider"; +import { REGION, USER_POOL_ID, CLIENT_USER_POOL_ID } from "../constants"; + +const cognitoClient = new cognito.CognitoIdentityProviderClient({ + region: REGION, +}); + +const userpool = USER_POOL_ID; +const userpoolClient = CLIENT_USER_POOL_ID; + +export const a_user_signs_up = async ( + password: string, + email: string, + given_name: string, + family_name: string +): Promise => { + const userPoolId = userpool; + const clientId = userpoolClient; + const username = email; + + console.log(`[${email}] - signing up...`); + + const command = new cognito.SignUpCommand({ + ClientId: clientId, + Username: username, + Password: password, + UserAttributes: [ + { Name: "email", Value: email }, + { Name: "custom:firstName", Value: given_name }, + { Name: "custom:lastName", Value: family_name }, + ], + }); + + const signUpResponse = await cognitoClient.send(command); + const userSub = signUpResponse.UserSub; + + const adminCommand: cognito.AdminConfirmSignUpCommandInput = { + UserPoolId: userPoolId as string, + Username: userSub as string, + }; + + const result = await cognitoClient.send( + new cognito.AdminConfirmSignUpCommand(adminCommand) + ); + + console.log("CONFIRM SIGNUP RESPONSE", result); + + console.log(`[${email}] - confirmed sign up`); + + return userSub as string; +}; diff --git a/cognito-lambda-dynamodb/cdk/bin/cdk.ts b/cognito-lambda-dynamodb/cdk/bin/cdk.ts new file mode 100644 index 000000000..6882df6d8 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/bin/cdk.ts @@ -0,0 +1,6 @@ +#!/usr/bin/env node +import * as cdk from "aws-cdk-lib"; +import { CdkStack } from "../lib/cdk-stack"; + +const app = new cdk.App(); +new CdkStack(app, "UserManagementStack"); diff --git a/cognito-lambda-dynamodb/cdk/cdk.json b/cognito-lambda-dynamodb/cdk/cdk.json new file mode 100644 index 000000000..48b0cb77a --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/cdk.json @@ -0,0 +1,88 @@ +{ + "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, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true, + "@aws-cdk/core:enableAdditionalMetadataCollection": true, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": true + } +} diff --git a/cognito-lambda-dynamodb/cdk/jest.config.js b/cognito-lambda-dynamodb/cdk/jest.config.js new file mode 100644 index 000000000..911fd44e1 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + testEnvironment: "node", + roots: ["/__tests__"], + testMatch: ["**/*.test.ts"], + transform: { + "^.+\\.tsx?$": "ts-jest", + }, + testTimeout: 90000, +}; diff --git a/cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts b/cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts new file mode 100644 index 000000000..5794c2ecc --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts @@ -0,0 +1,109 @@ +import * as cdk from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { AttributeType, BillingMode, Table } from "aws-cdk-lib/aws-dynamodb"; +import { Runtime } from "aws-cdk-lib/aws-lambda"; +import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as path from "path"; +import { aws_cognito as Cognito } from "aws-cdk-lib"; + +export class CdkStack extends cdk.Stack { + public readonly usersTable: Table; + public readonly addUserToTableFunc: NodejsFunction; + public readonly userPool: Cognito.UserPool; + public readonly userPoolClient: Cognito.UserPoolClient; + + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // 1. Create the DynamoDB Table + this.usersTable = new Table(this, "UsersTable", { + partitionKey: { + name: "UserID", + type: AttributeType.STRING, + }, + tableName: "Users", // optional: specify the table name + billingMode: BillingMode.PAY_PER_REQUEST, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // This function will be used as the Cognito PostConfirmation trigger + this.addUserToTableFunc = new NodejsFunction( + this, + "AddUserPostConfirmationFunc", + { + functionName: "addUserFunc", // optional + runtime: Runtime.NODEJS_20_X, + handler: "handler", + entry: path.join( + __dirname, + "./functions/AddUserPostConfirmation/handler.ts" + ), + environment: { + REGION: cdk.Stack.of(this).region, + TABLE_NAME: this.usersTable.tableName, + }, + } + ); + + // 3. Grant the Lambda permission to write to the "Users" table + this.addUserToTableFunc.addToRolePolicy( + new iam.PolicyStatement({ + actions: ["dynamodb:PutItem"], + resources: [this.usersTable.tableArn], + }) + ); + + // 4. Create the Cognito User Pool and configure it + this.userPool = new Cognito.UserPool(this, "UserPool", { + userPoolName: "USER-POOL", + selfSignUpEnabled: true, + autoVerify: { + email: true, + }, + passwordPolicy: { + minLength: 8, + requireLowercase: false, + requireUppercase: false, + requireDigits: false, + requireSymbols: false, + }, + signInAliases: { + email: true, + }, + standardAttributes: { + email: { + required: true, + mutable: true, + }, + }, + customAttributes: { + firstName: new Cognito.StringAttribute({ minLen: 1, maxLen: 50 }), + lastName: new Cognito.StringAttribute({ minLen: 1, maxLen: 50 }), + }, + + // 5. Attach our post-confirmation trigger to the Lambda + lambdaTriggers: { + postConfirmation: this.addUserToTableFunc, + }, + }); + + // 6. Create a User Pool Client + this.userPoolClient = new Cognito.UserPoolClient(this, "UserPoolClient", { + userPool: this.userPool, + authFlows: { + userPassword: true, + userSrp: true, + }, + }); + + // 7. (Optional) Output values for later reference + new cdk.CfnOutput(this, "UserPoolId", { + value: this.userPool.userPoolId, + }); + + new cdk.CfnOutput(this, "UserPoolClientId", { + value: this.userPoolClient.userPoolClientId, + }); + } +} diff --git a/cognito-lambda-dynamodb/cdk/lib/functions/AddUserPostConfirmation/handler.ts b/cognito-lambda-dynamodb/cdk/lib/functions/AddUserPostConfirmation/handler.ts new file mode 100644 index 000000000..2fad6e204 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/lib/functions/AddUserPostConfirmation/handler.ts @@ -0,0 +1,49 @@ +import { PostConfirmationConfirmSignUpTriggerEvent } from "aws-lambda"; +import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb"; +import { marshall } from "@aws-sdk/util-dynamodb"; + +// Read environment variables +const dynamoRegion = process.env.REGION; +const tableName = process.env.TABLE_NAME; + +// Throw an error if mandatory env variables aren't set +if (!dynamoRegion || !tableName) { + throw new Error( + "Missing mandatory environment variables: 'AWS_REGION' or 'TABLE_NAME'." + ); +} + +const client = new DynamoDBClient({ region: dynamoRegion }); + +export const handler = async ( + event: PostConfirmationConfirmSignUpTriggerEvent +) => { + const date = new Date(); + const isoDate = date.toISOString(); + + const params = { + TableName: tableName, + Item: marshall({ + UserID: event.request.userAttributes.sub, + Email: event.request.userAttributes.email, + firstName: + event.request.userAttributes["custom:firstName"] || + event.request.userAttributes.given_name, + lastName: + event.request.userAttributes["custom:lastName"] || + event.request.userAttributes.family_name, + CreatedAt: isoDate, + __typename: "User", + }), + }; + + try { + await client.send(new PutItemCommand(params)); + console.log("User added to DynamoDB successfully"); + } catch (error) { + console.error("Error adding user to DynamoDB:", error); + throw error; + } + + return event; +}; diff --git a/cognito-lambda-dynamodb/cdk/package.json b/cognito-lambda-dynamodb/cdk/package.json new file mode 100644 index 000000000..fd76ea4ef --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/package.json @@ -0,0 +1,34 @@ +{ + "name": "cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/cdk.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest jest --verbose ./__tests__/e2e/confirm-user-sign-up.test.ts", + "cdk": "cdk" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.148", + "@types/chance": "^1.1.6", + "@types/jest": "^29.5.14", + "@types/node": "^22.7.9", + "aws-cdk": "2.1005.0", + "esbuild": "^0.25.2", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "@aws-sdk/client-cognito-identity-provider": "^3.782.0", + "@aws-sdk/client-dynamodb": "^3.782.0", + "@aws-sdk/util-dynamodb": "^3.782.0", + "aws-cdk-lib": "2.181.1", + "chance": "^1.1.12", + "constructs": "^10.0.0", + "dotenv": "^16.4.7" + } +} diff --git a/cognito-lambda-dynamodb/cdk/test/cdk.test.ts b/cognito-lambda-dynamodb/cdk/test/cdk.test.ts new file mode 100644 index 000000000..1e6b29c85 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/test/cdk.test.ts @@ -0,0 +1,17 @@ +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as Cdk from '../lib/cdk-stack'; + +// example test. To run these tests, uncomment this file along with the +// example resource in lib/cdk-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new Cdk.CdkStack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); +}); diff --git a/cognito-lambda-dynamodb/cdk/tsconfig.json b/cognito-lambda-dynamodb/cdk/tsconfig.json new file mode 100644 index 000000000..3b3f3b309 --- /dev/null +++ b/cognito-lambda-dynamodb/cdk/tsconfig.json @@ -0,0 +1,24 @@ +{ + "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"], + "esModuleInterop": true + }, + "exclude": ["node_modules", "cdk.out"] +} diff --git a/cognito-lambda-dynamodb/example-pattern.json b/cognito-lambda-dynamodb/example-pattern.json new file mode 100644 index 000000000..8616bc19b --- /dev/null +++ b/cognito-lambda-dynamodb/example-pattern.json @@ -0,0 +1,59 @@ +{ + "title": "Step Functions to Athena", + "description": "Create a Step Functions workflow to query Amazon Athena.", + "language": "Python", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates how to use an AWS Step Functions state machine to query Athena and get the results. This pattern is leveraging the native integration between these 2 services which means only JSON-based, structured language is used to define the implementation.", + "With Amazon Athena you can get up to 1000 results per invocation of the GetQueryResults method and this is the reason why the Step Function has a loop to get more results. The results are sent to a Map which can be configured to handle (the DoSomething state) the items in parallel or one by one by modifying the max_concurrency parameter.", + "This pattern deploys one Step Functions, two S3 Buckets, one Glue table and one Glue database." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sfn-athena-cdk-python", + "templateURL": "serverless-patterns/sfn-athena-cdk-python", + "projectFolder": "sfn-athena-cdk-python", + "templateFile": "sfn_athena_cdk_python_stack.py" + } + }, + "resources": { + "bullets": [ + { + "text": "Call Athena with Step Functions", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html" + }, + { + "text": "Amazon Athena - Serverless Interactive Query Service", + "link": "https://aws.amazon.com/athena/" + } + ] + }, + "deploy": { + "text": [ + "sam deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk delete." + ] + }, + "authors": [ + { + "name": "Your name", + "image": "link-to-your-photo.jpg", + "bio": "Your bio.", + "linkedin": "linked-in-ID", + "twitter": "twitter-handle" + } + ] +} diff --git a/cognito-lambda-dynamodb/src/app.js b/cognito-lambda-dynamodb/src/app.js new file mode 100644 index 000000000..cb3c4d9c1 --- /dev/null +++ b/cognito-lambda-dynamodb/src/app.js @@ -0,0 +1,10 @@ +/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + */ + +'use strict' + +exports.handler = async (event) => { + // Lambda handler code + console.log(JSON.stringify(event, 0, null)) +} \ No newline at end of file diff --git a/cognito-lambda-dynamodb/template.yaml b/cognito-lambda-dynamodb/template.yaml new file mode 100644 index 000000000..269f82e41 --- /dev/null +++ b/cognito-lambda-dynamodb/template.yaml @@ -0,0 +1,16 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Serverless patterns - Service to Service description + +# Comment on each global +Globals: + + +# Comment each resource section to explain usage +Resources: + + +# List all common outputs for usage +Outputs: + + From 610e948e358afc817d2d1b74829687b04a8cc1b9 Mon Sep 17 00:00:00 2001 From: Vidit Shah Date: Fri, 4 Apr 2025 17:27:23 +0530 Subject: [PATCH 2/5] pattern-cognito-lambda-dynamodb --- cognito-lambda-dynamodb/README.md | 131 +++++++++++++------ cognito-lambda-dynamodb/example-pattern.json | 51 ++++---- cognito-lambda-dynamodb/src/app.js | 10 -- cognito-lambda-dynamodb/template.yaml | 16 --- 4 files changed, 117 insertions(+), 91 deletions(-) delete mode 100644 cognito-lambda-dynamodb/src/app.js delete mode 100644 cognito-lambda-dynamodb/template.yaml diff --git a/cognito-lambda-dynamodb/README.md b/cognito-lambda-dynamodb/README.md index 3ca759d23..698fa4c69 100644 --- a/cognito-lambda-dynamodb/README.md +++ b/cognito-lambda-dynamodb/README.md @@ -1,60 +1,113 @@ -# AWS Service 1 to AWS Service 2 +# Amazon Cognito to AWS Lambda to Amazon DynamoDB -This pattern << explain usage >> +This pattern demonstrates how to create a user in [Amazon Cognito](https://aws.amazon.com/cognito/), handle a **Post Confirmation** trigger using an [AWS Lambda](https://aws.amazon.com/lambda/) function, and store user details in [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). Specifically, when a user signs up and confirms their account in Cognito, the Lambda function automatically writes that user's information to a DynamoDB table. -Learn more about this pattern at Serverless Land Patterns: << Add the live URL here >> +Learn more about this pattern at Serverless Land Patterns: **<< Add the live URL here >>** -Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. +> **Important**: This application uses various AWS services and there are costs associated with these services after the Free Tier usage. Please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +--- ## Requirements -* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. -* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured -* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed +1. [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user/role that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +2. [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured. +3. [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). +4. [Node.js](https://nodejs.org/en/download/) (10.x or higher). +5. [AWS CDK Toolkit](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed (e.g., `npm install -g aws-cdk`). +6. [Docker](https://docs.docker.com/get-docker/) is recommended if you need to bundle Lambda dependencies in certain ways (though for this TypeScript example, it may not be strictly necessary). + +--- ## Deployment Instructions -1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: - ``` - git clone https://github.com/aws-samples/serverless-patterns - ``` -1. Change directory to the pattern directory: - ``` - cd _patterns-model - ``` -1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file: - ``` - sam deploy --guided - ``` -1. During the prompts: - * Enter a stack name - * Enter the desired AWS Region - * Allow SAM CLI to create IAM roles with the required permissions. - - Once you have run `sam deploy --guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults. - -1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing. +1. **Clone the GitHub Repository** + Create a new directory, navigate to that directory in a terminal, and clone the **serverless-patterns** GitHub repository: + ```bash + git clone https://github.com/aws-samples/serverless-patterns.git + ``` +2. Change directory to the pattern directory: + ``` + cd serverless-patterns/cognito-lambda-dynamodb/cdk + ``` +3. Install Dependencies: + + ``` + npm install + ``` + +4. Synthesize the AWS CloudFormation Templates: + + ``` + cdk synth + ``` + +5. Deploy the Stack + ``` + cdk deploy + ``` +6. Note the Outputs + + After deployment, CDK provides outputs such as the UserPoolId and UserPoolClientId. Make sure to save these for reference. They may be required for testing or client-side integration ## How it works -Explain how the service interaction works. +### Cognito User Pool + + - A new Amazon Cognito User Pool is created. Users can sign up using their email address. An optional User Pool Client is also created to handle authentication flows. + +### Post Confirmation Trigger + + - When a user signs up and confirms their email, Cognito invokes the Post Confirmation Lambda function (AddUserPostConfirmationFunc). + +### AWS Lambda Handler + + - The Lambda function reads attributes from the event (such as sub [the unique user ID], email, and optional name attributes). It then inserts a new item into the DynamoDB table. + +### DynamoDB Table + + - A DynamoDB table named Users is created with a primary key called UserID. The Lambda function stores user data (UserID, Email, firstName, lastName, etc.) in this table with each new sign-up. + +### Result + + - Whenever a new user confirms their email in Cognito, an entry is automatically created in the DynamoDB table with that user's information. ## Testing -Provide steps to trigger the integration and show what should be observed if successful. +## Option 1: Manual Sign-Up through Cognito + +1. In the Amazon Cognito Console: + +- Navigate to **User Pools** and select the **USER-POOL** that was created. +- Choose the **Users** section and manually create a new user or do a user sign-up using the **Hosted UI** or any relevant client (e.g., AWS Amplify). +- After confirming the user, check the **Users** table in Amazon DynamoDB Console to see if the new record appears. + +## Option 2: Automated Testing with Jest (E2E Tests) + +This project includes an end-to-end test in `cdk/__tests__/e2e/confirm-user-sign-up.test.ts`. By default, the test references environment variables in `cdk/__tests__/constants.ts`. Steps: + +1. Populate `REGION`, `USER_POOL_ID`, `CLIENT_USER_POOL_ID`, and `TABLE_NAME` in `cdk/__tests__/constants.ts` (or set them as environment variables before running tests if you prefer). +2. Run: + +```bash +npm run test +``` + +This will perform a sign-up flow using AWS SDK for Cognito, confirm the new user, and then query DynamoDB to validate that the user entry exists. ## Cleanup - + 1. Delete the stack - ```bash - aws cloudformation delete-stack --stack-name STACK_NAME - ``` + ```bash + cdk destroy + ``` 1. Confirm the stack has been deleted - ```bash - aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus" - ``` ----- -Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + ```bash + aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus" + ``` + +--- + +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 diff --git a/cognito-lambda-dynamodb/example-pattern.json b/cognito-lambda-dynamodb/example-pattern.json index 8616bc19b..2f9e4f664 100644 --- a/cognito-lambda-dynamodb/example-pattern.json +++ b/cognito-lambda-dynamodb/example-pattern.json @@ -1,59 +1,58 @@ { - "title": "Step Functions to Athena", - "description": "Create a Step Functions workflow to query Amazon Athena.", - "language": "Python", + "title": "Amazon Cognito to AWS Lambda to Amazon DynamoDB", + "description": "Create a user in Amazon Cognito, handle a Post Confirmation trigger with AWS Lambda, and store user details in Amazon DynamoDB.", + "language": "TypeScript", "level": "200", "framework": "AWS CDK", "introBox": { "headline": "How it works", "text": [ - "This sample project demonstrates how to use an AWS Step Functions state machine to query Athena and get the results. This pattern is leveraging the native integration between these 2 services which means only JSON-based, structured language is used to define the implementation.", - "With Amazon Athena you can get up to 1000 results per invocation of the GetQueryResults method and this is the reason why the Step Function has a loop to get more results. The results are sent to a Map which can be configured to handle (the DoSomething state) the items in parallel or one by one by modifying the max_concurrency parameter.", - "This pattern deploys one Step Functions, two S3 Buckets, one Glue table and one Glue database." + "This sample project demonstrates how to create a user in an Amazon Cognito User Pool, then automatically insert that user's details into a DynamoDB table once the user confirms their email. The Post Confirmation Lambda trigger handles the event from Cognito and uses the AWS SDK for JavaScript (v3) to write user data to the DynamoDB table.", + "Key attributes such as the user's unique ID (sub), email, and optional custom attributes are passed to Lambda, which then processes and persists this data. The table is configured in on-demand capacity mode (Pay Per Request) for cost efficiency and minimal management overhead.", + "This pattern deploys a Cognito User Pool, a User Pool Client, a DynamoDB table, and a Node.js AWS Lambda function as the trigger." ] }, "gitHub": { "template": { - "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sfn-athena-cdk-python", - "templateURL": "serverless-patterns/sfn-athena-cdk-python", - "projectFolder": "sfn-athena-cdk-python", - "templateFile": "sfn_athena_cdk_python_stack.py" + "repoURL": "https://github.com/aws-samples/serverless-patterns", + "templateURL": "serverless-patterns/cognito-lambda-dynamodb", + "projectFolder": "cognito-lambda-dynamodb/cdk", + "templateFile": "lib/cdk-stack.ts" } }, "resources": { "bullets": [ { - "text": "Call Athena with Step Functions", - "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html" + "text": "Amazon Cognito - User sign-up/sign-in with triggers", + "link": "https://aws.amazon.com/cognito/" }, { - "text": "Amazon Athena - Serverless Interactive Query Service", - "link": "https://aws.amazon.com/athena/" + "text": "AWS Lambda - Serverless compute for triggered actions", + "link": "https://aws.amazon.com/lambda/" + }, + { + "text": "Amazon DynamoDB - Fast and flexible NoSQL database", + "link": "https://aws.amazon.com/dynamodb/" } ] }, "deploy": { - "text": [ - "sam deploy" - ] + "text": ["cdk synth", "cdk deploy"] }, "testing": { "text": [ - "See the GitHub repo for detailed testing instructions." + "See the GitHub repo for end-to-end tests using Jest and manual sign-up steps." ] }, "cleanup": { - "text": [ - "Delete the stack: cdk delete." - ] + "text": ["Delete the stack: cdk destroy."] }, "authors": [ { - "name": "Your name", - "image": "link-to-your-photo.jpg", - "bio": "Your bio.", - "linkedin": "linked-in-ID", - "twitter": "twitter-handle" + "name": "Vidit Sah", + "linkedin": "www.linkedin.com/in/vidit-shah", + "twitter": "https://x.com/Vidit_210/", + "image": "https://media.licdn.com/dms/image/v2/D4D03AQHbL_7ZCYfUGQ/profile-displayphoto-shrink_200_200/B4DZUXcQlTGkAY-/0/1739855039564?e=2147483647&v=beta&t=MhOEFqsUDaKnLypK8eYYRqqD8Uq9xHUnijO5tN-fMpc" } ] } diff --git a/cognito-lambda-dynamodb/src/app.js b/cognito-lambda-dynamodb/src/app.js deleted file mode 100644 index cb3c4d9c1..000000000 --- a/cognito-lambda-dynamodb/src/app.js +++ /dev/null @@ -1,10 +0,0 @@ -/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: MIT-0 - */ - -'use strict' - -exports.handler = async (event) => { - // Lambda handler code - console.log(JSON.stringify(event, 0, null)) -} \ No newline at end of file diff --git a/cognito-lambda-dynamodb/template.yaml b/cognito-lambda-dynamodb/template.yaml deleted file mode 100644 index 269f82e41..000000000 --- a/cognito-lambda-dynamodb/template.yaml +++ /dev/null @@ -1,16 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: Serverless patterns - Service to Service description - -# Comment on each global -Globals: - - -# Comment each resource section to explain usage -Resources: - - -# List all common outputs for usage -Outputs: - - From 93aa84d9fa0785a3a8e8d89a46a959a80eba9f95 Mon Sep 17 00:00:00 2001 From: Vidit Shah <80155713+proton0210@users.noreply.github.com> Date: Fri, 18 Apr 2025 01:21:27 +0530 Subject: [PATCH 3/5] Update example-pattern.json --- cognito-lambda-dynamodb/example-pattern.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cognito-lambda-dynamodb/example-pattern.json b/cognito-lambda-dynamodb/example-pattern.json index 2f9e4f664..a068c1796 100644 --- a/cognito-lambda-dynamodb/example-pattern.json +++ b/cognito-lambda-dynamodb/example-pattern.json @@ -49,7 +49,8 @@ }, "authors": [ { - "name": "Vidit Sah", + "name": "Vidit Shah", + "bio":"Software Engineer working @ServerlessCreed,making Serverless Courses and workshops" "linkedin": "www.linkedin.com/in/vidit-shah", "twitter": "https://x.com/Vidit_210/", "image": "https://media.licdn.com/dms/image/v2/D4D03AQHbL_7ZCYfUGQ/profile-displayphoto-shrink_200_200/B4DZUXcQlTGkAY-/0/1739855039564?e=2147483647&v=beta&t=MhOEFqsUDaKnLypK8eYYRqqD8Uq9xHUnijO5tN-fMpc" From 91a3c21c1f3bcac908cfc09366aae3a200e46fe8 Mon Sep 17 00:00:00 2001 From: Vidit Shah <80155713+proton0210@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:07:31 +0530 Subject: [PATCH 4/5] Update cdk-stack.ts Updated deletion property for cognito --- cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts b/cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts index 5794c2ecc..682afa8a2 100644 --- a/cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts +++ b/cognito-lambda-dynamodb/cdk/lib/cdk-stack.ts @@ -58,6 +58,8 @@ export class CdkStack extends cdk.Stack { this.userPool = new Cognito.UserPool(this, "UserPool", { userPoolName: "USER-POOL", selfSignUpEnabled: true, + deletionProtection: false, + removalPolicy: cdk.RemovalPolicy.DESTROY, autoVerify: { email: true, }, From cc3be693757c10017f8678dc82e5598926ba29c1 Mon Sep 17 00:00:00 2001 From: Manju Arakere <23362982+marakere@users.noreply.github.com> Date: Thu, 24 Apr 2025 10:04:43 -0400 Subject: [PATCH 5/5] Create cognito-lambda-dynamodb.json --- .../cognito-lambda-dynamodb.json | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 cognito-lambda-dynamodb/cognito-lambda-dynamodb.json diff --git a/cognito-lambda-dynamodb/cognito-lambda-dynamodb.json b/cognito-lambda-dynamodb/cognito-lambda-dynamodb.json new file mode 100644 index 000000000..67ac93d4f --- /dev/null +++ b/cognito-lambda-dynamodb/cognito-lambda-dynamodb.json @@ -0,0 +1,94 @@ +{ + "title": "Amazon Cognito to AWS Lambda to Amazon DynamoDB", + "description": "Create a user in Amazon Cognito, handle a Post Confirmation trigger with AWS Lambda, and store user details in Amazon DynamoDB.", + "language": "TypeScript", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates how to create a user in an Amazon Cognito User Pool, then automatically insert that user's details into a DynamoDB table once the user confirms their email. The Post Confirmation Lambda trigger handles the event from Cognito and uses the AWS SDK for JavaScript (v3) to write user data to the DynamoDB table.", + "Key attributes such as the user's unique ID (sub), email, and optional custom attributes are passed to Lambda, which then processes and persists this data. The table is configured in on-demand capacity mode (Pay Per Request) for cost efficiency and minimal management overhead.", + "This pattern deploys a Cognito User Pool, a User Pool Client, a DynamoDB table, and a Node.js AWS Lambda function as the trigger." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns", + "templateURL": "serverless-patterns/cognito-lambda-dynamodb", + "projectFolder": "cognito-lambda-dynamodb/cdk", + "templateFile": "lib/cdk-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon Cognito - User sign-up/sign-in with triggers", + "link": "https://aws.amazon.com/cognito/" + }, + { + "text": "AWS Lambda - Serverless compute for triggered actions", + "link": "https://aws.amazon.com/lambda/" + }, + { + "text": "Amazon DynamoDB - Fast and flexible NoSQL database", + "link": "https://aws.amazon.com/dynamodb/" + } + ] + }, + "deploy": { + "text": [ + "cdk synth", + "cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for end-to-end tests using Jest and manual sign-up steps." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk destroy." + ] + }, + "authors": [ + { + "name": "Vidit Shah", + "bio": "Software Engineer working @ServerlessCreed,making Serverless Courses and workshops", + "linkedin": "www.linkedin.com/in/vidit-shah", + "twitter": "https://x.com/Vidit_210/", + "image": "https://media.licdn.com/dms/image/v2/D4D03AQHbL_7ZCYfUGQ/profile-displayphoto-shrink_200_200/B4DZUXcQlTGkAY-/0/1739855039564?e=2147483647&v=beta&t=MhOEFqsUDaKnLypK8eYYRqqD8Uq9xHUnijO5tN-fMpc" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "cognito", + "label": "Amazon Cognito" + }, + "icon2": { + "x": 50, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "icon3": { + "x": 80, + "y": 50, + "service": "dynamodb", + "label": "Amazon DynamoDB" + }, + "line1": { + "from": "icon1", + "to": "icon2", + "label": "" + }, + "line2": { + "from": "icon2", + "to": "icon3", + "label": "" + } + } +}