Amazon Connect - Invoke Lambda - aws-lambda

get_customer_input
ReasonForCalling_Play_prompt
Lambda function returns properly when testing Lex chatbot.
Invoking the function through Amazon Connect results in an Error.
Any ideas on how to save the return from Lambda in Connect with proper formatting? * Updated to add updated lambda code and Lex configuration image.
Lambda Code:
console.log('Loading event');
var AWS = require('aws-sdk');
var db = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
exports.handler = (event, context, callback) => {
var ssn = event.currentIntent.slots.userSSN;
var mySecret = event.currentIntent.slots.secretWord;
var params = {
TableName: 'users',
Key: {
"fourDigSSN": {
"N": ssn
},
"mySecretWord": {
"S": mySecret
}
},
AttributesToGet: ["accountBalance"]
};
db.getItem(params, function(err, data) {
if (err) {
console.log(" It didn't work and here is the error " + err); // an error occurred
}
else
callback(null, {
"sessionAttributes":{
"accountBal": data.Item.accountBalance.N
},
"dialogAction": {
"type": "ConfirmIntent",
"message": {
"contentType": "PlainText",
"content": "Your account has been verified. Your account balance is " + data.Item.accountBalance.N + "."
},
"intentName": "ReasonForCalling",
"slots": {
"userSSN": ssn,
"secretWord": mySecret
}
}
});
// var accountBal = data.Item.accountBalance.N;
// //console.log("GetDBItem succeeded:", JSON.stringify(data, null, 2));
// callback(null, {accountBalance : accountBal});
});
};
[lex_configuration][1]
[connect_contact_flow][2]
[connect_lambda_Details][3]

Your Amazon Connect contact flow appears to be referencing $.External.(something) after matching the "ReasonForCalling" intent. $.External is only used if Amazon Connect is calling upon a Lambda function directly. In your case, Amazon Connect is interacting with Lex. Lex is calling Lambda, so any response you get back to Amazon Connect will be coming from Lex, not Lambda. In order for Amazon Connect to read back something from Lex, you will need to use one of the following in your Play prompt step:
$.Lex.IntentName
$.Lex.Slots.(slotName)
$.Lex.SessionAttributes.(sessionAttributeKey)
Option1: You can configure Lex to insert the Account Balance into a slot called "AccountBal", then "return parameters to client". From there, Amazon Connect can access that value as $.Lex.Slots.AccountBal.
Option2: If you want Amazon Connect to interact directly to Lambda, you can use Lex to collect userSSN and secretWord and then build a separate Lambda function that Amazon Connect calls directly using an "Invoke AWS Lambda Function" step to perform the database lookup. With this method, you would be receiving a response directly from Lambda and can reference it as $.External.accountBalance.
EDITED ----------
Your callback has DialogAction type set as "ConfirmIntent" so Lex is most likely still expecting some user response. I doubt Amazon Connect is even getting anything back from Lex. Try updating the callback to something like the following so Lex will finish fulfillment and return to Amazon Connect:
callback(null, {
"sessionAttributes":{
"accountBal": data.Item.accountBalance.N
},
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "Your account has been verified. Your account balance is " + data.Item.accountBalance.N + "."
}
}
});

Related

NodeJS: AWS SDK V3: Not receiving any response data from lambda function

I'm trying to use the v3 javascript sdk to invoke a AWS Lambda function, and I'm having problems getting any meaningful response.
My code looks like so...
const { Lambda } = require("#aws-sdk/client-lambda");
const client = new Lambda();
const params = {
FunctionName: "MyLamdaFuncton",
Payload: JSON.stringify({ "action": "do_something" }),
InvocationType: "Event"
};
client.invoke(params)
.then((response) => {
console.log(JSON.stringify(response,null,4));
})
.catch((err) => {
console.error(err);
})
I can confirm from checking the CloudWatch logs that the lambda function works as exepcted. However this is the response I get in my NodeJS code...
{
"$metadata": {
"httpStatusCode": 202,
"requestId": "d6ba189d-9156-4f01-bd51-efe34a66fe34",
"attempts": 1,
"totalRetryDelay": 0
},
"Payload": {}
}
How do I get the actual response and status from the Lambda function?
If I change the payload above to intentionally throw an exception in my Lambda, the response in the console is still exactly the same.
update:
The Lambda function is written in Ruby. The response is returned like so...
{ statusCode: 200, body: JSON.generate(response.success?) }
where "response" is from another service it calls internally.
I've figured out what I was doing wrong. The issue was the "InvocationType". I got it working by changing to...
InvocationType: "RequestResponse"
Then I had to extract the response data like so...
const response_data = JSON.parse(new TextDecoder("utf-8").decode(response.Payload))

How to call one lambda from another in AWS SAM

