AWSTemplateFormatVersion: '2010-09-09' Description: AWS CloudFormation stack to set up infrastructure required for the Amazon Chime SDK chat demo app with Channel flows Parameters: DemoName: Type: String Default: AWSSDKChimeChannelFlowDemo Description: Unique Name for Demo Resources Resources: #Layer for the latest AWS SDK with Amazon Chime SDK for messaging AWSSDKChimeLayer: Type: AWS::Lambda::LayerVersion Description: The AWS SDK with support for Amazon Chime SDK messaging features. Properties: CompatibleRuntimes: - "nodejs12.x" Content: S3Bucket: aws-sdk-chime-channelflowdemo-assets S3Key: aws-sdk-chime-layer.zip # Profanity and DLP processor lambda ProfanityAndDLPProcessor: Type: "AWS::Lambda::Function" Properties: Description: "Lambda that processes Chime channel messages for auto moderation" Environment: Variables: DOCUMENT_CLASSIFIER_ENDPOINT: !Sub "arn:aws:comprehend:${AWS::Region}:${AWS::AccountId}:document-classifier-endpoint/profanityfilter" FunctionName: !Sub ${DemoName}-ProfanityAndDLPProcessor Handler: "index.handler" Code: S3Bucket: aws-sdk-chime-channelflowdemo-assets S3Key: ChannelFlowProfanityDLPProcessor.zip MemorySize: 128 Role: !GetAtt ProcessorLambdaRole.Arn Runtime: "nodejs12.x" Timeout: 30 Layers: - !Ref AWSSDKChimeLayer TracingConfig: Mode: "PassThrough" ProcessorLambdaRole: Type: "AWS::IAM::Role" Properties: Path: "/" RoleName: !Sub ${DemoName}-ProcessorLambdaRole AssumeRolePolicyDocument: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" MaxSessionDuration: 3600 Description: "Allows Lambda functions to access Kinesis stream for messaging demo" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: !Sub ${DemoName}-ProcessorLambda-ComprehendAccess PolicyDocument: | { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "comprehend:BatchDetectDominantLanguage", "comprehend:DetectDominantLanguage", "comprehend:DetectPiiEntities", "comprehend:DetectSentiment", "comprehend:ClassifyDocument" ], "Resource": "*" } ] } - PolicyName: !Sub ${DemoName}-ProcessorLambda-ChimeMessagingSDKAccess PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "chime:ChannelFlowCallback" Resource: !Sub "arn:aws:chime:${AWS::Region}:${AWS::AccountId}:app-instance/*" PermissionForChimeToInvokeProfanityAndDLPProcessor: Type: AWS::Lambda::Permission DependsOn: - "ProfanityAndDLPProcessor" Properties: FunctionName: !Sub ${DemoName}-ProfanityAndDLPProcessor Action: "lambda:InvokeFunction" Principal: "chime.amazonaws.com" SourceAccount: {Ref: "AWS::AccountId"} SourceArn: !Sub "arn:aws:chime:${AWS::Region}:${AWS::AccountId}:app-instance/*" #Lambda that creates AWS Chime App instance and a Channel Flow with the Profanity processor lambda ChimeAppInstanceLambda: Type: "AWS::Lambda::Function" DependsOn: - "ProfanityAndDLPProcessor" Properties: Handler: "index.handler" Environment: Variables: PROCESSOR_LAMBDA_ARN: !GetAtt ProfanityAndDLPProcessor.Arn Role: !GetAtt LambdaExecuteRole.Arn Runtime: "nodejs12.x" Timeout: 60 Layers: - !Ref AWSSDKChimeLayer Code: ZipFile: | "use strict"; const AWS = require("aws-sdk"); const uuidv4 = require("uuid"); var response = require("cfn-response"); AWS.config.update({ region: process.env.AWS_REGION }); const chimeIdentity = new AWS.ChimeSDKIdentity({ region: process.env.AWS_REGION }); const chimeMessaging = new AWS.ChimeSDKMessaging({ region: process.env.AWS_REGION }); const {PROCESSOR_LAMBDA_ARN} = process.env; exports.handler = async (event, context, callback) => { console.log("Event: \n", event); console.log("Create Chime SDK App Instance"); if (event["RequestType"] === "Create") { //create a chime app instance var params = { Name: `AWSChimeMessagingSDKDemo-${uuidv4()}`, }; try { var chime_response = await chimeIdentity.createAppInstance( params, function (err, data) { if (err) console.log(err, err.stack); // an error occurred else { console.log(data); // successful response return data; } } ).promise(); var channelFlowParams = { Name: "Profanity and DLP Flow", ClientRequestToken: `CreateChannelFlow-${uuidv4()}`, AppInstanceArn: chime_response.AppInstanceArn, Processors: [{ ExecutionOrder: 1, Name: "ProfanityAndDLPProcessor", FallbackAction: 'ABORT', Configuration: { Lambda: { ResourceArn: PROCESSOR_LAMBDA_ARN, InvocationType: 'ASYNC' } } }] }; console.log("Creating channel flow resource"); await chimeMessaging.createChannelFlow(channelFlowParams, function (err, data) { if (err) { console.log('Error calling create channel flow'); console.log(err, err.stack); } else { console.log(data); // successful response return data; } }).promise(); await response.send(event, context, response.SUCCESS, chime_response); } catch (error) { console.log("ERROR CAUGHT \n", error); await response.send(event, context, response.FAILED, {}); } } else { //NOT A CREATE REQUEST await response.send(event, context, response.SUCCESS, {}); } }; # Trigger Lambda function to create Amazon Chime App Instance and channel flow creation TriggerChimeAppInstanceLambda: Type: AWS::CloudFormation::CustomResource DependsOn: ProfanityAndDLPProcessor Properties: ServiceToken: !GetAtt ChimeAppInstanceLambda.Arn # Creates an S3 bucket to store chat attachments ChatAttachmentsBucket: Type: AWS::S3::Bucket Properties: CorsConfiguration: CorsRules: - AllowedHeaders: - "*" AllowedMethods: - GET - HEAD - PUT - POST - DELETE AllowedOrigins: - "*" ExposedHeaders: - "x-amz-server-side-encryption" - "x-amz-request-id" - "x-amz-id-2" MaxAge: "3000" # Creates a role that allows Cognito to send SNS messages SNSRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "cognito-idp.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: !Sub ${DemoName}-CognitoSNSPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sns:publish" Resource: "*" #Creates a role to allow SignIn Lambda to execute LambdaExecuteRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${DemoName}-lambdarole AssumeRolePolicyDocument: Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: - lambda.amazonaws.com Version: 2012-10-17 ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: !Sub ${DemoName}-LambdaUserCreatePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'chime:CreateAppInstance' - 'chime:CreateAppInstanceAdmin' - 'chime:CreateAppInstanceUser' Resource: "*" - PolicyName: !Sub ${DemoName}-LambdaChannelFlowCreatePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'chime:CreateChannelFlow' Resource: !Sub "arn:aws:chime:${AWS::Region}:${AWS::AccountId}:app-instance/*" #Create Lambda used by Cognito Post Authentication Trigger to Create Chime App Instance user if user does not already exist SignInHookLambda: Type: AWS::Lambda::Function DependsOn: TriggerChimeAppInstanceLambda Properties: FunctionName: !Sub ${DemoName}-SignInHook Handler: "index.handler" Runtime: nodejs12.x MemorySize: 512 Role: !GetAtt LambdaExecuteRole.Arn Layers: - !Ref AWSSDKChimeLayer Timeout: 800 Code: ZipFile: | const AWS = require('aws-sdk'); AWS.config.update({ region: process.env.AWS_REGION }); const chimeIdentity = new AWS.ChimeSDKIdentity({ region: process.env.AWS_REGION }); const { CHIME_APP_INSTANCE_ARN } = process.env; exports.handler = async (event, context, callback) => { const username = event.userName; const userId = event.request.userAttributes.profile; // 'none' is default user profile attribute in Cognito upon registration which if (userId === 'none') { console.log(`User hasn't logged in yet and hasn't been setup with profile`); callback(null, event); } // Create a Chime App Instance User for the user const chimeCreateAppInstanceUserParams = { AppInstanceArn: CHIME_APP_INSTANCE_ARN, AppInstanceUserId: userId, Name: username }; try { console.log(`Creating app instance user for ${userId}`); await chimeIdentity .createAppInstanceUser(chimeCreateAppInstanceUserParams) .promise(); } catch (e) { console.log(JSON.stringify(e)); return { statusCode: 500, body: e.stack }; } // Return to Amazon Cognito callback(null, event); }; Environment: Variables: CHIME_APP_INSTANCE_ARN: !GetAtt TriggerChimeAppInstanceLambda.AppInstanceArn # Allows Sign In Lambda to be called by Cognito LambdaInvocationPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt SignInHookLambda.Arn Principal: cognito-idp.amazonaws.com SourceArn: !GetAtt UserPool.Arn # Creates a Cognito User Pool with a Post Authentication Trigger of the Sign In Lambda UserPool: Type: "AWS::Cognito::UserPool" DependsOn: SignInHookLambda Properties: UserPoolName: !Sub ${DemoName}-user-pool LambdaConfig: PostAuthentication: !GetAtt SignInHookLambda.Arn AutoVerifiedAttributes: - email # Creates a User Pool Client to be used by the identity pool UserPoolClient: Type: "AWS::Cognito::UserPoolClient" Properties: ClientName: !Sub ${DemoName}-client GenerateSecret: false UserPoolId: !Ref UserPool #Creates a federeated Identity pool IdentityPool: Type: "AWS::Cognito::IdentityPool" Properties: IdentityPoolName: !Sub ${DemoName}-IdentityPool AllowUnauthenticatedIdentities: true CognitoIdentityProviders: - ClientId: !Ref UserPoolClient ProviderName: !GetAtt UserPool.ProviderName # Create a role for unauthorized acces to AWS resources. Very limited access. Only allows users in the previously created Identity Pool CognitoUnAuthorizedRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Federated: "cognito-identity.amazonaws.com" Action: - "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdentityPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": unauthenticated Policies: - PolicyName: !Sub ${DemoName}-CognitoUnauthorizedPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" Resource: "*" # Create a role for authorized acces to AWS resources. Control what your user can access. # Allows users to access their s3 ChatBucket files CognitoAuthorizedRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Federated: "cognito-identity.amazonaws.com" Action: - "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdentityPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: !Sub ${DemoName}-CognitoAuthorizedPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" - Effect: "Allow" Action: - "lambda:InvokeFunction" Resource: "*" - PolicyName: !Sub ${DemoName}-AttachmentsS3PermissionPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "s3:GetObject" - "s3:PutObject" - "s3:DeleteObject" Resource: !Join ['', ['arn:aws:s3:::', !Ref ChatAttachmentsBucket, '/protected/${cognito-identity.amazonaws.com:sub}/*']] - Effect: "Allow" Action: - "s3:GetObject" Resource: !Join ['', ['arn:aws:s3:::', !Ref ChatAttachmentsBucket, '/protected/*']] - PolicyName: !Sub ${DemoName}-ChimeSDKDemoUserPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "chime:GetMessagingSessionEndpoint" Resource: "*" - Effect: "Allow" Action: - "cognito-idp:ListUsers" Resource: !Join ['', ['arn:aws:cognito-idp:us-east-1:', !Ref AWS::AccountId, ':userpool/', !Ref UserPool]] - Effect: "Allow" Action: - "chime:SendChannelMessage" - "chime:GetChannelMessage" - "chime:ListChannelMessages" - "chime:CreateChannelMembership" - "chime:ListChannelMemberships" - "chime:DeleteChannelMembership" - "chime:CreateChannelModerator" - "chime:ListChannelModerators" - "chime:DescribeChannelModerator" - "chime:CreateChannel" - "chime:DescribeChannel" - "chime:ListChannels" - "chime:UpdateChannel" - "chime:DeleteChannel" - "chime:RedactChannelMessage" - "chime:UpdateChannelMessage" - "chime:Connect" - "chime:ListChannelMembershipsForAppInstanceUser" - "chime:CreateChannelBan" - "chime:ListChannelBans" - "chime:DeleteChannelBan" - "chime:AssociateChannelFlow" - "chime:DisassociateChannelFlow" - "chime:DescribeChannelFlow" - "chime:ListChannelFlows" Resource: - !Join ['', [!GetAtt TriggerChimeAppInstanceLambda.AppInstanceArn, '/user/${cognito-identity.amazonaws.com:sub}']] - !Join ['', [!GetAtt TriggerChimeAppInstanceLambda.AppInstanceArn, '/channel/*']] - !Join ['', [!GetAtt TriggerChimeAppInstanceLambda.AppInstanceArn, '/channel-flow/*']] # Assigns the roles to the Identity Pool IdentityPoolRoleMapping: Type: "AWS::Cognito::IdentityPoolRoleAttachment" Properties: IdentityPoolId: !Ref IdentityPool Roles: authenticated: !GetAtt CognitoAuthorizedRole.Arn unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn Outputs: cognitoUserPoolId: Value: !Ref UserPool cognitoAppClientId: Value: !Ref UserPoolClient cognitoIdentityPoolId: Value: !Ref IdentityPool appInstanceArn: Value: !GetAtt TriggerChimeAppInstanceLambda.AppInstanceArn attachmentsS3BucketName: Value: !Ref ChatAttachmentsBucket