File validation using Multer - Expressjs - node.js

I have a form which have multiple fields one of them is file field for uploading image.
name = 'john doe'
location = 'Some location'
image = (binary)
My question is how do I validate this image file for both the times i.e CREATE AND UPDATE.
For create, every field is mandatory but for edit, user might not want to update image but only text fields hence he won't select an image file
router.post('/user', upload.single('image'), userRoute);
I have made a middleware like this but this does not give proper validation result. Let's assume that user has selected an image and pressed the submit button then I will get the like this in req.file.
{ fieldname: 'image',
originalname: 'images (2).jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'uploads/top-level',
filename: 'images (2)-1583345397445.jpg',
path: 'uploads\\top-level\\images (2)-1583345397445.jpg',
size: 33766 }
which make sense but suppose user selected some zip file which is not a file type which I want user to submit hence I have to catch it and show it to user that the file type is not an image. Let me know if i can do using above mentioned middleware.
router.post('/user', userRoute);
and i userRoute function I wrote something like this to catch the error
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// catch error and show to user
} else if (err) {
// catch error and show to user here also
}
// Logic goes here
// and we get the req.body here
})
this is good enough for create where every field is mandatory
But for edit how can I save data to database if user is not updating image which means I am getting only two fields
name
location
If user is selecting image then I will save otherwise i will skip image and save only two fields. HOW can i achieve this and if user is not selecting correct image type then i will have to show error to user.
here is the multer code
var upload = multer({
storage: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/top-level')
},
filename: function (req, file, cb) {
cb(null, file.originalname.split('.')[0] + '-' + Date.now() + path.extname(file.originalname))
}
}),
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" || file.mimetype == "image/jpeg") {
cb(null, true);
} else {
return cb(null, false);
}
}
});

First of all : You should have validation on both front & back ends in
order to provide the best experience possible and avoid "stupid"
errors which can (and will) ultimately become flaws just waiting to be
exploited by anyone that gets it.
Second : YOU NEVER SHOW ERRORS TO CLIENTS (EVER)..consider using the
connect-flash middleware to send a flash message either in
notifications or anywhere in your app(check docs for flash()).
On the front-end , you should block uploads of filetypes by extensions which is pretty easy either on native html or through JS ( you didn't specify your frontend so I will skip this part for the sake of clarity ).
On the backend , you can check on the "req.file.mimetype" to see if it's in an array of extensions allowed by your upload functionality.
something like this:
var authorizedMimeTypes=['jpg','jpeg',...];
if(req.file.mimetype in authorizedMimeTypes){
//your code goes here..
}else{
//your error code goes here..
};

Related

Angular NodeJS Upload File and parameters together

I have been looking through multiple tutorials and stack overflow questions but for some reason I just cannot make this work. I have issues with uploading a file, so maybe fixing that first would solve the whole issue.
I tried a few options of sending a file from the front end to the back end, but it seems to always "get lost" before reaching the back end.
I have decided to use multer at the NodeJS backend to upload the file. Not sure if I am calling multer upload single right or not. Currently this is the code which I have for it:
const multer = require('multer');
const storage = multer.diskStorage({
destination: './uploadedImages',
filename: function(req,file,cb){
cb(null,file.originalname)
}
}) ;
const upload = multer({storage: storage})
exports.saveDrawing = async(req, res, next) => {
try
{
//save image
//tried a few different logs, but with FormData it seems like everything always empty
console.log("Image:");
console.log(req.body.drawingElement);
console.log(req.file);
upload.single('body.file');
return res.status(200).json({message: element});
}
}
catch (err)
{
console.log("Error at drawing save: " + err)
return res.status(500).json({message: "Error - Could not add/edit Drawing"});
}
}
And this is how it is sent from the Angular front end:
setDrawing(params, image): Observable<any> {
const formData = new FormData();
formData.append('file', image)
formData.append('data', params)
console.log("File: ");
console.log(formData.get('file'));
console.log("Data: ");
console.log(formData.get('data'));
return this.http.post<any>(`api/v1/structure/drawing/save`, formData);
}
At this stage printing out the data shows the right values. And the browser shows the right payload too:
At the back end I cannot see them in the req, req.body is empty, there is no req.form. For this api call before I have tried to include any files without the FromData I have accessed the data from req.body.
Am I looking for the data at the right place?
You're not using multer correctly, it's not doing anything.
To implement it as a middleware which you call from your handler, check the example from the docs
So, your handler should look something like this:
// setup multer middleware, set file field name
const upload = multer({storage: storage}).single('file');
exports.saveDrawing = async(req, res, next) => {
// now use the middleware, handle errors
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// A Multer error occurred when uploading.
return res.status(500).json({message: "Error - Could not add/edit Drawing"});
} else if (err) {
// An unknown error occurred when uploading.
return res.status(500).json({message: "Error - Could not add/edit Drawing"});
}
// Everything went fine.
console.log("Image:");
console.log(req.body.drawingElement);
console.log(req.file);
return res.status(200).json({message: element});
});
});

display file name in a single array in multer using nodejs

