CORS Issue with AWS Lambda and Ajax - ajax

So I have been working on this serverless configuration that calls a Lambda function through ajax. The I enable CORs through the API Gateway, and I have made sure of the domain I specified. This domain works when calling other lambda functions within the same API.
Now for the weird stuff.
I send a post request (I am trying to upload a file through ajax, lambda, and S3), to my API. If I configure the Access-Control-Allow-Origin so that it points to the domain WITHOUT the http in front of it. Ex: example.com. When I try to call this i get:
Failed to load https://m562ogkc1l.execute-api.us-east-1.amazonaws.com/test/upload: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'example.com' that is not equal to the supplied origin. Origin 'http://example.com' is therefore not allowed access.
Ok fine, this is assumed, since that's not the proper domain. So when I add in the http (http://example.com) for the CORs of the API I get:
Failed to load https://m562ogkc1l.execute-api.us-east-1.amazonaws.com/test/upload: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example.com' is therefore not allowed access. The response had HTTP status code 400.
What this seems like to me is that there is an issue elsewhere, except I don't know where the issue lies.
I have made sure the data I pass for parameters of the ajax call are strignified (JSON.stringify()), and I am NOT running an AWS Lambda Proxy which means I shouldn't be configuring responses on the lambda side of things.
This all really confuses me and I wish AWS had better documentation and examples since they really want to push these serverless services.
Further code is here:
Ajax:
$('#submitButton').on('click', function(){
//console.log(document.getElementById('fileUpload').value.substring(12));//C:\fakepath\ in front of filename (size = 12)
$.ajax({
type: 'POST',
url: 'https://m562ogkc1l.execute-api.us-east-1.amazonaws.com/test/upload',
data: JSON.stringify({"id": id,"name": document.getElementById('fileUpload').value.substring(12),"body": document.getElementById('fileUpload').files[0]}),
contentType: "application/json",
success: function(data){
console.log(data);
//location.reload();
}
});
return false;
});
Lambda:
const AWS = require('aws-sdk');
var s3 = new AWS.S3();
exports.handler = async (event) => {
let encodedImage = JSON.parse(event.body);
let decodedImage = Buffer.from(encodedImage, 'base64');
var filePath = event.id + '/' + event.name
var params = {
"Body": decodedImage,
"Bucket": "repository.example.com",
"Key": filePath
};
return await new Promise((resolve, reject) => {
s3.upload(params, function(err, data){
if(err) {
let response = {
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "http://example.com"
},
"body": JSON.stringify(err),
"isBase64Encoded": false
};
resolve(response);
} else {
let response = {
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "http://example.com"
},
"body": JSON.stringify(data),
"isBase64Encoded": false
};
resolve(response);
}
});
});
};
(Yes I threw in some response configuration for the function, I just wanted to see if it would work)

Related

Axios - Request header content-type was not present in the Access-Control-Allow-Headers list - ElasticSearch

