Cross origin resource sharing in AWS lambda proxy integration - amazon-web-services

Been browsing through SO for the past hours to find a fix for my issue, but no progress yet, I'm getting
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Now typically, this could be fixed with adding appropriate headers to the code, and it would work, however it's not the case for me, since I've tried to configure cors through API Gateway on AWS.
Screenshot:
Some research on google mentioned, that if the function is using lambda proxy integration, we would have to modify the lambda itself, and add the headers by our own, e.g
headers: {
'Access-Control-Allow-Origin': '*',
},
However this doesn't make much difference, is there anything I'm missing?
My actual code for the lambda (forgot to add):
const rp = require('request-promise')
const sendEmail = require('./sendEmail')
module.exports.run = async (event, context, callback) => {
const body = JSON.parse(event.body)
const { name, email, budget, message, attachment } = body
if (!name) {
return callback(null, {
statusCode: 400,
body: JSON.stringify({ message: 'Name is required' }),
})
}
if (!email) {
return callback(null, {
statusCode: 400,
body: JSON.stringify({ message: 'Email address is required' }),
})
}
if (!message) {
return callback(null, {
statusCode: 400,
body: JSON.stringify({ message: 'Message is required' }),
})
}
return Promise.all([
sendEmail({
to: 'Example <user#example.com>',
subject: 'New enquiry received!',
data:
`Name: ${name}\n` +
`Email: ${email}\n` +
`Budget: ${budget || 'n/a'}\n` +
`Attachment: ${attachment || 'n/a'}\n` +
`\n${message}`,
}),
sendEmail({
to: `${name} <${email}>`,
subject: 'Your message was delivered at ',
data:
'Thanks for reaching out!\n' +
'Somebody at our office will get back to you as soon as possible.\n' +
'\n' +
'While you wait, check out our Handbook (/) and get acquainted with how we do things around here.\n' +
'We have a lot of content there so feel free to explore as you please.\n' +
'\n' +
'Speak soon,\n' +
'\n',
}),
rp({
method: 'POST',
uri: `https://hooks.slack.com/services/${process.env.SLACK_PATH}`,
json: true,
body: {
text: `<!channel> New enquiry received`,
attachments: [
{
fallback: 'Information:',
pretext: 'Information:',
color: '#FF5050',
fields: [
{ title: 'Name', value: name, short: false },
{ title: 'Email', value: email, short: false },
{ title: 'Budget', value: budget || 'n/a', short: false },
{ title: 'Attachment', value: attachment || 'n/a', short: false },
{ title: 'Message', value: message || 'n/a', short: false },
],
},
],
},
}),
])
.then(() => {
return callback(null, {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ message: 'Great success' }),
})
})
.catch(err => {
return callback(null, {
statusCode: 500,
body: JSON.stringify({
message: 'Oh no :( Message not delivered',
error: err
}),
})
})
}

No 'Access-Control-Allow-Origin' header is present on the requested resource.
This is saying that the resource you requested, your Lambda via API Gateway, is not returning an Access-Control-Allow-Origin header in its response; the browser is expecting the CORS headers in the response from the API (possibly because of an OPTIONS request), but the response doesn’t have them.
To solve your issue, add a Access-Control-Allow-Origin: * header to the response your Lambda returns. Using the first item you're returning:
if (!name) {
return callback(null, {
statusCode: 400,
headers: {
'Access-Control-Allow-Origin': '*',
// any other required headers
},
body: JSON.stringify({ message: 'Name is required' }),
})
}
Worth noting that you'll have to add those headers to every response.

Related

Why do I get "The request signature we calculated does not match the signature you provided." for GET but not PUT for OpenSearch?