I'm writing application with multiple functions inside SAM application. I can invoke lambda function that is already deployed to AWS with code similar to AWS Lambda call Lambda but it doens't work with local functions. I tried things from https://github.com/awslabs/aws-sam-cli/issues/510 but nothing seem to work yet.
This is closest I got yet (you need sam local start-lambda --host 172.17.0.1 where host is in docker network)
var AWS = require("aws-sdk");
exports.lambdaHandler = async (event, context) => {
let lambda = new AWS.Lambda({});
if (process.env.AWS_SAM_LOCAL) {
var ep = new AWS.Endpoint("http://172.17.0.1:3001");
lambda = new AWS.Lambda({ endpoint: ep });
}
const body = await new Promise(r => {
lambda.invokeAsync(
{
FunctionName: "myFunction",
InvokeArgs: JSON.stringify({ arguments: "for other function" })
},
function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
r(data);
}
);
});
response = {
statusCode: 200,
body: JSON.stringify(body)
};
return response;
};
and at least I see some activity, but I get error in invokeAsync
PathNotFoundLocally: PathNotFoundException\n at Object.extractError (/var/task/node_modules/aws-sdk/lib/protocol/json.js:51:27)\n
and this error in start-lambda console
2019-12-20 16:07:02 172.17.0.7 - - [20/Dec/2019 16:07:02] "POST /2014-11-13/functions/myFunction/invoke-async/ HTTP/1.1" 404 -
UPDATE:
I was able to fix ssl error that I had in first version of this question, and it looks like it works in sync mode with lambda.invoke
It looks like this is not currently supported until https://github.com/awslabs/aws-sam-cli/pull/749 is merged

Sending message from Cognito triggers

I want to restrict user sign-ins from Cognito hosted UI. I can see there are triggers in which we can attach lambda, but whenever I change event object inside of lambda, instead of getting my custom message User exceeded limits, I get unrecognizable lambda output error.
Can anyone help me in this or is there any other way to achieve this functionality?
Now,I'm getting this
with this code :
exports.handler = (event, context, callback) => {
if (true) {
var error = new Error("Cannot signin because your signin count is 5");
// Return error to Amazon Cognito
callback(error, event);
}
// Return to Amazon Cognito
callback(null, event);
};
But,I don't want prefix PreAuthentication failed with error,I just want to display my message.
Any help is appreciated.
Currently, there is no way to stop Cognito from adding the prefix because the form is a hosted web UI.
If this is a hard requirement, the workaround is to create your own login form and use the aws-cognito-sdk
Once you make the call to cognitoUser.authenticateUser in the code below the Pre authentication trigger will fire the Lambda function and you will need to handle the error and parse it to remove the unwanted prefix.
Hope this Helps
aws Examples: Using the JavaScript SDK
var authenticationData = {
Username : 'username',
Password : 'password',
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = { UserPoolId : 'us-east-1_TcoKGbf7n',
ClientId : '4pe2usejqcdmhi0a25jp4b5sh3'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username : 'username',
Pool : userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var accessToken = result.getAccessToken().getJwtToken();
/* Use the idToken for Logins Map when Federating User Pools with identity pools or when passing through an Authorization Header to an API Gateway Authorizer*/
var idToken = result.idToken.jwtToken;
},
//Your message from the Lambda will return here, you will need to parse the err to remove the unwanted prefix*
onFailure: function(err) {
alert(err);
},
});

AWS lambda function failing in Amazon Connect