I'm new to a lot of this technology, but I think I've diagnosed my issue and need some help. I've seen numerous posts on SO regarding this issue, but none have worked, though they have helped me diagnose issue.
I believe the issue is when I send the Header Content-Type w/ my pre-flight w/ Axios, it fails. This is possibly due to lower/case upper case? The error has lower case, but I tried both on the server without luck.
Basically, if I don't specify any header and Axios uses json as content-type, it works, but as soon as I specify Content-Type my pre-flight fails (even though I think post would work..).
Here is the elasticsearch.yml
cluster.name: "docker-cluster"
network.host: 0.0.0.0
http.cors.enabled : true
http.cors.allow-origin: "*"
http.cors.allow-methods: OPTIONS,HEAD,GET,POST,PUT,DELETE
http.cors.allow-headers: X-Requested-With,X-Auth-Token,Content-Type,Content-Length
#http.cors.allow-credentials: true
Here is my JS that I'm testing BTW w/ an Office Add-In solution in Visual Studio 2017 which I think is using IE as a browser.
Main Func:
var URL = "https://elasticsearch:9200/users/_search"
const data = {
"query": {
"match": {
"name": "freesoftwareservers"
}
}
};
Do_Axios('get', URL, data, null, false)
Do_Axios('post', URL, data, null, false)
Do_Axios:
async function Do_Axios(method, URL, data, headers, withCredentials) {
return axios({
method: method,
url: URL,
withCredentials: withCredentials,
//contentType: 'application/json', // does nothing
//data: JSON.stringify(data), //Causes urlformencoded which is wrong
data: data, //caues type to be json and I get error
headers: {
//"Content-Type": "application/json"
},
})
.then(function (response) {
console.log("Axios " + method + " response:");
console.log(response)
return response;
})
.catch(function (error) {
console.log(error);
});
}
Note: I can get/post if I comment out //data but then the post doesn't run my query. If I uncomment data then Axios uses urlformencoded but that doesn't work.
For now, I've been able to search API via urlformencoded queries, but I'd like to fix my ability to POST correctly to resolve future errors. I'm unsure if issue should be pointed to Axios or Elasticsearch if I open a request.
Well, I finally figured it out. I wonder how many of the other posts I read have similar issues... anyway, the issue was w/ my NGinX proxy server. No better way to learn about CORS then to setup an API and make CORS requests via IE! Without the below, I was still able to post w/ POSTMAN to the same URL which hit my nginx server, but the call from Axios/IE/JS Evironment failed.
I found these snippets and this was the magic that needed added to my "regular" configuration:
proxy_pass_header Access-Control-Allow-Origin;
proxy_pass_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type';
add_header Access-Control-Allow-Credentials true;
https://gist.github.com/sahilsk/b16cb51387847e6c3329
Here is my code as it stands, cleaned up but generic atm:
Note: I pass axios because I can't figure out how to get my Webpack to transform/polyfill my funcs in seperate js files. But I can declare axios in the main func and pass it and then I can move my funcs into separate files as needed for organization. There is likely a better way to do without passing axios and configuring webpack
Main Func:
var username = "freesoftwareservers"
var ipv4 = "192.168.1.255"
var showhelp = "false"
await Do_AddUserToES(axios,username, ipv4, showhelp)
Get_UserFromES(axios,username)
var index = "users"
var query = {
query: {
match: {
"username": username
}
}
};
Get_PostQueryToES(axios,query, index)
Funcs:
function Do_Axios(axios, method, URL, data, headers, withCredentials) {
return axios({
method: method,
url: URL,
withCredentials: withCredentials,
data: data,
headers: headers,
})
.then(function (response) {
console.log("Axios " + method + " response:");
console.log(response)
return response;
})
.catch(function (error) {
console.log(error);
});
}
function Get_ESURL(Bool_Search, Bool_Doc, Bool_Update, Opt_Index, Opt_IndexKey) {
var ESUrl = "https://elasticsearch:9200"
var ESSearch = "/_search"
var ESDoc = "/_doc"
var ESUpdate = "/_update"
var ReturnURL = ESUrl
if (Opt_Index != undefined) { ReturnURL = ReturnURL + "/" + Opt_Index }
if (Bool_Search == true) { ReturnURL = ReturnURL + ESSearch }
if (Bool_Doc == true) { ReturnURL = ReturnURL + ESDoc }
if (Bool_Update == true) { ReturnURL = ReturnURL + ESUpdate }
if (Opt_IndexKey != undefined) { ReturnURL = ReturnURL + "/" + Opt_IndexKey }
console.log("ReturnURL:" + ReturnURL)
return ReturnURL;
}
function Do_AddUserToES(axios, username, ipv4, showhelp) {
var adduser = {
"username": username,
"ipv4": ipv4,
"showhelp": showhelp
};
var URL = Get_ESURL(false, true, false, "users", username)
return Do_Axios(axios, 'post', URL, adduser, null, false);
}
function Get_UserFromES(axios, username) {
var URL = Get_ESURL(false, true, false, "users", username)
return Do_Axios(axios, 'get', URL, null, null, false);
}
function Get_PostQueryToES(axios, query, index) {
var URL = Get_ESURL(true, false, false, index)
return Do_Axios(axios, 'post', URL, query, null, false);
}

API Gateway + Lambda - CORS Issue

