NodeJS - CORS issue with Google Cloud Storage Signed URLs - node.js

I want to enable artist clients to upload images to Cloud Storage using Signed URLs.
The following backend script is used to get the signed url and it is behind a Google Cloud load balancer:
(Does that (load balancer) affect anything?)
exports.getSignedUrl = async (req, res) => {
if (req.method !== 'POST') {
// Return a "method not allowed" error
return res.status(405).end();
}
try {
console.log("Getting signed url..");
const storage = new Storage();
const bucketName = 'bucket' in req.body ? req.body.bucket : 'gsk-public';
// Get a reference to the destination file in GCS
const file = storage.bucket(bucketName).file(req.body.filename);
// Create a temporary upload URL
const expiresAtMs = 'expires' in req.body ? req.body.expires : Date.now() + 100; // Link expires in 1 minute
const configuration = {
version: 'v4',
action: req.body.action,
expires: expiresAtMs
};
if ('type' in req.body) {
configuration.contentType = req.body.type;
}
console.log("Got signed url!");
const url = await file.getSignedUrl(configuration);
console.log(url);
return res.send(url[0]);
}
catch (err) {
console.error(err);
return res.status(500).end();
}
};
CORS setting:
[
{
"origin": [
"https://localhost:3000/",
"https://dreamlouvre-git-development-samuq.vercel.app/"
],
"responseHeader": "*",
"method": ["GET", "PUT", "POST", "OPTIONS", "HEAD"],
"maxAgeSeconds": 3600
}
]
Next.js API endpoint to get the signed url:
import axios from 'axios';
import {config} from '../config';
import { fileTypeToJPG, createQueryString } from '../../../helpers/helpers';
import mime from 'mime';
export default async (req, res) => {
if(req.method === 'POST'){
try{
if(!config.mimeExtensions.some(el => el===mime.getExtension(req.body.type))){
//throw new Error('The file type of your image is not supported. Following types are supported: jpg, png, bmp and tiff.');
return res.status(500).json({ statusCode: 500, message: 'Unsupported filetype.' });
}
//console.log('file name: ', req.body.filename);
//console.log('auth header: ', req.headers.authorization);
const response = await axios.post(`${config.apiBaseUrl}/auth/signedUrl`, req.body, {headers:{"authorization": `Bearer ${req.headers.authorization}`}});
//console.log("Respdata");
//console.log(response.data);
//console.log(response.data[0]);
const respData = {
filename: fileTypeToJPG(req.body.filename),
originalFileName: req.body.filename,
url: response.data
};
return res.status(200).json(respData);
}catch (err) {
//console.log(err);
return res.status('status' in err ? err.status : 500).json({ statusCode: 500, message: err.message });
}
}
else return res.status(403);
};
Next.js Front-end code to put to signed url:
const badi = {
type: file.type,
filename: fileName,
action: "write",
bucket: config.bucketOriginal,
};
console.log("upload 3");
const resp = await axios.post(`/api/auth/getSignedUrl`, badi, {
headers: { authorization: token },
});
console.log("upload 4", resp.data);
await axios.put(resp.data.url, file, {
headers: { "Content-type": file.type },
});
Observed behaviour: Access has been blocked by CORS policy.
Console logs upload 4 and the signed url, but the signed url doesn't work.
Expected behaviour: put request would work correctly.

There was an error in expiry time that probably caused the CORS error.
I fixed this:
const expiresAtMs = 'expires' in req.body ? req.body.expires : Date.now() + 100;
to this:
const expiresAtMs = 'expires' in req.body ? req.body.expires : Date.now() + 15*60*1000;
EDIT: The code already started to work, but is now throwing 403 erros, claiming there's a malformed content-type header although there is not. Content-type header is present in the request.

Related

FaunaDB returns empty array (FaunaDB + Netlify + VueJS)

