AWS - How to get username from token in a NodeJs Lamda? - node.js

I use a NodeJs Lambda after an API Gateway.
var AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'})
exports.handler = async (event) => {
const token = ...;
const username = ...;
const response = {
"username": "...",
firstname: "...",
name: "...",
email: "...",
groups: []
};
return response;
};
I add a cognito Authorizer. With token, the result is OK and whithout the result is 403. is it OK for me.
exemple:
https://123456789.execute-api.eu-west-1.amazonaws.com/v1/user/me
return:
{
"username": "foobar",
"firstname": "foo",
"name": "bar",
"email": "foobar#gmail.com",
"roles": [
"Admin"
]
}
* with foobar if foobar is in JWT and return toto if toto is in JWT
Now, I want return username of user (from JWT). My event is empty:
"event": {},

const claims = event.requestContext.authorizer.claims;
const username = claims['cognito:username'];
before use this, I need use proxyLambda
and add this for the CORS (CORB):
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(r)
};
return response;
or other solution: i do not use proxyLamba but you configure this:
after, you need decode JWT in Lambda.
const jwtToken = event.params.header.Authorization;

I don't think return is doing what you expect. Try using callback. Detail here
Node.js runtimes support the optional callback parameter. You can use
it to explicitly return information back to the caller.
callback(Error error, Object result); Both parameters are optional.
error is an optional parameter that you can use to provide results of
the failed Lambda function execution. When a Lambda function succeeds,
you can pass null as the first parameter.
result is an optional parameter that you can use to provide the result
of a successful function execution. The result provided must be
JSON.stringify compatible. If an error is provided, this parameter is
ignored.
If you don't use callback in your code, AWS Lambda will call it
implicitly and the return value is null. When the callback is called,
AWS Lambda continues the Lambda function invocation until the event
loop is empty.
var AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'})
exports.handler = function(event, context, callback) => {
const token = ...;
const username = ...;
const response = {
"username": "...",
firstname: "...",
name: "...",
email: "...",
groups: []
};
callback(null, response);
};

Related

Parsing Slack Interactive Message POST payload parameter to JSON in Azure Functions

I'm trying to create an Azure Function to take POST requests from Slack Message Interactions. I'm able to get a test request to come in following this guide using ngrok. However the payload is not coming in like a normal POST request body. Assuming this is because it's a "parameter" payload and not a body.
module.exports = async (context, req) => {
const { body } = req;
context.log(body);
context.res = {
body,
};
};
Output:
payload=%7B%22type%22%3A%22block_actions%22%2C%22user%22%3A%7B%22id%22%3A%22xxx%22%2C%22username%22%3A%22...
How do I parse this POST parameter payload into JSON in an Azure Function?
With help from this post I was able to figure this out for my use case.
Using qs package npm i qs
const { parse } = require('qs');
module.exports = async (context, req) => {
const payload = JSON.parse(parse(req.rawBody).payload);
context.log(payload);
context.res = {
payload,
};
};
Output:
{
type: 'block_actions',
user: {
id: 'xxx',
username: 'xxx',
name: 'xxx',
team_id: 'xxx'
},
api_app_id: 'xx',
...
}

How to handle POST request with Lambda Proxy Integration and API Gateway REST

