Unable to access lambda function from s3 hosted react application - reactjs

I have a react application which has a post method requesting data from a lambda function. It is working fine in my local machine. But when I deployed the application on s3 bucket and try to access it through s3 endpoint, it gives me 405 Method not allowed error. After search I found that post method will not work with s3 endpoint, So again I tried with rest api endpoint, but it shows file name xml. I am able to access application when I add index page to rest api endpoint, But here also I am getting 412 error in post method. So in any case I am getting error with post method. Can someone help me with that. I don't know what I am missing. Below is my code:
const apiUrl = process.env.NODE_ENV === 'production' ? process.env.REACT_APP_PROD_API_URL : process.env.REACT_APP_DEV_API_URL;
const params = (query: string) => ({
headers: {
"Accept": "application/sparql-results+json",
"Accept-Encoding": "gzip",
"Content-Type": "application/x-www-form-urlencoded"
},
method: "POST",
body: `query=${encodeURIComponent(query)}`
})
const fetchClinicalStudies = async () => {
const clinicalStudiesQuery = `
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX disco: <http://rdf-vocabulary.ddialliance.org/discovery#>
SELECT ?id ?label
WHERE {
?id a disco:Study ;
dcterms:title ?label .
}
ORDER BY ?label`
const response = await fetch(apiUrl ?? '', params(clinicalStudiesQuery))
return response;
}

Related

403 when upload file to S3 bucket using axios

