How do I store user input (the utterance) from Amazon Lex? - aws-lambda

I am trying to store the user utterance from the Lex bot. I am currently using DynamoDB. I do not need to store the slot. I am using this information to build a transcript.
I tried using event['inputTranscript'] to access the user input, but I get an error message saying that the lambda is unhandled in my lex bot.
Please note that event = intent_request, for those of you familiar with AWS documentation. Also, this is a helper function in lambda. There is a response to Lex in another function (Not shown) which is called after
def write_dynamo(intent_request):
t = datetime.datetime.now
tString = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('<TABLENAME>')
ID = intent_request['userId'] + tString
intent_name = intent_request['currentIntent']['name']
response = table.put_item(
Item = {
'ID': ID,
'user': intent_request['userId'],
'time': tString,
'input': intent_request['inputTranscript']
})
return
Thank you for your time

Related

Mentioning a user in the System.History

I'm trying to add a new comment to a work item which mentions a user, but using the traditional "#adamh" as you would do on the website does not seem to work via the API.
The data updates fine, however the "#adamh" is just plain text, I need to be able to somehow chuck an identity into here. Can anyone point me in the right direction?
Thanks!
A snippet is here
const vsts = require('vso-node-api');
const item = require('vso-node-api/WorkItemTrackingApi')
const ti = require('vso-node-api/interfaces/WorkItemTrackingInterfaces');
// your collection url
const collectionUrl = "https://myArea.visualstudio.com/defaultcollection";
// ideally from config
const token = "helloWorld";
async function run() {
let authHandler = vsts.getPersonalAccessTokenHandler(token);
let connection = new vsts.WebApi(collectionUrl, authHandler);
let itemTracking = await connection.getWorkItemTrackingApi();
//Add all task data to new array
let taskData = await itemTracking.getWorkItems([15795,15796])
let newData = taskData[0]
let wijson = [
{
"op": "add",
"path": "/fields/System.History",
"value": "#adamh"
}
];
const updateItem = itemTracking.updateWorkItem(null, wijson, 15795).catch(err => {
console.log(err)
}).then(() => console.log("updated"))
return newData
}
const express = require('express')
const app = express()
app.get('/', async (req, res) => {
let data = await run()
res.send(data)
})
app.listen(3000, () => console.log('Example app listening on port 3000!'))
You can use the format shown here as part of the text value for your new comment:
...
This will create a mention link to that user. The link text can be the person's name or any other text you choose to put there. An email alert will be sent to the mentioned user if your system is configured to do so (same as in the UI).
To get your users' userid strings, you can follow the method shown here.
You can use the # to notify another team member about the discussion. Simply type # and their name.
It's using the #mention control , the person you #mention will receive an email alert with your comment and a link to the work item, commit, changeset, or shelveset.
There is not any public API shows how this work in VSTS, you could try to use F12 in google browser to track the process. Another workaround is directly using API to send a notification to the user you want to mention at.

Get user attributes from Cognito User Pool through lambda

I've created a lambda that retrieves user attributes as (username, email, name...etc) however, I wonder how it's possible to get user attributes without explicitly hardcoding sub value to get all other related attributes? do I need to decode JWT Cognito token in frontend and use it in the lambda to determine the correct user and retrieve the related attributes?
here is my lambda in Node.JS:
const AWS = require('aws-sdk');
exports.handler = function(event, context) {
var cog = new AWS.CognitoIdentityServiceProvider();
var filter = "sub = \"" + "UserSUB" + "\"";
var req = {
"Filter": filter,
"UserPoolId": 'POOL here',
};
cog.listUsers(req, function(err, data) {
if (err) {
console.log(err);
}
else {
if (data.Users.length === 1){
var user = data.Users[0];
var attributes = data.Users[0].Attributes;
console.log(JSON.stringify(attributes));
} else {
console.log("error.");
}
}
});
}
I think the proper way to do this depends on whether you want to use API Gateway or not (It will make things simpler IMHO).
If you don't want to use APIG, and you are calling the lambda directly using temporary credentials, then you should pass the entire ID token and have the lambda do all of the validation and decoding (probably using a third party library for JWTs). It's not safe to do it in the frontend as that would mean you have a lambda that blindly accepts the attributes as facts from the frontend, and a malicious user could change them if they wanted.
If you are using API Gateway to put lambdas behind an API then I would create a cognito authorizer based on the User Pool, create a resource/method and configure it to use the authorizer, and enable Use Lambda Proxy Integration for the Integration Request. All the token's claims enabled for the client will be passed through on event.requestContext.authorizer.claims so long as it's valid.
There are some AWS docs here, although this does not use proxy integration. If you use proxy integration then you can skip 6b as the APIG will set the values for you. This is described in an answer here.