i am experiencing continuing problems with the CORS integration for API Gateway + Lambda. i have enabled CORs for the resources associated with the API. Everything appears to work fine via Lambda testing, Postman testing etc, but calling the api from a webpage script is giving the following error "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 415." Do I need to change the Lambda function? Thanks
Here is my simple Lambda code..
'use strict';
var AWS = require('aws-sdk');
var dclient = new AWS.DynamoDB.DocumentClient();
var getItems = (event, context, callback) => {
var params = {
TableName: "OMSCaseDataTest",
Key: {
"IncidentID": event.IncidentID
}
}
dclient.get(params, (error, data) => {
if (error) {
callback(null, "error occured")
} else {
callback(null, data);
}
});
};
exports.getItems = getItems;
If you are using proxy integration in API Gateway, then enabling CORS from API Gateway doesn't work. You have to set the Header 'Access-Control-Allow-Origin' from your Lambda code itself.
Its mentioned in the doc.
Python code sample:
response = {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({'message': 'CORS enabled')
}
return response
Assuming you're using proxy integration, you'll need to handle the CORS yourself. Your lambda function will need to handle the HTTP methods differently. CORS problems usually occur when the pre-flight option request is not entertained. Here's a code snippet could help your cause.
function main(event, context, lambdaCallback) {
if (event.httpMethod === 'OPTIONS') {
doneOptions(200, '{"status": "OK"}', 'application/json', lambdaCallback);
} else if (event.httpMethod === 'POST') {
// do your POST here
} else {
return done(400, '{"message":"Invalid HTTP Method"}', 'application/json', lambdaCallback);
}
}
The functions that return the HTTP 200 to your frontend which decide what your frontend/API could call and what's not.
function doneOptions(statusCode, body, contentType, lambdaCallback, isBase64Encoded = false) {
lambdaCallback(null, {
statusCode: statusCode,
isBase64Encoded: isBase64Encoded,
body: body,
headers: {
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Authorization,Content-Type',
'Access-Control-Allow-Method': 'GET,POST,OPTIONS',
}
});
}
Some times the lambda time out causes CORS error. You can increase the timeout this may fix the issue.

spotify application requests authorization

I am trying to get 'access token' from spotify with the following code.
var encoded = btoa(client_id+':'+client_secret);
function myOnClick() {
console.log('clikced!');
$.ajax({
url: 'https://accounts.spotify.com/api/token',
type: 'POST',
data: {
grant_type : "client_credentials",
'Content-Type' : 'application/x-www-form-urlencoded'
},
headers: {
Authorization: 'Basic ' + encoded
},
dataType: 'json'
}).always((data)=> console.log(data));
}
however I keep getting errors:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at https://accounts.spotify.com/api/token.
(Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
and
readyState: 0, status: 0
Arielle from Spotify here.
Looks like you're using the Client Credentials Flow, which is one of 3 Authentication flows you can use with the Spotify API. (You can check out all 3 here)
Client Credentials is meant for server-side use only, and should not be used on the front-end, as it requires a client secret which you shouldn't be exposing!
You should use the Implicit Grant flow, which is made for use in the browser, instead. It's easy to get up and running, too!
// Get the hash of the url
const hash = window.location.hash
.substring(1)
.split('&')
.reduce(function (initial, item) {
if (item) {
var parts = item.split('=');
initial[parts[0]] = decodeURIComponent(parts[1]);
}
return initial;
}, {});
window.location.hash = '';
// Set token
let _token = hash.access_token;
const authEndpoint = 'https://accounts.spotify.com/authorize';
// Replace with your app's client ID, redirect URI and desired scopes
const clientId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
const redirectUri = 'http://localhost:8888';
const scopes = [
'user-read-birthdate',
'user-read-email',
'user-read-private'
];
// If there is no token, redirect to Spotify authorization
if (!_token) {
window.location = `${authEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes.join('%20')}&response_type=token`;
}
Gist: https://gist.github.com/arirawr/f08a1e17db3a1f65ada2c17592757049
And here's an example on Glitch, that you can "Remix" to make a copy and start making your app: https://glitch.com/edit/#!/spotify-implicit-grant
Hope that helps - happy hacking! 👩🏼‍💻
const result = await axios({
url: this.apiLoginUrl,
method: 'post',
data: "grant_type=client_credentials",
headers: {
'Authorization': `Basic ${Buffer.from(this.clientId + ":" + this.clientSecret).toString('base64')}`,
},
});

POST binary data from browser to JFrog / Artifactory server without using form-data

So we get a file (an image file) in the front-end like so:
//html
<input type="file" ng-change="onFileChange">
//javascript
$scope.onFileChange = function (e) {
e.preventDefault();
let file = e.target.files[0];
// I presume this is just a binary file
// I want to HTTP Post this file to a server
// without using form-data
};
What I want to know is - is there a way to POST this file to a server, without including the file as form-data? The problem is that the server I am send a HTTP POST request to, doesn't really know how to store form-data when it receives a request.
I believe this is the right way to do it, but I am not sure.
fetch('www.example.net', { // Your POST endpoint
method: 'POST',
headers: {
"Content-Type": "image/jpeg"
},
body: e.target.files[0] // the file
})
.then(
response => response.json() // if the response is a JSON object
)
You can directly attach the file to the request body. Artifactory doesn't support form uploads (and it doesn't look like they plan to)
You'll still need to proxy the request somehow to avoid CORS issues, and if you're using user credentials, you should be cautious in how you treat them. Also, you could use a library like http-proxy-middleware to avoid having to write/test/maintain the proxy logic.
<input id="file-upload" type="file" />
<script>
function upload(data) {
var file = document.getElementById('file-upload').files[0];
var xhr = new XMLHttpRequest();
xhr.open('PUT', 'https://example.com/artifactory-proxy-avoiding-cors');
xhr.send(file);
}
</script>
Our front-end could not HTTP POST directly to the JFrog/Artifactory server. So we ended up using a Node.js server as a proxy, which is not very ideal.
Front-end:
// in an AngularJS controller:
$scope.onAcqImageFileChange = function (e) {
e.preventDefault();
let file = e.target.files[0];
$scope.acqImageFile = file;
};
// in an AngularJS service
createNewAcqImage: function(options) {
let file = options.file;
return $http({
method: 'POST',
url: '/proxy/image',
data: file,
headers: {
'Content-Type': 'image/jpeg'
}
})
},
Back-end:
const express = require('express');
const router = express.Router();
router.post('/image', function (req, res, next) {
const filename = uuid.v4();
const proxy = http.request({
method: 'PUT',
hostname: 'engci-maven.nabisco.com',
path: `/artifactory/cdt-repo/folder/${filename}`,
headers: {
'Authorization': 'Basic ' + Buffer.from('cdt-deployer:foobar').toString('base64'),
}
}, function(resp){
resp.pipe(res).once('error', next);
});
req.pipe(proxy).once('error', next);
});
module.exports = router;
not that we had to use a PUT request to send an image to Artifactory, not POST, something to do with Artifactory (the engci-maven.nabisco.com server is an Artifactory server). As I recall, I got CORS issues when trying to post directly from our front-end to the other server, so we had to use our server as a proxy, which is something I'd rather avoid, but oh well for now.

How can I send an AJAX request to a node.js server via HTTPS?

I have the following node.js server set up listening to port 9001
var https = require('https');
var fs = require('fs');
var qs = require('querystring');
var options = {
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
console.log('Request Received!');
console.log(req.method);
if (true || req.method == 'POST') {
var body = '';
req.on('data', function (data) {
body += data;
});
req.on('end', function () {
console.log(body);
var POST = qs.parse(body);
console.log(POST);
});
}
res.end("hello, world\n");
}).listen(9001);
and I am trying to get this server to respond to an AJAX call
function form_save()
{
console.log("submitted!");
var data_obj = {
data1: "item1",
data2: "item2"
}
$.ajax({
url: 'https://adam.testserver.com:9001/',
type: "POST",
dataType: "json",
data: data_obj,
success: function() {
console.log("success!");
},
complete: function() {
console.log("complete!");
}
});
}
There are two problems occurring with my arrangement. The first is that if I start the server and then click the button that triggers my form_save() the node server does not respond and I get the following error:
submitted!
OPTIONS https://adam.testserver.com:9001/ Resource failed to load
jQuery.extend.ajaxjquery.js:3633
$.ajaxjquery.validate.js:1087
form_savew_worksheet.php:64
confirm_deletew_worksheet.php:95
jQuery.event.handlejquery.js:2693
jQuery.event.add.handlejquery.js:2468
w_worksheet.php:73
complete!
At this point if I access that url directy (https://adam.testserver.com:9001/) I get the expected "hello, world" output as well as the console message "Request Received!
GET". From this point on if I click the button to trigger my AJAX call I get a new error.
submitted!
XMLHttpRequest cannot load https://adam.testserver.com:9001/. Origin
https://adam.testserver.com is not allowed by Access-Control-Allow-Origin.
w_worksheet.php:73
complete!
I don't understand why I get this message as both my form and node server reside on the same server. Thanks for taking the time to read, I appreciate any help I can get on this. I've been stuck for a while now!
You've run into the Cross-Origin Resource Sharing (CORS) specification.
Note the OPTIONS in your output. The OPTIONS HTTP Verb is used by the browser to query the web server about the URL, not to GET its contents or POST data to it.
Your server doesn't respond with the correct header data on a CORS request, so your browser assumes it has no rights to access the data, and refuses to GET or POST to the URL.
If you truly want to let any website in the world run that AJAX request, you can do something similar to the following:
function handleOptions(request, response) {
response.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Method": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": request.headers["access-control-request-headers"]
});
response.end();
}
function server(request, response) {
if(request.method == "POST") {
handlePost(request, response);
} else if(request.method == "OPTIONS") {
handleOptions(request, response);
} else {
handleOther(response);
}
}
https.createServer(sslObj, server).listen(9001);
You can fill in the details and whether you should handle GET separately, and so on (handleOther should return an appropriate error code for each request method you don't support).

Resources