here is a part of my code of multer using nodejs
const storageEngine = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './Images')
const Files = [file.originalname]
console.log(Files)
},
filename: (req, file, cb) => {
cb(null, file.originalname)
}
})
the console log of the above code is this:
['image.png']
['image2.png']
['image3.png]
so i want all file names to be in one array to be posted in mysql in one go as i do not want to send multiple request to mysql as there are numerous amount of images.
so all i want is the file names to be present in one single array like this:
['image.png', 'image2.png', 'image3.png']
The destination event that you use is invoked once per uploaded file, so you cannot use it to collect all file names.
Assuming you have an input field <input type="file" multiple name="upload">, you can instead use
app.use(storageEngine.array("upload"))
.use(function(req, res, next) {
console.log(req.files.map(file => file.originalname));
next();
});

Save image url to database after upload

I upload an image from a react native expo app.
In the back end, I have this code :
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, "avatars");
},
filename: (req, file, callback) => {
let imagePath = Date.now() + path.extname(file.originalname);
callback(null, imagePath);
},
});
const upload = multer({ storage: storage });
app.use("/uploadAvatar", upload.single("avatar"), (req, res) => {
res.status(200).json("Image enregistrée !");
});
It works fine and save the image into a folder.
What I need to do, is to save the image path in the database.
The image is an avatar for a user profile, so I need to add its path to the user table.
Suppose you are storing the image in your middleware:
app.use('/uploadAvatar', upload.single('avatar'), async (req, res) => {
// Assume that you're storing the image with a function,
// in this case you just need to pass the filename into
// your function. This is a pseudocode and is not expected to work
// as it is, so please adjust accordingly.
const result = await storeImage(req.file.filename);
// Return the response as JSON.
res.status(200).json('Image enregistrée!');
});
Basically, you have to inspect the req.file that would be populated after you call the upload.single('avatar') middleware. In this case, we're taking the filename to be stored into the database. You can do any path/file/property manipulations that you might want to do before storing that image, though.
For further information about req.file and what properties it contains, please see the Multer documentation.

how to determine real type of file without check file extension using multer and node js

i want to check the real type of image file when i upload it using multer in nodejs. for example when i check the mimtype or ext of files, if the ext of uploaded photo changed from rar to jpeg. the server will accept the file but it shoudnt.could anyone help me with file filter?
var upload = multer({
storage: storage,
dest: "uploads/",
fileFilter: function (req, file, cb) {
if (
file.mimetype !== "image/png" &&
file.mimetype !== "image/gif" &&
file.mimetype !== "image/jpeg"
) {
return cb(null, false);
} else {
cb(null, true);
}
},
});
The only way to check the actual type of a file is by reading its content.
When 'fileFilter' play a role in file uploading. The server in fact does not own this file, so the 'file' argument only have some very limited property.
So it's best to check the file type on the client side, reject the request at the very begin if file type mismatch or whatever you want, anyhow you can check the actual file type in the callback function of express http method since the file is allocated in the server somewhere (I am using 'file-type' package for this purpose)`
const storage = multer.memoryStorage()
const upload = multer({
storage: storage
})
const FileType = require('file-type')
app.post('/uploadfile', upload.single('file'), async (req, res) => {
console.log(Object.keys(req.file))
console.log(await FileType.fromBuffer(req.file.buffer));
res.send()
}) `
Maybe you can do somehing in the middleware, since I learned node.js 3 weeks ago so I will just leave it here.

How do I make a form-data request in node pulling the files from s3

Hey everyone so I am trying to make this type of request in nodejs. I assume you can do it with multer but there is one major catch I don't want to download the file or upload it from a form I want to pull it directly from s3, get the object and send it as a file along with the other data to my route. Is it possible to do that?
Yes it's completely possible. Assuming you know your way around the aws-sdk, you can create a method for retrieving the file and use this method to get the data in your route and do whatever you please with them.
Example: (Helper Method)
getDataFromS3(filename, bucket, callback) {
var params = {
Bucket: bucket,
Key: filename
};
s3.getObject(params, function(err, data) {
if (err) {
callback(true, err.stack); // an error occurred
}
else {
callback(false, data); //success in retrieving data.
}
});
}
Your Route:
app.post('/something', (req, res) => {
var s3Object = getDataFromS3('filename', 'bucket', (err, file) => {
if(err) {
return res.json({ message: 'File retrieval failed' });
}
var routeProperties = {};
routeProperties.file = file;
routeProperties.someOtherdata = req.body.someOtherData;
return res.json({routeProperties});
});
});
Of course, the code might not be totally correct. But this is an approach that you can use to get what you want. Hope this helps.
There are two ways that I see here, you can either:
pipe this request to user, it means that you still download it and pass it through but you don't save it anywhere, just stream it through your backend.
There is a very similar question asked here: Streaming file from S3 with Express including information on length and filetype
I'm just gonna copy & paste code snippet just for the reference how it could be done
function sendResponseStream(req, res){
const s3 = new AWS.S3();
s3.getObject({Bucket: myBucket, Key: myFile})
.createReadStream()
.pipe(res);
}
if the file gets too big for you to easily handle, create presigned URL in S3 and send it through. User then can download the file himself straight from S3 for a limited amount of time, more details here: https://docs.aws.amazon.com/AmazonS3/latest/dev/ShareObjectPreSignedURL.html

Resources