I read a lot of topics about how to handle POST requests with Lambda Proxy Integration from AWS, but no one of them helps me to solve my issue.
I read that if we are using a Lambda Proxy Integration the post body have to be like this :
{
"body": "{\"name\":\"Mickael2\"}"
}
Here is my Lambda function (very basic):
const AWS = require('aws-sdk')
const dynamoDB = new AWS.DynamoDB({ region: 'eu-west-3'})
const { v4: uuidv4 } = require('uuid');
exports.handler = (event, context, cb) => {
const body = JSON.parse(event.body).body;
const name = JSON.parse(body).name;
let uuid = uuidv4();
let response = {
statusCode: 200,
headers: {
'x-custom-header': 'My Header Value',
},
body: JSON.stringify({})
};
const params = {
Item: {
"id": {
S: uuid
},
"name": {
S: name
}
},
TableName: "Users3"
};
dynamoDB.putItem(params, function(err, data) {
if (err){
console.log(err, err.stack);
cb(null, response);
}
else{
console.log(data);
response.body = JSON.stringify({name:name, uuid: uuid })
cb(null, response);
}
});
};
For example when i am trying to give this body:
{
"name":"Mickael"
}
And doing console.log(event.body) i am getting:
{
"name": "Mickael" }
But when I am trying to get access to event.body.name i am getting undefined
Does someone have any other way to work with Post request on Lambda Proxy Integration? Thanks.
I see that you are using the Lambda function to push into DynamoDB. Since you're asking about other ways to handle it, if this Lambda function isn't doing anything else (and assuming you're using API Gateway to kick off the Lambda Function), my recommendation is to skip Lambda altogether and just go directly from APIGateway to DynamoDB. In your APIGW Integration Request, set it to use AWS Service instead of Lambda, then select DynamoDB as the service, Set the HTTP Method to POST, and set the Action to PutItem. And for the Integration Request Mapping Template, build out that same JSON as you are doing above {Item: {"id": {S: uuid}, "name": {S: name}}, TableName: "Users3"}.
I read that if we are using a Lambda Proxy Integration the post body have to be like this:
{
"body": "{\"name\":\"Mickael2\"}"
}
Depends what you mean by "post body", is that what you're sending or what you're receiving? If you're sending it, you can structure it however you want. If you're receiving it (i.e. in the lambda function), the structure will be as in your example, i.e. a body key in the event object, containing the stringified payload.
const body = JSON.parse(event.body).body;
If you're parsing the body, and the body is "{\"name\":\"Mickael2\"}" (i.e. stringified payload), then there is no body key/property on the resultant object, and therefore const body === undefined.
If you actually sent { body: "{\"name\":\"Mickael2\"}" } as the payload, which you shouldn't, then that code is valid.
const name = JSON.parse(body).name;
Depending on the actual payload, body is either an object, undefined, or a string at this point. If it's an object or undefined then parsing it again should throw an exception. If it's not doing so then perhaps it's because you are in fact supplying a payload of { body: "{\"name\":\"Mickael2\"}" }, in which case that code is valid, but as stated it's the wrong way to send the payload.
Your payload should be:
{
name: "Mickael2"
}
When it goes through proxy integration into your lambda, the event object will look like so (irrelevant bits removed):
{
body: "{\"name\":\"Mickael2\"}"
}
So this is how you should handle it in your lambda:
const payload = JSON.parse(event.body);
const name = payload.name;

Coinbase web API through Node fetch "invalid API key" (not Coinbase pro)

Just trying to get my Coinbase balance. I have tried making a bunch of different API keys, keep getting the same error:
{
"errors": [{
"id": "authentication_error",
"message": "invalid api key"
}]
}
Im using Node.js through Netlify Lambda functions.
Here's my code:
import fetch from "node-fetch"
import crypto from "crypto"
const mykey = '<KEY>'
const mysecret = '<SECRET>'
exports.handler = async (event, context) => {
const url = `https://api.coinbase.com/v2/accounts`
var nonce = Math.floor(new Date().getTime() * 1e-3)
var my_hmac = crypto.createHmac('SHA256', nonce+'POST'+'v2/accounts', mysecret)
my_hmac.update(nonce + url)
var signature = my_hmac.digest('hex')
var msg;
return fetch(url, { headers:
{
'CB-ACCESS-KEY' : mykey,
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': nonce,
'Content-Type': 'application/json'
}
}).then(res => {
// console.log(res)
res.json
})
.then(data => {
return ({
statusCode: 200,
body: JSON.stringify(data)
})
})
}
You are using the wrong names for the tokens.
ACCESS_KEY is supposed to be CB-ACCESS-KEY
ACCESS_SIGNATURE is supposed to be CB-ACCESS-SIGN
I couldn't find info about the nonce. I found this over here.
Update:
signature looks like it is not made properly:
The nonce+'POST'+'/v2/accounts' is supposed to be the value in my_hmac.update
In turn for the createHmac it is only supposed to be SHA256 and mysecret
The signature pre-hash value is supposed to have a / at the beginning
A useful reference is
here (be sure to click node.js at the top).

Accessing appsync via lambda produces unauthorized even with proper role

I'm trying to call appsync from a lambda function that I set up using aws amplify. I can tell that my lambda function has read/write permission for appsync, but when I make the POST request from lambda to appsync, I get a Unable to parse JWT token error. The weird thing is that when I look at the header, I don't see the authorization jwt that I see when I am requesting from the web application, so that could be why I'm seeing this error. Instead, I see an x-amz-security-token and a different type of authorization string that you can see in the image below.
My code is pulled from a blog I found from Adrian Hall:
const env = require('process').env
const fetch = require('node-fetch')
const URL = require('url')
const AWS = require('aws-sdk')
AWS.config.update({
region: env.AWS_REGION,
credentials: new AWS.Credentials(
env.AWS_ACCESS_KEY_ID,
env.AWS_SECRET_ACCESS_KEY,
env.AWS_SESSION_TOKEN
),
})
exports.handler = (event, context, callback) => {
const ListCourses = `query ListCourses(
$filter: ModelTodoFilterInput
$limit: Int
$nextToken: String
) {
listCourses(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
}
nextToken
}
}`
// const details = {
// userId: event.request.userAttributes.sub,
// userDetails: {
// name: event.request.userAttributes.name,
// },
// }
const post_body = {
query: ListCourses,
operationName: 'ListCourses',
variables: details,
}
console.log(env)
console.log(`Posting: ${JSON.stringify(post_body, null, 2)}`)
// POST the GraphQL mutation to AWS AppSync using a signed connection
const uri = URL.parse(env.API_GRAPHQLAPIENDPOINTOUTPUT)
const httpRequest = new AWS.HttpRequest(uri.href, env.REGION)
httpRequest.headers.host = uri.host
httpRequest.headers['Content-Type'] = 'application/json'
httpRequest.method = 'POST'
httpRequest.body = JSON.stringify(post_body)
AWS.config.credentials.get(err => {
const signer = new AWS.Signers.V4(httpRequest, 'appsync', true)
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate())
const options = {
method: httpRequest.method,
body: httpRequest.body,
headers: httpRequest.headers,
}
console.log('here is the uri and options')
console.log(uri.href)
console.log(options)
fetch(uri.href, options)
.then(res => res.json())
.then(json => {
console.log(`JSON Response = ${JSON.stringify(json, null, 2)}`)
callback(null, event)
})
.catch(err => {
console.error(`FETCH ERROR: ${JSON.stringify(err, null, 2)}`)
callback(err)
})
})
}
Does anyone know why the credentials methods are authorizing the way that they are and how I can fix this UnauthorizedException error? Just to sanity check me, in amplify, I did select that I wanted this lambda function to have read/write access and I can see in the CF template that:
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:Create*",
"appsync:StartSchemaCreation",
"appsync:GraphQL",
"appsync:Get*",
"appsync:List*"
],
"Resource": [
{
"Fn::Join": [
"",
[
"arn:aws:appsync:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":apis/",
{
"Ref": "apiGraphQLAPIIdOutput"
},
"/*"
]
]
}
]
}
]
}
You would have to use AWS_IAM as the authorization mode for your API if you want to call it from the lambda. Based on the error, it seems your API is setup to use AMAZON_COGNITO_USER_POOLS as the authorization. If you want to mix the 2 in your API, you might want to look at the following blog:
https://aws.amazon.com/blogs/mobile/using-multiple-authorization-types-with-aws-appsync-graphql-apis/