slack api to find existing channel

I am playing with slack apis to create an integration. I am able to sucessfully create a slack channel using
this.http.post(slackApiHost + '/channels.create', payload, {headers: headers})
.subscribe(
res => {
console.log(res);
resolve(res)
},
err => {
console.log('error:' + err)
}
)
})
the payload looks like
var payload = {
"name" : channelName
};
So, it will fail with name_taken if the channel already exists. which is great. However, I need to find the channel id of the existing channel so that i can then use it for my purpose. how do i go about it?
To get a list of all existing channel you can use conversations.list. This is the most flexible approach and allows you to retrieve any type of channel if you so choose.
If you are looking for a channel by a specific name you will need to first retrieve the list of all channels and then run your own name matching against the full list. An API method which allows you to directly search for a channel by name does not exist.
Note that if you are looking for private channels this method will only retrieve channels that you (the installer of your Slack app / your bot user) has been invited to.
There's currently no direct Slack API to find a slack channel by its name. But you can use conversations.list to handle this and here's the right code to use:
const slack = new SlackWebClient(token);
const channelNameToFind = "test";
for await (const page of slack.paginate("conversations.list", {
limit: 50,
})) {
for (const channel of page.channels) {
if (channel.name === channelNameToFind) {
console.log(`Found ${channelNameToFind} with slack id ${channel.id}`);
break;
}
}
}
This is the right way™️ to do it since it will ensure you stop listing channels as soon as you found the right one.
Good luck!
You can use built-in admin.conversations.search
and then iterate found results.
One more kind of fast tricky solution:
try to send chat.scheduleMessage and then if it was sent chat.deleteScheduledMessage or catch error.
Ruby example:
def channel_exists?(channel)
begin
response = #slack_client.chat_scheduleMessage(channel: channel, text: '', post_at: Time.now.to_i + 100)
#slack_client.chat_deleteScheduledMessage(channel: channel, scheduled_message_id: response.scheduled_message_id)
return true
rescue Slack::Web::Api::Errors::ChannelNotFound
return false
end
end

AWS Lambda API gateway with Cognito - how to use IdentityId to access and update UserPool attributes?