My code is based on the repository - https://github.com/ttntm/recept0r-ts
Code from "\functions\read-all.js":
const faunadb = require('faunadb');
const fnHeaders = require('./_shared/headers.js');
exports.handler = (event, context) => {
const client = new faunadb.Client({
secret: process.env.FAUNA_SECRET,
domain: 'db.fauna.com',
scheme: 'https',
port: '443'
});
const q = faunadb.query;
const headers = { ...fnHeaders };
const origin = event.headers.Origin || event.headers.origin;
headers['Access-Control-Allow-Origin'] = origin ? origin : '*';
return client.query(q.Paginate(q.Match(q.Index('all_users'), false), { size: 500 }))
.then((response) => {
const listRefs = response.data;
const getListDataQuery = listRefs.map(ref => q.Get(ref)); // create new query out of list refs, then query the refs
return client.query(getListDataQuery).then((records) => {
return { statusCode: 200, headers: headers, body: JSON.stringify(records) }
})
})
.catch((error) => {
return { statusCode: 400, headers: headers, body: JSON.stringify(error) }
});
}
Code from "\src\store\modules\data.js":
async readAll({ commit, dispatch, rootGetters })
{
const fn = rootGetters['app/functions'];
const request = await fetch(fn.readAll, { method: 'GET' });
const response = await request.json();
if (response.length > 0) {
commit('SET_ALL_RECIPES', response);
commit('SET_LAST_UPDATED', new Date); }
else {
dispatch('app/sendToastMessage', { text: 'Error loading recipes. Please try again later.', type: 'error' }, { root: true });
return 'error';
}
}
Everything seems to be set. For example, this code works:
client.query(q.CreateCollection({ name: 'someCollection' }))
But can't read any data.
If launch application by "netlify dev" (localhost) - "read-all" returns empty array ("[]").
If launch application by "network" - "read-all" returns default "index.html".
I have no idea what's wrong. Maybe someone give advice...
I found a similar question - Local Netlify function server gives strange response instead of FaunaDB data
Some answer:
"In my experience, one of the most common reasons for this error is a routing problem, which is triggering a 404 response route serving HTML instead of your expected function handler."
This code works:
return client.query(q.Paginate(q.Documents(q.Collection('customers')), { size: 500 }))
.then((response) => {
const listRefs = response.data;
const getListDataQuery = listRefs.map(ref => q.Get(ref)); // create new query out of list refs, then query the refs
return client.query(getListDataQuery).then((records) => {
return { statusCode: 200, headers: headers, body: JSON.stringify(records) }
});
})
.catch((error) => {
return { statusCode: 400, headers: headers, body: JSON.stringify(error) }
});

Cannot save email in React.js + Express

So I have a function getProfile(). Inside it I am trying to fetch users from the server using a POST call, however it's throwing a CORS error in its response.
Here is the getProfile:
const getProfile = async () => {
try {
const res = await fetch("http://localhost:5000/dashboard/", {
method: "POST",
headers: {
jwt_token: localStorage.token,
"Content-Type": "application/json",
Accept: "application/json",
},
});
const parseData = await res.json();
setEmail(parseData.email);
console.log("Try success");
console.log(parseData.email);
} catch (err) {
console.log("Try failed");
console.error(err.message);
}
};
if you are handling your nodeServer by your own you need to add CORS package
please check this and follow their docs
https://www.npmjs.com/package/cors
1.install package
2. in your app.js file add following
var cors = require('cors')
app.use(cors())
and change you frontend function getProfile() to following:
const getProfile = async () => {
let token = localStorage.getItem("token");
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "bearer" + token);
try {
await fetch("http://localhost:5000/dashboard/", {
method: "POST",
headers: myHeaders,
})
.then((r) => r.json().then((data) => ({ status: r.status, data })))
.then((obj) => obj.status === 200 && console.log("data", obj.data));
} catch (err) {
console.error("Try failed", err.message);
}
};

Axios POST request sending nothing with 'multipart/form-data' [React Native - Expo]