I'm using axios to upload an audio file to AWS s3 bucket.
The workflow is: React => AWS API Gateway => Lambda.
Here is the backend Lambda code where generates the S3 presigned URL:
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(AUDIO_S3_BUCKET)
.key(objectKey)
.contentType("audio/mpeg")
.build();
PutObjectPresignRequest putObjectPresignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(10))
.putObjectRequest(putObjectRequest)
.build();
PresignedPutObjectRequest presignedPutObjectRequest = s3Presigner.presignPutObject(putObjectPresignRequest);
AwsProxyResponse awsProxyResponse = new AwsProxyResponse();
awsProxyResponse.setStatusCode(HttpStatus.SC_OK);
awsProxyResponse.setBody(
GetS3PresignedUrlResponse.builder()
.s3PresignedUrl(presignedPutObjectRequest.url().toString())
.build().toString());
return awsProxyResponse;
Here is the java code to create the bucket:
private void setBucketCorsSettings(#NonNull final String bucketName) {
s3Client.putBucketCors(PutBucketCorsRequest.builder()
.bucket(bucketName)
.corsConfiguration(CORSConfiguration.builder()
.corsRules(CORSRule.builder()
.allowedHeaders("*")
.allowedMethods("GET", "PUT", "POST")
.allowedOrigins("*") // TODO: Replace with domain name
.exposeHeaders("ETag")
.maxAgeSeconds(3600)
.build())
.build())
.build());
log.info("Set bucket CORS settings successfully for bucketName={}.", bucketName);
}
In my frontend, here is the part that try to upload file:
const uploadFile = (s3PresignedUrl: string, file: File) => {
let formData = new FormData();
formData.append("file", file);
formData.append('Content-Type', file.type);
const config = {
headers: {
"Content-Type": 'multipart/form-data; boundary=---daba-boundary---'
//"Content-Type": file.type,
},
onUploadProgress: (progressEvent: { loaded: any; total: any; }) => {
const { loaded, total } = progressEvent;
let percent = Math.floor((loaded * 100) / total);
if (percent < 100) {
setUploadPercentage(percent);
}
},
cancelToken: new axios.CancelToken(
cancel => (cancelFileUpload.current = cancel)
)
};
axios(
{
method: 'post',
url: s3PresignedUrl,
data: formData,
headers: {
"Content-Type": 'multipart/form-data; boundary=---daba-boundary---'
}
}
)
.then(res => {
console.log(res);
setUploadPercentage(100);
setTimeout(() => {
setUploadPercentage(0);
}, 1000);
})
.catch(err => {
console.log(err);
if (axios.isCancel(err)) {
alert(err.message);
}
setUploadPercentage(0);
});
};
However, when try to upload the file, it return 403 error.
And if I use fetch instead of axios instead and it works, like this:
export async function putToS3(presignedUrl: string, fileObject: any) {
const requestOptions = {
method: "PUT",
headers: {
"Content-Type": fileObject.type,
},
body: fileObject,
};
//console.log(presignedUrl);
const response = await fetch(presignedUrl, requestOptions);
//console.log(response);
return await response;
}
putToS3(getPresignedUrlResponse['s3PresignedUrl'], values.selectdFile).then(
(putToS3Response) => {
console.log(putToS3Response);
Toast("Success!!", "File has been uploaded.", "success");
}
);
It seems to me that the only difference between these two is that: when using fetch the request's Content-Type header is Content-Type: audio/mpeg, but when using axios it is Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryClLJS3r5Xetv3rN7 .
How can I make it work with axios? I'm switching to axios for its ability to monitor request progress as I want to show an upload progress bar.
I followed this blog and not sure what I missed: https://bobbyhadz.com/blog/aws-s3-presigned-url-react
You are using POST in your axios. Should be PUT instead.
Also I think the content type has to match the one specified during requesting the pre-signed URL, which is audio/mpeg as you rightly pointed out.
Correspondingly, your data should be just file, instead of formData.
axios(
{
method: 'put',
url: s3PresignedUrl,
data: file,
headers: {
"Content-Type": 'audio/mpeg'
}
}
...
You didn't mark any answers as accepted so I guess you didn't solve it.
For any future viewers out there. The reason why you are getting 403 forbidden error is because your Content-Type in your server and client side are not matching. I'm assuming you set up the AWS policies correctly.
Your code in the backend should look like this:
const presignedPUTURL = s3.getSignedUrl("putObject", {
Bucket: "bucket-name",
Key: String(Date.now()),
Expires: 100,
ContentType: "image/png", // important
});
and in the front-end (assuming you are using axios):
const file = e.target.files[0]
const result = await axios.put(url, file, {
withCredentials: true,
headers: { "Content-Type": "image/png" },
});
In practical, you would normally have to send the file type to generate the pre-signed url in the POST body or whatever and then in axios you do file.type to get the file type of the uploaded file.
Check your Lambda execution role. It may be the culprit. Perhaps it does not grant enough permissions to allow PUTting files into your bucket.
URL signing is a delegation of power on behalf of the signer, which is restricted to a specified object, action... Signing does not magically grants full read/write permissions on S3, even on the specific object related to the presigned URL.
The "user" who generates the signature requires sufficient permissions to allow the actions you want to delegate through that presigned URL. In this case, this is the execution role of your Lambda function.
You can add the AmazonS3FullAccess managed policy to the execution role and see if it solves your situation. This change took me out of a blocked situation me after days of struggle. Afterwards, before going to production, restrict that rule to the specific bucket you want to allow uploads into (least privilege principle).
If you develop using SAM local emulation, those execution roles seem not to be taken into account as long as you run your functions locally; the signed links work in that context even without S3 permissions.

NEXTJS Sending formdata to api endpoint - no response

Hellooooo.
I am trying to upload images to AWS S3 and I've stumbled upon something that irritates me quite a lot. I simply can't seem to understand why it would act like this.
Aight so.. I am using formdata to send data to my api endpoint. The API gets called without any issues, no errors, nothing.. Like srly, nothing. Before getting to image upload I was just using a basic body post request with fetch but now I am using formdata in order to upload images.
Here's my "fetch/axios/sendthingy"
const formData = new FormData();
formData.append('file', validFiles[0]);
formData.append('postTitle', postTitle);
formData.append('postProduct', postProduct);
formData.append('postDescription', postDescription);
formData.append('postPrice', postPrice);
formData.append('postCoins', cryptocoins);
formData.append('postCategory', Cate);
formData.append('postSubCategory', subCat);
formData.append('postCryptoDiscount', cryptoDiscount);
for (const entry of formData.entries())
{
// debugging
console.log(entry)
}
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json"); // or form data?
const requestOptions = {
method: 'POST',
body: formData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, // or use myHeaders
// body: JSON.stringify({ postTitle: postTitle, postProduct: postProduct, postDescription: postDescription, postPrice: postPrice, postCoins: cryptocoins, postImage: validFiles[0], postCategory: Cate, postSubCategory: subCat, postCryptoDiscount: cryptoDiscount})
};
// const res = await fetch(`http://localhost:3000/api/posts/create`, requestOptions)
// const data = await res.json();
axios.post('http://localhost:3000/api/posts/create', formData).then(console.log).catch(console.error)
My comments are from before using formdata. That worked.
As you can see, I have installed Axios. I wanted to see if axios would fix the return stuff. No luck. Any ideas on how to solve this or how to send images to api with or without formdata?
Right, let's move on. Next issue is that I can see it calling my API endpoint but my api endpoint does nothing. It returns nothing. Does nothing. But when I call the api from postman it works just as it should. How come? It creates the post and returns the correct data. Any ideas?
My API:
export default async function handler(req, res) {
console.log('body', req.body);
/*
Redacted
*/
res.status(200).json({response: 'test', code: 200, message:"Successfully created the post."})
})
Why is my API not doing anything locally... It will only work with formdata if I call the endpoint from postman.
This is what I mean.. The dark one is Firefox, white is Postman:
Firstly , you should use multipart/form-data for uploading files. And to see the response you have to console.log the response like this:
axios.post(
'http://localhost:3000/api/posts/create',
formData,
{headers:{'Content-Type':'multipart/form-data'}}
).then(res => console.log(res))
.catch(err => console.error(err))