OK I am now days into this and have made significant progress but am still completely stumped about the fundamentals.
My application uses Cognito User Pools for creating and managing users - these are identified on S3 it seems by their IdentityId. Each of my users has their own S3 folder, and AWS automatically gives them a folder name that is equal to the user's IdentityId.
I need to relate the IdentityId to the other Cognito user information but cannot work out how.
The key thing I need is to be able to identify the username plus other cognito user attributes for a given IdentityId - and it's insanely hard.
So the first battle was to work out how to get the IdentityId when a Cognito user does a request via the AWS API Gateway. Finally I got that worked out, and now I have a Cognito user, who does a request to the API Gateway, and my Lambda function behind that now has the IdentityId. That bit works.
But I am completely stumped as to how to now access the Cognito user's information that is stored in the user pool. I can't find any clear information, and certainly no code, that shows how to use the IdentityId to get the Cognito user's attributes, username etc.
It appears that if I use a "Cognito user pool" to authorize my method in API Gateway, then the body mapping template can be used to put Cognito User information such as the sub and the username and email address into the context, BUT I do NOT get the IdentityId.
BUT if I use the AWS_IAM to authorize my method in the API gateway then the body mapping template does the inverse - it gives me the IdentityId but not the Cognito user fields such as sub and username and email.
It's driving me crazy - how can I get the IdentityId and all the Cognito users fields and attributes together into one data structure? The fact that I seem to be only able to get one or the other just makes no sense.
It turns out that to get the IdentityId AND user details at the same time using AWS Lambda/Cognito/API Gateway, you need to have a Lambda function that is authenticated using AWS_IAM (NOT COGNITO_USER_POOLS), you must send your request the AWS API Gateway, BUT it MUST be a signed request, you must then modify the integration request body mapping templates so that you are given the IdentityId in the event (maybe the context? can't remember). Now you have the IdentityId. Phew. Now you must submit the client's Cognito ID token from the front end to the back end. It is important to validate the token - you cannot trust that it has not been tampered with if you do not validate it. To decode and validate the token you must get the keys from your userpool, put them into your script, ensure that you have jwt decoding libraries plus signature validation libraries included in your AWS lambda zipfile. Now your script must validate the token submitted from the front end and then you can get the user details out of the token. Voila! Now you have both IdentityId plus user details such as their sub, username and email address. So easy.
The above is what is takes to get the username associated with an IdentityId using AWS Cognito/Lambda/API Gateway. This took me days to get working.
Can I please say to any Amazon employees who wander across this ........ well it's WAY too hard to get the user details associated with an IdentityId. You need to fix this. It made me angry that this was so hard and burned so much of my time.
The solution:
I did this by modifying an Amazon employees custom authorizer here:
https://s3.amazonaws.com/cup-resources/cup_custom_authorizer_lambda_function_blueprint.zip
as found and described here:
https://aws.amazon.com/blogs/mobile/integrating-amazon-cognito-user-pools-with-api-gateway/
use strict';
let util = require('util');
var jwt = require('jsonwebtoken');
var jwkToPem = require('jwk-to-pem');
var userPoolId = 'YOUR USERPOOL ID';
var region = 'YOUR REGION'; //e.g. us-east-1
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
//https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
// DOWNLOAD FROM https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
let userPoolKeys = {PUT YOUR DOWNLOADED USER POOL KEYS JSON HERE};
var pems = {};
let convertKeysToPems = () => {
var keys = userPoolKeys['keys'];
for(var i = 0; i < keys.length; i++) {
//Convert each key to PEM
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
pems[key_id] = pem;
}
}
exports.handler = function(event, context) {
convertKeysToPems()
console.log(event);
let token = event['body-json'].cognitoUserToken;
console.log(event['body-json'].cognitoUserToken);
ValidateToken(pems, event, context, token);
};
let ValidateToken = (pems, event, context, token) => {
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {complete: true});
console.log(decodedJwt)
if (!decodedJwt) {
console.log("Not a valid JWT token");
context.fail("Unauthorized");
return;
}
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss) {
console.log("invalid issuer");
context.fail("Unauthorized");
return;
}
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'id') {
console.log("Not an id token");
context.fail("Unauthorized");
return;
}
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log(pems, 'pems');
console.log(kid, 'kid');
console.log('Invalid token');
context.fail("Unauthorized");
return;
}
//Verify the signature of the JWT token to ensure it's really coming from your User Pool
jwt.verify(token, pem, { issuer: iss }, function(err, payload) {
if(err) {
context.fail("Unauthorized");
} else {
let x = decodedJwt.payload
x.identityId = context.identity.cognitoIdentityId
//let x = {'identityId': context['cognito-identity-id'], 'decodedJwt': decodedJwt}
console.log(x);
context.succeed(x);
}
});
}
This problem -- the problem of using the user's sub instead of their identityId in S3 paths and how to set that up -- is 100% solved for me thanks to the help of #JesseDavda's solution to this problem in this issue: https://github.com/aws-amplify/amplify-js/issues/54
For all of you developers who have been trying to get the identityId in lambdas so that your Amplify default paths in S3 work - this solution simply ends up ignoring identityId altogether - it is a solution that sets up the paths in S3 based on sub instead of the identityId. At the end of this solution, you will never have to deal with more than one id for your users, you will never have to deal with identityId (hopefully) ever again.
If I'm understanding this correctly you want the CognitoIdentityId and the User attributes in the same place. How we do it is the following way:
From the event request context we get the IdentityId:
event.requestContext.identity.cognitoIdentityId
Also from the request context we get the user's sub:
event.requestContext.identity.cognitoAuthenticationProvider.split(':CognitoSignIn:')[1]
Then with the sub you can request the rest of the attributes the following way:
const AWS = require('aws-sdk');
let cognito = new AWS.CognitoIdentityServiceProvider();
let request = {
Username: userSub,
UserPoolId: process.env.userPoolId,
};
let result = await cognito.adminGetUser(request).promise();
const userAttributes = result.UserAttributes.reduce((acc, attribute) => {
const { Name, Value } = attribute;
acc[Name] = Value;
return acc;
}, {});
return userAttributes;

