AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Amazon Connect chat integration. Globals: Function: Timeout: 60 MemorySize: 256 Runtime: python3.8 Api: Cors: AllowMethods: "'*'" AllowHeaders: "'*'" AllowOrigin: "'*'" Resources: chatDependencies: Type: AWS::Serverless::LayerVersion Properties: ContentUri: dependencies/ CompatibleRuntimes: - python3.10 - python3.9 - python3.8 Metadata: BuildMethod: python3.8 ActiveConnections: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: "contactId" AttributeType: "S" - AttributeName: "custID" AttributeType: "S" KeySchema: - AttributeName: "contactId" KeyType: "HASH" BillingMode: "PAY_PER_REQUEST" GlobalSecondaryIndexes: - IndexName: "custID-index" KeySchema: - AttributeName: "custID" KeyType: "HASH" Projection: ProjectionType: "ALL" dialSFRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - states.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: LogAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogDelivery - logs:CreateLogStream - logs:PutLogEvents - logs:GetLogEvents Resource: - '*' - PolicyName: InvokeLambdaFunctions PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: lambda:InvokeFunction Resource: - !GetAtt dial.Arn - !GetAtt getAvailableAgents.Arn ConnectChatLambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole Policies: - PolicyName: ConnectStartContact PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - connect:UpdateContactAttributes - connect:StartChatContact - connect:StartTaskContact - connect:StartContactStreaming - connect:UpdateContact - connect:StopContact Resource: - '*' - PolicyName: ConnectionsTableAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:DeleteItem - dynamodb:GetItem - dynamodb:Scan - dynamodb:Query - dynamodb:UpdateItem Resource: - !GetAtt ActiveConnections.Arn - Fn::Join: [ '/', [ !GetAtt ActiveConnections.Arn, 'index/custID-index' ] ] - PolicyName: TopicAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - sns:Publish - sns:Subscribe Resource: - !Ref messageExchange - PolicyName: ConfigurationAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - secretsManager:GetSecretValue Resource: - !Ref ConnectChatConfig - PolicyName: ControlStateMachine PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - states:DescribeExecution - states:StartExecution - states:StopExecution Resource: - '*' - PolicyName: s3Access PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:ListBucket Resource: - '*' encryptionkey: Type: AWS::KMS::Key Properties: Description: Connect Messages Encryption Key Enabled: True EnableKeyRotation: True KeySpec: SYMMETRIC_DEFAULT KeyPolicy: Version: 2012-10-17 Statement: - Sid: "Enable IAM User Permissions" Effect: "Allow" Principal: AWS: Fn::Join: - "" - - "arn:aws:iam::" - Ref: "AWS::AccountId" - ":root" Action: 'kms:*' Resource: '*' - Sid: "Enable Services Access" Effect: "Allow" Principal: Service: - 'connect.amazonaws.com' Action: - 'kms:GenerateDataKey*' - 'kms:Decrypt' Resource: '*' messageAPI: Type: 'AWS::ApiGateway::RestApi' Properties: Name: !Join - '' - - !Ref 'AWS::StackName' - '-messageAPI' Description: API used for sending messages from external services. FailOnWarnings: true TwilioLambdaPermission: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:invokeFunction' FunctionName: !GetAtt - twilioIncomingMessage - Arn Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref messageAPI - /* HealthcheckLambdaPermission: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:invokeFunction' FunctionName: !GetAtt - healthcheck - Arn Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref messageAPI - /* CloudAPILambdaPermission: Type: 'AWS::Lambda::Permission' Properties: Action: 'lambda:invokeFunction' FunctionName: !GetAtt - cloudAPIIncomingMessage - Arn Principal: apigateway.amazonaws.com SourceArn: !Join - '' - - 'arn:aws:execute-api:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref messageAPI - /* ApiGatewayCloudWatchLogsRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: - 'sts:AssumeRole' Policies: - PolicyName: ApiGatewayLogsPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:DescribeLogGroups' - 'logs:DescribeLogStreams' - 'logs:PutLogEvents' - 'logs:GetLogEvents' - 'logs:FilterLogEvents' Resource: '*' ApiGatewayAccount: Type: 'AWS::ApiGateway::Account' Properties: CloudWatchRoleArn: !GetAtt - ApiGatewayCloudWatchLogsRole - Arn MessageApiStage: DependsOn: - ApiGatewayAccount - TwilioMessageRequest - TwilioCallbackRequest - WhatsCloudAPIMessage - WhatsCloudAPIHealthcheck Type: 'AWS::ApiGateway::Stage' Properties: DeploymentId: !Ref ApiDeployment MethodSettings: - DataTraceEnabled: true HttpMethod: '*' LoggingLevel: INFO ResourcePath: /* RestApiId: !Ref messageAPI StageName: Prod ApiDeployment: Type: 'AWS::ApiGateway::Deployment' DependsOn: - TwilioMessageRequest - TwilioCallbackRequest - WhatsCloudAPIMessage - WhatsCloudAPIHealthcheck Properties: RestApiId: !Ref messageAPI StageName: Dev TwilioMessageResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref messageAPI ParentId: !GetAtt - messageAPI - RootResourceId PathPart: twilio TwilioMessageRequest: DependsOn: TwilioLambdaPermission Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: NONE HttpMethod: POST Integration: Type: AWS IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - twilioIncomingMessage - Arn - /invocations IntegrationResponses: - StatusCode: 204 PassthroughBehavior: 'WHEN_NO_TEMPLATES' RequestTemplates: application/json: !Join - '' - - '{' - ' "name": "$input.params(''name'')"' - '}' application/x-www-form-urlencoded: !Join - "" - - "{\n" - "#foreach( $token in $input.path('$').split('&') )\n" - " #set( $keyVal = $token.split('=') )\n" - " #set( $keyValSize = $keyVal.size() )\n" - " #if( $keyValSize >= 1 )\n" - " #set( $key = $util.urlDecode($keyVal[0]) )\n" - " #if( $keyValSize >= 2 )\n" - " #set( $val = $util.urlDecode($keyVal[1]) )\n" - " #else\n" - " #set( $val = '' )\n" - " #end\n" - " \"$key\": \"$util.escapeJavaScript($val)\"#if($foreach.hasNext),#end\n" - " #end\n" - "#end\n" - "}" RequestParameters: method.request.querystring.name: false ResourceId: !Ref TwilioMessageResource RestApiId: !Ref messageAPI MethodResponses: - StatusCode: 204 TwilioCallbackResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref messageAPI ParentId: !Ref TwilioMessageResource PathPart: callback TwilioCallbackRequest: Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: NONE HttpMethod: POST Integration: Type: MOCK IntegrationResponses: - StatusCode: 204 PassthroughBehavior: 'WHEN_NO_TEMPLATES' RequestTemplates: application/json: !Join - '' - - '{' - '"statusCode": 204' - '}' application/x-www-form-urlencoded: !Join - '' - - '{' - '"statusCode": 204' - '}' ResourceId: !Ref TwilioCallbackResource RestApiId: !Ref messageAPI MethodResponses: - StatusCode: 204 WhatsCloudAPIResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref messageAPI ParentId: !GetAtt - messageAPI - RootResourceId PathPart: cloudapi WhatsCloudAPIHealthcheck: DependsOn: HealthcheckLambdaPermission Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: NONE HttpMethod: GET MethodResponses: - StatusCode: 200 ResponseModels: text/html: Empty ResponseParameters: method.response.header.Content-Type: true Integration: Type: AWS IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - healthcheck - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Content-Type: "'text/html'" ResponseTemplates: text/html: !Join - "" - - "$input.path('$')" - "" PassthroughBehavior: 'WHEN_NO_TEMPLATES' RequestTemplates: application/json: !Join - "" - - "#set($allParams = $input.params())\n" - "{\n" - "\"params\" : { \n" - "#foreach($type in $allParams.keySet())\n" - " #set($params = $allParams.get($type))\n" - "\"$type\" : { \n" - " #foreach($paramName in $params.keySet())\n" - "\"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\n" - " #if($foreach.hasNext),#end\n" - " #end\n" - "}\n" - " #if($foreach.hasNext),#end\n" - " #end\n" - "}\n" - "}" RequestParameters: method.request.querystring.name: false ResourceId: !Ref WhatsCloudAPIResource RestApiId: !Ref messageAPI WhatsCloudAPIMessage: DependsOn: CloudAPILambdaPermission Type: 'AWS::ApiGateway::Method' Properties: AuthorizationType: NONE HttpMethod: POST Integration: Type: AWS IntegrationHttpMethod: POST Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - ':lambda:path/2015-03-31/functions/' - !GetAtt - cloudAPIIncomingMessage - Arn - /invocations IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Content-Type: "'text/html'" PassthroughBehavior: 'WHEN_NO_TEMPLATES' RequestTemplates: application/json: !Join - '' - - "{\n" - " \"body-json\": $input.json('$')\n" - "}\n" RequestParameters: method.request.querystring.name: false ResourceId: !Ref WhatsCloudAPIResource RestApiId: !Ref messageAPI MethodResponses: - StatusCode: 200 ResponseModels: text/html: Empty ResponseParameters: method.response.header.Content-Type: true messageExchange: Type: AWS::SNS::Topic Properties: DisplayName: 'Amazon Connect Chat Message Exchange' KmsMasterKeyId: !Ref encryptionkey ConnectChatConfig: Type: AWS::SecretsManager::Secret Properties: SecretString: '{"CONNECT_INSTANCE_ID":"Replace with instance ID data","CONNECT_QUEUE_ID":"Replace with queue ID data","CONTACT_FLOW_ID":"Replace with contact flow ID data","TWILIO_SID":"Replace with account SID","TWILIO_AUTH_TOKEN":"Replace with authentication token","TWILIO_FROM_NUMBER":"Replace with phone -FROM- number","WHATS_TOKEN":"Replace with whatsApp phone Token","WHATS_PHONE_ID":"Replace with whatsApp phone ID","WHATS_VERIFICATION_TOKEN":"Replace with WhatsApp verification Token"}' Tags: - Key: AppName Value: ConnectChat twilioIncomingMessage: Type: AWS::Serverless::Function Properties: Role: !GetAtt ConnectChatLambdaRole.Arn CodeUri: twilio-processExternal/ Handler: lambda_function.lambda_handler Environment: Variables: CONFIG_PARAMETER: !Ref ConnectChatConfig ACTIVE_CONNNECTIONS: !Ref ActiveConnections SNS_TOPIC: !Ref messageExchange Layers: - !Ref chatDependencies cloudAPIIncomingMessage: Type: AWS::Serverless::Function Properties: Role: !GetAtt ConnectChatLambdaRole.Arn CodeUri: cloudapi-processExternal/ Handler: lambda_function.lambda_handler Environment: Variables: CONFIG_PARAMETER: !Ref ConnectChatConfig ACTIVE_CONNNECTIONS: !Ref ActiveConnections SNS_TOPIC: !Ref messageExchange Layers: - !Ref chatDependencies healthcheck: Type: AWS::Serverless::Function Properties: Role: !GetAtt ConnectChatLambdaRole.Arn CodeUri: healthcheck/ Handler: lambda_function.lambda_handler Environment: Variables: CONFIG_PARAMETER: !Ref ConnectChatConfig Layers: - !Ref chatDependencies callbackDial: Type: AWS::Serverless::StateMachine Properties: DefinitionUri: statemachine/dial.asl.json Role: !GetAtt dialSFRole.Arn DefinitionSubstitutions: dial: !GetAtt dial.Arn getAvailableAgents: !GetAtt getAvailableAgents.Arn manualCallback: Type: AWS::Serverless::Function Properties: Role: !GetAtt ConnectChatLambdaRole.Arn CodeUri: manualcallback/ Handler: lambda_function.lambda_handler Environment: Variables: MACHINE_ID: !Ref callbackDial dial: Type: AWS::Serverless::Function Properties: Role: !GetAtt ConnectChatLambdaRole.Arn CodeUri: callback-dial/ Handler: lambda_function.lambda_handler Environment: Variables: CONFIG_SECRET: !Ref ConnectChatConfig getAvailableAgents: Type: AWS::Serverless::Function Properties: Role: !GetAtt ConnectChatLambdaRole.Arn CodeUri: getAvailableAgents/ Handler: lambda_function.lambda_handler Environment: Variables: CONFIG_SECRET: !Ref ConnectChatConfig processConnectMessage: Type: AWS::Serverless::Function Properties: Role: !GetAtt ConnectChatLambdaRole.Arn CodeUri: processConnectMessage/ Handler: lambda_function.lambda_handler Environment: Variables: CONFIG_PARAMETER: !Ref ConnectChatConfig ACTIVE_CONNNECTIONS: !Ref ActiveConnections Layers: - !Ref chatDependencies Events: incomingmessageSNS: Type: SNS Properties: Topic: !Ref messageExchange