Scenario
Front end is basically a React Native (Expo) application where users can issue a report - this includes taking multiple photos and filling in some details.
Backend is just node.js, with Express & Multer.
Problem
I use Axios to send the images and form data through FormData(), however on the server side, req.body and req.files consist of nothing.
One thing here is that sending the SAME data through POSTMAN works COMPLETELY fine, the images were stored into S3 and the form details were stored in the database. It is through the app/emulator that does not work.
I've tried removing the "multipart/form-data" header and this is the output from console.log(req.body) (req.files show undefined):
{
_parts: [
[ 'userId', '1f400069-07d0-4277-a875-cbb2807750c5' ],
[
'location',
'some location'
],
[ 'description', 'Aaaaa' ],
[ 'report-images', [Object] ]
]
}
When I put the "multipart/form-data" header back this output didn't even bother showing up.
What I've Done
I've been searching for solutions for the past hours and none of them worked. Those solutions are:
Adding boundary behind the "multipart/form-data" header
Putting the type to "image/jpeg"
Trimming the file uri to "file://"
Yet none of them works
Here is my code:
React Native Frontend (Expo)
const submitReport = async () => {
setShowLoading(true);
// Validate details (location & images)
if (location === "") {
setShowLoading(false);
showToast(7002);
return;
}
if (images.length === 0) {
setShowLoading(false);
showToast(7004);
return;
}
try {
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
images.forEach(img => {
const trimmedURI = (Platform.OS === "android") ? img.uri : img.uri.replace("file://", "");
const fileName = trimmedURI.split("/").pop();
const media = {
name: fileName,
height: img.height,
width: img.width,
type: mime.getType(trimmedURI),
uri: trimmedURI
};
formData.append("report-images", media);
});
const response = await axios.post(`http://<my-ip-address>:3003/api/report/submit`, formData, { headers: { 'Content-Type': "application/x-www-form-urlencoded" } });
console.log(response)
setShowLoading(false);
}
catch (error) {
console.log(error);
setShowLoading(false);
showToast(9999);
}
};
Backend
// Multer-S3 Configuration
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.AWS_S3_BUCKET_NAME,
contentType: (req, file, callback) => {
callback(null, file.mimetype);
},
metadata: (req, file, callback) => {
callback(null, { fieldName: file.fieldname });
},
key: (req, file, callback) => {
callback(null, `${process.env.AWS_S3_REPORT_IMAGES_OBJECT_PATH}${req.body.userId}/${new Date().getTime().toString()}-${file.originalname}`)
}
}),
fileFilter: (req, file, callback) => {
// Check if file formats are valid
if (file.mimetype === "image/png" || file.mimetype === "image/jpg" || file.mimetype === "image/jpeg") {
callback(null, true);
}
else {
callback(null, false);
return callback(new Error("Image File Type Unsupported"));
}
},
});
router.post("/submit", upload.array("report-images", 3), async (req, res) => {
try {
// req -> FormData consisting of userId, location & description
// multer-s3 library will handle the uploading to S3 - no need to code here
// Details of files uploaded on S3 (Bucket, Key, etc.) will be displayed in req.files
// Analyze from Rekognition
//Add to Database code blablabla
if (result.success === true) {
res.status(200).send({ message: result.data });
}
else if (result.success === false) {
res.status(404).send({ error: ERROR_MESSAGE });
}
}
catch (error) {
console.log(error);
res.status(404).send({ error: ERROR_MESSAGE });
}
});
I'm unsure if this an Axios problem or some problem on my side.
This project is for my Final Year Project.
So after diving through search results in Google, I've found this StackOverflow post: react native post form data with object and file in it using axios
I took the answer provided by user_2738046 in my code and it worked! Combining with Ali's suggestion here is the final code that worked.
const FormData = global.FormData;
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
images.forEach(img => {
const trimmedURI = (Platform.OS === "android") ? img.uri : img.uri.replace("file://", "");
const fileName = trimmedURI.split("/").pop();
const media = {
name: fileName,
height: img.height,
width: img.width,
type: mime.getType(trimmedURI),
uri: trimmedURI
};
formData.append("report-images", media);
});
const response = await axios({
method: "POST",
url: `http://${<my-ip-address>}:3003/api/report/submit`,
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
transformRequest: (data, error) => {
return formData;
}
});
// If success, clear all text fields
if (response) {
showToast(7005);
setLocation("");
setImages([]);
setDescription("");
}
setShowLoading(false);
You need to change your image uploading code with this one, you also need to install mime npm package.
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
const formData = new FormData();
files = files || [];
if (files.length) {
for (let index = 0; index < files.length; index++) {
const filePayload = files[index];
const file = filePayload.value;
const localUri =
Platform.OS === "android" ?
file.uri :
file.uri.replace("file://", "");
const newImageUri = localUri;
const filename = newImageUri.split("/").pop();
const media = {
name: filename,
height: file?.height,
width: file?.width,
type: mime.getType(newImageUri),
uri: localUri,
};
formData.append(filePayload.name, media);
}
}
const response = await axios.post(`http://<my-ip-address>:3003/api/report/submit`, formData, {
headers: headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});