I have written an AWS lambda function in node.js to send an email, which is invoked in an Amazon Connect contact flow. In the error branch it plays a prompt saying "lambda function failed." I verified the IAM role has permission to send email with SES, the sender/receiver emails are verified in SES, and also the lambda function has permissions for Amazon Connect.
The email does actually get sent out, but oddly I still hear the prompt "lambda function failed." Here is the code:
"use strict";
const aws = require('aws-sdk');
const sender = "Sender Name <sender#email.com>";
const recipient = "recipient#email.com";
const subject = "ALERT: no agents are logged in";
const body_text = "There are no agents logged in";
const body_html =
`<html>
<head></head>
<body>
<h1>ALERT</h1>
<p>Ther are no agents logged in to take calls in the queue.</p>
</body>
</html>`;
const charset = "UTF-8";
let params = {
Source: sender,
Destination: {
ToAddresses: [
recipient
],
},
Message: {
Subject: {
Data: subject,
Charset: charset
},
Body: {
Text: {
Data: body_text,
Charset: charset
},
Html: {
Data: body_html,
Charset: charset
}
}
},
};
const ses = new aws.SES({apiVersion: '2010-12-01'});
exports.handler = function(event, context, callback) {
ses.sendEmail(params, function(err, data) {
if(err) {
console.log("fail");
callback(err, err.message);
}
else {
console.log("success");
callback(null);
}
});
};
I checked the cloudwatch logs and don't see any error:
00:17:24
START RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91 Version: $LATEST
00:17:24
2018-08-06T00:17:24.723Z 17f1e239-990e-11e8-96bb-a1980f44db91 success
00:17:24
END RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91
00:17:24
REPORT RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91 Duration: 226.51 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 32 MB
How do I troubleshoot this?
EDIT:
I enabled contact flow logs. In CloudWatch I noticed this:
{
"Parameters": { "FunctionArn": "arn:aws:lambda:us-west-2:769182588423:function:noAgentEmail", "TimeLimit": "8000" },
"Timestamp": "2018-08-06T06:36:31.786Z",
"ContactFlowModuleType": "InvokeExternalResource",
"Results": "The Lambda Function Returned An Error.",
"ExternalResults": { "forceClose": "false" },
"ContactId": "458027b0-d895-439e-bc06-114500dce64a",
"ContactFlowId": "arn:aws:connect:us-west-2:769182588423:instance/1e2ddedd-8335-42fe-89de-1e986fc016ef/contact-flow/2329af39-682c-4dc8-b3a2-5e7fe64de5d2"
}
What's confusing is it indicates the lambda function returned something:
"ExternalResults": { "forceClose": "false" }
But this is clearly not the case given the code. What's going on?
There's a couple of things to ensure your working Lambda function can be used for Connect.
As mentioned in the above answer, the return value should be a flat JSON object. As the guide says:
Nested and complex objects are not supported.
So, it should look something like this when you execute the lambda directly:
Response:
{
"statusPop": "success",
"id": "1",
"name": "me",
}
The property names and values need to be
alphanumeric, dash, and underscore characters.
Oh, and here's what tripped me up:
Amazon Connect sends paramters as part of a hierarchical structure, so any parameters you're expecting need to be referenced as
event['Details']['Parameters']['statusPop'];
instead of
event['statusPop'];
And don't forget to add permissions using:
aws lambda add-permission --function-name function:my-lambda-function --statement-id 1 \
--principal connect.amazonaws.com --action lambda:InvokeFunction --source-account 123456789012 \
--source-arn arn:aws:connect:us-east-1:123456789012:instance/def1a4fc-ac9d-11e6-b582-06a0be38cccf
More info and in depth details of what I mentioned here: https://docs.aws.amazon.com/connect/latest/adminguide/connect-lambda-functions.html
Your lambda needs to return a flat filed JSON object. It looks like on success your lambda callbacks with null.
Update your exports to this:
exports.handler = function(event, context, callback) {
ses.sendEmail(params, function(err, data) {
if(err) {
console.log("fail");
//callback(err, err.message);
callback(null, {"status":"error"});
}
else {
console.log("success");
callback(null, {"status":"success"});
}
});
});
More information on lambda/connect requirements here: https://docs.aws.amazon.com/connect/latest/adminguide/connect-lambda-functions.html
Note:
The output returned from the function must be a flat object of key/value pairs, with values that include only alphanumeric, dash, and underscore characters. Nested and complex objects are not supported. The size of the returned data must be less than 32 Kb of UTF-8 data.
you must return a valid JSON object such as below example
export const handler = async(event) => {
console.log("event" + JSON.stringify(event));
let amount = event['Details']['Parameters']['amount'];
console.log("amount:" + amount);
let resultMap = {
"amountValid": "valid"
};
console.log("resultMap" + JSON.stringify(resultMap));
return resultMap;
};

custom authorizers in Amazon API Gateway 500 error

I use Serverless-Authentication-boilerplate and want to map custom error response. But it always return 500 error.
authorize.js
// Authorize
function authorize(event, callback) {
let providerConfig = config(event);
try {
let data = utils.readToken(event.authorizationToken, providerConfig.token_secret);
console.log("Decrypted data: " + JSON.stringify(data));
let methodArn = event.methodArn.replace(/(GET|POST|PUT|DELETE)/g, '*').replace(/mgnt.+/g, 'mgnt/*');
console.log(`Change methodArn to: ${methodArn}`);
// TODO: handle expiration time validation
callback(null, utils.generatePolicy(
data.id, // which is $context.authorizer.principalId
'Allow',
methodArn));
} catch (err) {
console.log(err);
callback('401 Unauthenticated');
}
}
s-function.json
responses:{
"401 Unauthenticated.*": {
"statusCode": "401"
},
"default": {
"statusCode": "200",
"responseModels": {
"application/json;charset=UTF-8": "Empty"
},
"responseTemplates": {
"application/json;charset=UTF-8": ""
}
}
}
After ask to Amazon Web Services.
Unfortunately the mapping of the Authorizer is not currently configurable and every returned error from a lambda function will map to a 500 status code in API gateway. Moreover, the mapping is performed on an exact string match of the output, so, in order to return the intended 401 Error to the client, you should execute a call to 'context.fail('Unauthorized');.
Finally, I change
callback('401 Unauthenticated');
to
context.fail('Unauthorized');
and work fine.
Sharing to whom may encounter this.

Resources