I am following this guide for signing HTTP requests to an Amazon OpenSearch Service using Node.js (version 3 of the AWS SDK for JavaScript).
When I copy the exact sample code and export my AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY of my authorised user, the PUT index/type/id request to add an item is successful:
201 Created
Response body: {"_index":"products","_type":"_doc","_id":"2","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":0,"_primary_term":1}
However, when I change the request to instead GET /_search endpoint, I get:
403 Forbidden
Response body: {"message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."}
The user is fully authorised against the index:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::**:user/aws-elasticbeanstalk-ec2-user"
},
"Action": "es:*",
"Resource": "arn:aws:es:ap-southeast-2:**:domain/mydomain/*"
},
How do I rectify my signature?
Here is my modified code from the above link:
const { HttpRequest } = require('#aws-sdk/protocol-http')
const { defaultProvider } = require('#aws-sdk/credential-provider-node')
const { SignatureV4 } = require('#aws-sdk/signature-v4')
const { NodeHttpHandler } = require('#aws-sdk/node-http-handler')
const { Sha256 } = require('#aws-crypto/sha256-browser')
const region = ''
const domain = ''
const index = 'products'
const type = '_search'
const createBody = (query) => ({
query: {
multi_match: {
query,
type: 'phrase',
fields: [
'tags',
'name',
'category',
'maker'
]
}
},
highlight: {
pre_tags: [''],
post_tags: [''],
fields: {
tags: {},
name: {},
category: {},
maker: {}
}
}
})
searchIndex('sh').then(() => process.exit())
async function searchIndex (query) {
const request = new HttpRequest({
body: JSON.stringify(createBody(query)),
headers: {
'Content-Type': 'application/json',
host: domain
},
hostname: domain,
method: 'GET',
path: index + '/' + type
})
const signer = new SignatureV4({
credentials: defaultProvider(),
region: region,
service: 'es',
sha256: Sha256
})
const signedRequest = await signer.sign(request)
const client = new NodeHttpHandler()
const { response } = await client.handle(signedRequest)
console.log(response.statusCode + ' ' + response.body.statusMessage)
let responseBody = ''
return new Promise((resolve) => {
response.body.on('data', (chunk) => {
responseBody += chunk
})
response.body.on('end', () => {
console.log('Response body: ' + responseBody)
resolve(responseBody)
})
}, (error) => {
console.log('Error: ' + error)
})
}
i also had this issue, using the same tutorial
reading the docs on request body searches, i found it states the following:
Note The _search API accepts HTTP GET and POST for request body
searches, but not all HTTP clients support adding a request body to a
GET request. POST is the more universal choice.
changing my method to POST solved the issue for me

API-gateway-lambda : Cookie not getting saved in browser

I'm trying to know how to use cookies with AWS-Lambda with the serverless framework as per this blogpost
and following is my serverless.yml code
functions:
hello:
handler: handler.hello
events:
- http:
path: /post
method: post
cors:
origin : 'https://xyz.netlify.app'
and Lambda function as per following
"use strict";
const cookie = require("cookie");
module.exports.hello = async (event) => {
const body = JSON.parse(event.body);
const name = body.name;
const value = body.value;
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "https://xyz.netlify.app",
"Access-Control-Allow-Credentials": true,
"Set-Cookie": cookie.serialize(name, value, {
expires: new Date(new Date().getTime() + 10 * 1000),
}),
},
body: JSON.stringify(
{
input: event,
},
null,
2
),
};
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
As you can notice, I already have configured the code to avoid any cors issue.
While try to send a post request as per following,
const name = document.getElementById('name')
const value = document.getElementById('value')
const post_btn = document.getElementById('post_btn')
post_btn.addEventListener('click', () => {
console.log(name.value, value.value)
const post_url = 'https://abcdxyz59t9.execute-api.ap-south-1.amazonaws.com/dev/post'
const user = {
name: name.value,
value: value.value
};
// request options
const options = {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}
// send POST request
fetch(post_url, options)
.then(res => res.json())
.then(res => console.log(res));
})
I do get a Set-Cookie header like below
But the cookie doesn't get saved in the browser.
That's not the case when I directly try to hit a get request with that URL without the cors in the browser. Can anyone please tell me what to do?

Express Gateway: Not getting response body

I am not being able to get the response body that I need to store in the logs along with the request body. The only time I'm able to get the response body is when the request fails.
I've followed the blog post that solved my issue on getting the request body while using body-parser plugin - https://www.express-gateway.io/exploit-request-stream/.
const { PassThrough } = require("stream");
const jsonParser = require("express").json();
const urlEncodedParser = require("express").urlencoded({ extended: true });
module.exports = {
name: 'body-parser',
policy: actionParams => {
return (req, res, next) => {
req.egContext.requestStream = new PassThrough()
req.pipe(req.egContext.requestStream)
return jsonParser(req, res, () => urlEncodedParser(req, res, next))
}
}
};
When the request does work:
{ res: { statusCode: 400 },
req:
{ body: { a: 'b' },
headers:
{ ... } },
responseTime: 310 }
When it does not work:
{ res: { body: 'Bad gateway.', statusCode: 502 },
req:
{ body: { a: 'b' },
headers:
{ ... } },
responseTime: 1019 }
this code alone is not enough to get the response body. This will simply hook in the request body processing and make it available to EG in a parsed way. In case you want to hook in the response too, you will need to write an hook in the response object, once it's done.
You can find an example code here
I hope that helps!
V.

Serverless framework on AWS - Post API gives Internal server error