AWS Preflight response blocked by CORs for PUT method

Here's the actual error
Access to XMLHttpRequest at 'https://uzk3crusd9.execute-api.us-east-2.amazonaws.com/production/contact' from origin 'https://seb-contact-form.netlify.app' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
It runs fine locally, but when I went to connect it to Netlify, this error has appeared.
my lambda function:
const AWS = require('aws-sdk');
AWS.config.update({
region: 'us-east-2'
});
const dynamodb = new AWS.DynamoDB.DocumentClient();
const dynamodbTableName = 'contact-form';
const contactPath = '/contact';
exports.handler = async function(event) {
console.log('Request event: ', event);
let response;
switch(true) {
case event.httpMethod === 'POST' && event.path === contactPath:
response = await createContact(JSON.parse(event.body));
break;
default:
response = buildResponse(404, '404 Not Found');
}
return response;
}
async function createContact(requestBody) {
const params = {
TableName: dynamodbTableName,
Item: requestBody
}
return await dynamodb.put(params).promise().then(() => {
const body = {
Operation: 'SAVE',
Message: 'SUCCESS',
Item: requestBody
}
return buildResponse(200, body);
}, (error) => {
console.error('Oh no! Something went wrong...', error);
})
}
function buildResponse(statusCode, body) {
const response = {
statusCode,
headers: {
'Content-Type': 'application/json',
"Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
"Access-Control-Allow-Methods" : "OPTIONS,POST,PUT,PATCH",
"Access-Control-Allow-Credentials" : true,
"Access-Control-Allow-Origin" : "*",
"X-Requested-With" : "*"
},
body: JSON.stringify(body)
}
return response;
}
I realize that there's a mix between using the post and put methods here. But it worked fine locally, and in some youtube tutorial.
There are two steps:
Enable support for CORS in your Lambda (which you already did).
Enable CORS support in the API Gateway endpoint itself (which I believe you didn't do).
https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html

PDF not uploading to Amazon S3 with Nodejs and Reactjs

I am working on uploading PDF files to S3 using nodejs and react and I am running into an issue where some PDFs are being uploaded and some are not.
This endpoint gets a signed url from AWS
'/api/v1/upload/pdf',
requireAuth,
roleAuthorization(['admin']),
(req, res) => {
const date = new Date();
const year = date.getFullYear();
const key = `${date.toLocaleString('default', {
month: 'long',
})}-${short.uuid('0123456789').slice(0, 2)}-${year}.pdf`;
s3.getSignedUrl(
'putObject',
{
Bucket: 'bucket-name',
ContentType: 'application/pdf',
Key: key,
},
(err, url) => res.send({ key, url })
);
}
);
And this endpoint does the upload from react js and save the link to the file in the database
const createIssue = (dispatch) => async (issue, town) => {
try {
const token = await localStorage.getItem('token');
const uploadConfig = await axios.get('/api/v1/upload/pdf', {
headers: {
Authorization: `Bearer ${token}`,
},
});
const upload = await axios.put(uploadConfig.data.url, issue, {
headers: {
'Content-Type': issue.type,
},
});
const response = await api.post(`/api/v1/town/${town._id}/issue`, {
issue: uploadConfig.data.key,
});
dispatch({ type: 'create_issue', payload: response.data });
} catch (err) {
dispatch({
type: 'add_error',
payload: err.message,
});
}
};
This works but does not work for all PDF files, the file remains pending and does not upload.
Any help welcome.
thanks

Resources