How to perform a post request with axios on React and pass your data as FormData?

There's this API:http://zssn-backend-example.herokuapp.com/api/, detailed here: http://zssn-backend-example.herokuapp.com/swagger-api/index.html#!/people/Api_People_index . I am trying to do a POST request on this endpoint "/people/{person_id}/properties/trade_item.json"(Make a trade transaction between survivors). On the Website for testing I filled the fields with the following data and got 204 response:
person_id:34e183a6-965e-4a61-8db5-5df9103f4d4b
consumer[name]: Person4
consumer[pick]: AK47:1;Campbell Soup:1
consumer[payment]: First Aid Pouch:2
I investigated a Little and got
this information
didn't understand the "PROPERTY[KEY]:VALUE" representation
When I try to the Request with this code, with the same values(by the way, the database of this API is cleared each 24 hours, so those value might not work, even if you do it in the right way):
this is My code:
async function handleTrade(e){
e.preventDefault();
const config = {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
};
await api.post(
"/people/" + match.params.id + "/properties/trade_item.json",
{
person_id:match.params.id,
name: survivorRef.current,
pick: survivorItemsToPick,
payment: userItemsToPay,
},
config
);
And this is what I get:
Request:
request-description
Response:
response-description
How Can i make this request in the right way in order to get the same result as the first Image ?
You can use FormData to send the request, I made an example:
const data = new FormData();
data.append("customer[name]", "Person4");
data.append("customer[pick]", "AK47:1");
await api.post("/people/" + match.params.id + "/properties/trade_item.json", data, config)

InvalidSignatureException from POST request

I have a Lambda function that handles reading data from a file(stored inside S3 bucket) as well as inserting data to a Dynamodb table. This Lambda function is exposed as a REST endpoint using API gateway. The function accepts GET request as well as POST request. I'm making GET/POST requests from my REACT project using axios and aws4(for signing) libraries. GET request is to read data from a file stored inside S3 and it works just fine. And POST request is for inserting data into Dynamodb table. However, it doesn't work and AWS returns InvalidSignatureException error as a respond. This is an excerpt of my code :
createAWSSignedRequest(postData) {
let request = {};
if (postData) {
request = {
host: process.env.AWS_HOST,
method: 'POST',
url: process.env.AWS_URL,
path: process.env.AWS_PATH,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
}
} else {
request = {
host: process.env.AWS_HOST,
method: 'GET',
url: process.env.AWS_URL,
path: process.env.AWS_PATH
}
}
let signedRequest = aws4.sign(request, {
secretAccessKey: process.env.AWS_SECRET_KEY,
accessKeyId: process.env.AWS_ACCESS_KEY
});
return signedRequest;
}
This is how GET request is made :
let signedRequest = this.createAWSSignedRequest('GET');
axios(signedRequest)
.then(response => {
})
.catch((error) => {
console.log("error",error);
});
This is how POST request is made :
const data = {
uuid: "916b7d90-0137-11e8-94e6-116965754e23", //just a mock value
date : "22/jan/2018",
user_response: [
{
question:"this is quesiton1",
choice:"user selected A"
},
{
question:"this is quesiton2",
choice: "user selected b"
},
{
question:"this is quesiton3",
choice: "user selected C"
}
]
};
let signedRequest = this.createAWSSignedRequest(data);
axios(signedRequest)
.then(response => {
......
})
.catch((error) => {
console.log("error",error);
});
As you can see, the code for both GET and POST requests are exactly the same (except payload and method type). I'm singing with the same secret access key and access key id for both requests. I'm not sure why one request results in "InvalidSignatureException" when the other doesn't. Can anyone shed a light on this issue for me.
Thanks
After having discussion with AWS4 lib developer, I figured out what I did wrong. AWS4 uses "body" as a payload attribute to compute signature. However, Axios uses "data" attribute as payload. My mistake was only setting either one of them. So when I set just "data" attribute, the payload was present in the request and content-length is computed correctly. However, the signature was incorrect since the payload was not taken into consideration when computing signature. When I set just "body", payload was not present in the request because Axios does not use "body" attribute for payload. The solution is to set both attributes with payload. I hope this helps to anyone who are having the same issue I have.
If you use the AWS Amplify library it has a module called API which should fit your use cases, and it will perform Sigv4 signing for you either with authenticated or unauthenticated roles. The Auth category uses Cognito as the default implementation. For instance:
npm install aws-amplify --save
Then import and configure the lib:
import Amplify, { API } from 'aws-amplify';
Amplify.configure({
Auth: {
identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',
region: 'XX-XXXX-X'
},
API: {
endpoints: [
{
name: "APIName",
endpoint: "https://invokeURI.amazonaws.com"
}
]
}
});
Then for your API Gateway endpoint calling a Lambda:
let apiName = 'MyApiName';
let path = '/path';
let options = {
headers: {...} // OPTIONAL
}
API.get(apiName, path, options).then(response => {
// Add your code here
});
More info here: https://github.com/aws/aws-amplify

How to call Web Api method that

I am writing a Web API method to export a PDF file from querying data from database.
I am able to get this worked by calling directly and get the result as expected. But calling this method from angularjs is not working. I see that the method is called but download is not happenning. I saw in someposts that ajax calls dont work on downloading the content....if so what are the other options to call this method and get the file downloaded ? Can somebody give some hint on how to get this worked ?
This is the code I wrote on WEB API and angularjs.
[HttpGet]
[Route("ExportAppsData")]
public HttpResponseMessage ExportAppsData(string ids, bool skip)
{
Int64[] convertedIds = new Int64[] { };
if (ids != null)
convertedIds = ids.Split(',').Select(n => Convert.ToInt64(n)).ToArray();
List<App> apps = new List<App>();
if (skip)
apps = _appRepository.GetAllApps().SkipWhile(a => convertedIds.Contains(a.Id)).ToList();
else
apps = _appRepository.GetAllApps().Where(a => convertedIds.Contains(a.Id)).ToList();
List<AppDto> mappedResult = _mapppingEngine.Map<List<App>, List<AppDto>>(apps);
return Request.CreateResponse(HttpStatusCode.OK, mappedResult,new PdfMediaTypeFormatter());
}
function exportAppsData(appIds, skipFlag) {
return $http(
{
url: ngSettings.apiServiceUri + 'api/Apps/ExportAppsData',
responseType : ArrayBuffer,
params: { ids: appIds.join(","), skip: skipFlag },
contentType: 'application/json; charset=utf-8',
headers: {
Accept: "text/csv; charset=utf-8",
},
method: "GET",
}
);
}
You need to set your Accept header to application/pdf instead of text/csv; charset=utf-8.

Resources