My serverless.yml looks like this -
service: books-api-v1
provider:
name: aws
region: eu-west-1
role: arn:aws:iam::298945683355:role/lambda-vpc-role
runtime: nodejs8.10
iamRoleStatements:
- Effect: Allow
Action:
- "ec2:CreateNetworkInterface"
- "ec2:DescribeNetworkInterfaces"
- "ec2:DeleteNetworkInterface"
Resource: "*"
functions:
login:
handler: api/controllers/authController.authenticate
vpc: ${self:custom.vpc}
events:
- http:
path: /v1/users/login
method: post
cors: true
And the actual API function looks this -
'use strict';
var db = require('../../config/db'),
crypt = require('../../helper/crypt.js'),
jwt = require('jsonwebtoken');
exports.authenticate = function(event, context, callback) {
console.log(JSON.parse(event.body));
const data = IsJsonString(event.body) ? JSON.parse(event.body) : event.body;
let myEmailBuff = new Buffer(process.env.EMAIL_ENCRYPTION_KEY);
db.users.find({
where:{
username : crypt.encrypt(data.username)
}
}).then(function(user) {
try {
let res = {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
message:'ERROR!',
success: false
})
};
if (!user) {
res.body = JSON.stringify({
success: false,
message: 'Authentication failed. User not found.',
authFail:true
});
//res.status(200).json({ success: false, message: 'Authentication failed. User not found.',authFail:true, });
}else if (user) {
// check if password matches
if (crypt.decrypt(user.password) != data.password) {
res.body = JSON.stringify({
success: false,
message: 'Authentication failed. Wrong password.',
authFail:true
});
//res.status(200).json({ success: false, message: 'Authentication failed. Wrong password.',authFail:true, });
} else {
// if user is found and password is right
// create a token with only our given payload
// we don't want to pass in the entire user since that has the password
const payload = {
username: crypt.decrypt(user.username)
};
var token = jwt.sign(payload, process.env.JWT_SIGINING_KEY, {
algorithm: process.env.JWT_ALGORITHM,
expiresIn: 18000,
issuer: process.env.JWT_ISS
});
// return the information including token as JSON
// res.status(200).json({
// success: true,
// message: 'Enjoy your token!',
// token: token
// });
res.body = JSON.stringify({
success: true,
message: 'Enjoy your token!',
token: token
});
}
//callback(null, res);
}
console.log(res);
callback(null, res);
} catch (error) {
console.log('errorsssss: ');
console.log(error);
}
});
};
function IsJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
Works very well on my serverless local, but when I try it on AWS Lambda function, then it gives "Internal server error" Message.
I have looked into my cloudwatch logs and the response looks correct to me. Here is the response that I am sending back to callback.
{
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: '{"success":true,"message":"Enjoy your token!","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhaW1hLm5hc2lyckBnbWFpbC5jb20iLCJpYXQiOjE1NDUwNjE4OTYsImV4cCI6MTU0NTA3OTg5NiwiaXNzIjoiaWFtbm90YWdlZWsuY28udWsifQ.2HyA-wpmkrmbvrYlWOG41W-ezuLCNQnt0Tvrnsy2n3I"}'
}
Any help please?
In this case, it might because of the Api Gateway configurations. Does your api public? Because I have met such problems when the api is not public.

What is the code to tell a Lambda#Edge function to do a redirect?

So just to be clear I have spent several hours googling things and none of these work. This is not a "low effort post".
This is an example of the code I have been trying. It doesn't work. Neither does doing response like this response.headers=[{Location:"foo"}] or response.headers=[{location:"foo"}] or the other eight ways I've tried it.
exports.handler = (event, context, callback) => {
if(request.uri === "/") {
var response = {
statusCode: 301,
headers: {
"location" : [{
key: "Location",
value: "foo"
}]
},
body: null
};
callback(null, response);
}
I've tried the following links:
http://blog.ryangreen.ca/2016/01/04/how-to-http-redirects-with-api-gateway-and-lambda/
http://blog.rowanudell.com/redirects-in-serverless/
https://kennbrodhagen.net/2016/04/02/how-to-return-302-using-api-gateway-lambda/
Python AWS Lambda 301 redirect
http://www.davekonopka.com/2016/serverless-aws-lambda-api-gateway.html
You mentioned the link to this example in your question; it should work with Lambda Proxy Integration:
'use strict';
exports.handler = function(event, context, callback) {
var response = {
statusCode: 301,
headers: {
"Location" : "http://example.com"
},
body: null
};
callback(null, response);
};
source: http://blog.ryangreen.ca/2016/01/04/how-to-http-redirects-with-api-gateway-and-lambda/
Update:
Else, try using this example from this page of example functions:
'use strict';
exports.handler = (event, context, callback) => {
/*
* Generate HTTP redirect response with 302 status code and Location header.
*/
const response = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: 'http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html',
}],
},
};
callback(null, response);
};