Alexa skills kit, trouble getting session attributes to persist

I have been working on a skill where I am using Login With Amazon account linking so I can grab the user email address and name to use in my skill. I am doing something similar to the scoreKeeper sample, using the eventHandlers.js and the storage.js for saving items to a database. In the eventHandlers.onLaunch I am successfully getting the profile name and email address from Amazon and I save it to the session.attributes like this:
var profile = JSON.parse(body);
speechOutput="Hello, " + profile.name.split(" ")[0] + ".";
var sessionAttributes = {};
sessionAttributes = { name: profile.name, email: profile.email };
session.attributes = sessionAttributes;
console.log("Name in session:", session.attributes.name);
The console log shows the name so I know it is being saved in the session.attributes, but when I try to access the session.attributes in my storage.js or intentHandlers.js, it shows it as being empty. What am I missing? Thanks in advance. This has been driving me crazy.
I got this working by adding session to the callback of the response.ask function, like this:
ask: function (speechOutput, repromptSpeech, _session) {
this._context.succeed(buildSpeechletResponse({
session: _session,
output: speechOutput,
reprompt: repromptSpeech,
shouldEndSession: false
}));
},
It had session: this._session for saving the session, but that didn't seem to be working for me. Passing it as a variable did.
Let's say you want to obtain city name from the user and be able to access that value later in the Alexa session:
1) Pre-define your key/value pairs in the JSON response:
def build_response(session_attributes, speechlet_response):
return {
'version': '1.0',
'sessionAttributes': {
"session_attributes": {
'NameOfCity': city_name,
}
},
'response': speechlet_response
}
2) In global scope, declare the variable as an empty string:
city_name = "",
3) Whenever you update that variable within a function, make sure you reference that variable as global. For example:
def set_city_name_in_session(intent, session):
card_title = intent['name']
session_attributes = {}
should_end_session = False
if 'CityIntent' in intent['slots']:
global city_name
city_name = intent['slots']['CityIntent']['value']
4) When creating a function that needs to access this variable, pass the global variable as an argument into the function then, within the function, expose all session key/pair values like so:
def access_variables(intent, session, city_name):
card_title = intent['name']
session_attributes = {}
should_end_session = False
exposed_attributes = session['attributes']['session_attributes']
city_name_attribute_value = exposed_attributes.get('NameOfCity')
if (some_kind_of_condition):
do something awesome with city_name_attribute_value
To test, enter sample utterances into Service Simulator then check if values are persisting in your Lambda JSON response.

Resources