Nodejs API call returning undefined to lambda function

This is the aws lambda function which will invoke an api:
'use strict';
var request = require("request")
exports.handler = function (event, context,callback) {
let url = "https://3sawt0jvzf.execute-api.us-east-1.amazonaws.com/prod/test"
request({
url: url,
method: "POST",
json: event,
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
callback(null, { "isBase64Encoded": true|false,
"statusCode": "200",
"headers": { "headerName": "headerValue"},
"body": body});
}
else {
console.log("error: " + error)
console.log("response.statusCode: " + response.statusCode)
console.log("response.statusText: " + response.statusText)
}
})
};
This is the api written as an aws lambda function:
'use strict';
exports.handler = function(event, context, callback) {
console.log(event.name);
callback(null, { "isBase64Encoded": true|false,
"statusCode": "200",
"headers": { "headerName": "headerValue"},
"body": `Hello World ${event.name}`}); // SUCCESS with message
};
When I try to call the api from the lambda function it just returns "Hello World undefined". It is not appending the name at the end and returning the correct response.
Assumptions:
You're using Lambda-Proxy Integration.
You want to pass the the exact same payload that the first Lambda received to the second Lambda.*
You're misunderstanding what event is. This is NOT the JSON payload that you sent through your HTTP request.
An HTTP request through the API Gateway gets transformed into an event object similar to this:
{
"resource": "Resource path",
"path": "Path parameter",
"httpMethod": "Incoming request's method name"
"headers": {Incoming request headers}
"queryStringParameters": {query string parameters }
"pathParameters": {path parameters}
"stageVariables": {Applicable stage variables}
"requestContext": {Request context, including authorizer-returned key-value pairs}
"body": "A JSON string of the request payload."
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}
As you can see, the JSON payload is accessible in a stringified form in event.body.
If you want to send the pass the same payload to the second Lambda, you have to parse it first.
const body = JSON.parse(event.body)
Then, send body instead of event.
Then, in your second Lambda, you parse the stringified JSON in event.body and then you get your original payload back.
If you sent name in that original payload, you can get it from JSON.parse(event.body).name.
Reference: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-input-format
Had a similar problem and debugged with logging event to console.
Add logging on the event,
console.log(JSON.stringify(event));
to evaluate how mapping is done in your API-Gateway to Lambda integration and see where the post parameter exists.
If the post value is not there fix the integration until you get the post values in your event.
Data Mapping API-Gateway to Lambda:
http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
Hope it helps.
I had some very similar problem. When trying to call JSON.parse() on the event.body, I'm getting an error because event is already the object that I'm trying to POST. I haven't actually linked my database to my front end, but I have this error while testing from Lambda and from API Gateway.
Example of how I'm being able only now to read the object from my request:
"use strict";
const AWS = require("aws-sdk");
exports.handler = async (event, context) => {
const documentClient = new AWS.DynamoDB.DocumentClient();
let responseBody;
let statusCode;
console.log(event.id) // I have access to dot notation straight from the event
const {id, item, quantity, orderTotal, userId} = event;

Resources