How to restrict file upload, if it is not valid file format in loopback? - file-upload

I have added meta data by referring How to store files with meta data in LoopBack?
Now I have to check if the filetype is in csv, before uploading it to the server.
Right now, I delete the uploaded file if it is not valid. Is there a better way to solve this?
let filePath;
File.app.models.container.upload(ctx.req, ctx.result, options, function(err, fileObj) {
if (err) {
callback(err);
}
let fileInfo = fileObj.files.file[0];
filePath = path.join("server/storage", fileInfo.container, fileInfo.name);
if (fileInfo.type === "text/csv") {
File.create({
name: fileInfo.name,
type: fileInfo.type,
container: fileInfo.container,
url: path.join(CONTAINERS_URL, fileInfo.container, "/download/",
fileInfo.name)
}, function(err, obj) {
if (err) {
callback(err);
}
callback(null, filePath);
});
} else {
fs.unlinkSync(filePath); //delete if it is not csv
let error = new Error();
error.message = "Please upload only csv file";
error.name = "InvalidFile";
callback(error);
}
});

Here is what I've done,
in middleware.json
"parse": {
"body-parser#json": {},
"body-parser#urlencoded": {"params": { "extended": true }}
},
in server/server.js
var multer = require('multer');
var boot = require('loopback-boot');
var app = module.exports = loopback();
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
app.use(multer()); // for parsing multipart/form-data
and remote method as ,
File.remoteMethod(
'upload', {
description: 'Uploads a file with metadata',
accepts: {arg: 'ctx', type: 'object', http: function(ctx) {
console.log(ctx.req.files);
return ctx;
}
},
returns: {arg: 'fileObject', type: 'object', root: true},
http: {verb: 'post'}
}
);
Now ctx can give the mime type..
Update 1:
There is another easier option,
You could define the restriction in datasources.local.js as below, I tested with a filesystem provider
module.exports = {
container: {
root: "./upload",
acl: 'public-read',
allowedContentTypes: ['image/jpg', 'image/jpeg', 'image/png'],
maxFileSize: 10 * 1024 * 1024
}
}

Related

AWS SDK for JavaScript v3 PutObjectCommand error 'A header you provided implies functionality that is not implemented'

I'm trying to upload a file with node.js from my client app (electron) to an S3 bucket in this manner:
const { S3Client, PutObjectCommand } = require('#aws-sdk/client-s3');
const s3Client = new S3Client({
region: 'eu-central-1',
credentials: {
accessKeyId: 'access',
secretAccessKey: 'secret',
},
});
const uploadFileToS3 = async (f) => {
const bucketParams = {
ACL: 'private',
Bucket: 'bucket',
Key: f.name,
Body: f.data,
ServerSideEncryption: 'AES256',
ContentType: 'image/png',
};
try {
return await s3Client
.send(new PutObjectCommand(bucketParams))
.then((result) => {
return process.send({
type: 'success',
fileName: f.name,
result,
});
});
} catch (erro) {
process.send({
type: 'error',
fileName: f.name,
error: erro,
});
}
};
process.on('message', (file) => {
uploadFileToS3(file);
});
I get the following error, that I'm unable to understand:
error: {
name: 'NotImplemented',
'$fault': 'client',
'$metadata': {
httpStatusCode: 501,
requestId: 'PXEBV6H4MX3',
extendedRequestId: 'yyyyyy',
attempts: 1,
totalRetryDelay: 0
},
Code: 'NotImplemented',
Header: 'Transfer-Encoding',
RequestId: 'PXEBV6H4MX3',
HostId: 'yyyyyy',
message: 'A header you provided implies functionality that is not implemented'
}
The file is a buffer generated with:
fs.readFileSync(pth)
Any idea of what could caused this error ?
Seems like the buffer created with
fs.readFileSync(pth)
was rejected and I could only use a stream:
const readableStream = await createReadStream(Buffer.from(f));
Maybe I'm wrong but it is possible that the actual SDK version is unable to accept a buffer yet, this could be the reason for that "missing functionality" message.

Vue Apollo Upload file crashes Node Maximum call stack size exceeded at _openReadFs

I am trying to setup front end for graphQl file upload with Apollo-boost-upload. The backend code is based on this link
https://dev.to/dnature/handling-file-uploads-with-apollo-server-2-0-14n7.
It's now reaching the resolver breakpoint after adding the following line in the server.js file
const { apolloUploadExpress } = require("apollo-upload-server");
app.use(apolloUploadExpress({ maxFileSize: 1000000000, maxFiles: 10 }));
And after modifying the schema for the upload type
scalar Upload
Here is the Vue component
<input
type="file"
style="display:none"
ref="fileInput"
accept="image/*"
#change="upload"
>
//Upload method
upload({ target: { files = [] } }) {
if (!files.length) {
return;
}
this.logoImage = files[0];
},
//Dispatching action from vue component
this.$store.dispatch("uploadLogo", { image: this.logoImage });
//Vuex action
const uploadLogo = async (context, payload) => {
context.commit("setLoading", true);
try {
const { data } = await apolloClient.mutate({
mutation: UPLOAD_LOGO,
variables: {file: payload.image},
context: {
hasUpload: true,
},
});
context.commit("setLoading", false);
console.log("Logo:", data.uploadLogo);
} catch (error) {
context.commit("setLoading", false);
console.log(error);
}
};
//Mutation
export const UPLOAD_LOGO = gql`
mutation uploadLogo($file: Upload!) {
uploadLogo(file: $file) {
_id
path
filename
mimetype
user {
_id
}
}
}
`;
// Apolloclient config on main.js
import ApolloClient from "apollo-boost-upload";
import { InMemoryCache } from "apollo-boost";
import VueApollo from "vue-apollo";
// Set up Apollo Client
export const defaultClient = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache({
addTypename: false,
}),
fetchOptions: {
credentials: "include",
},
request: (operation) => {
// if no token in local storage, add it
if (!localStorage.someToken) {
localStorage.setItem("someToken", "");
}
// operation adds the token to authorizatrion header, which is sent o backend
operation.setContext({
headers: {
authorization: "Bearer " + localStorage.getItem("someToken"),
},
});
},
onError: ({ graphQLErrors, networkError }) => {
if (networkError) {
console.log("[networkError]", networkError);
}
if (graphQLErrors) {
for (const error of graphQLErrors) {
console.dir(error);
if (error.name === "AuthenticationError") {
// set auth errir in state
store.commit("setError", error);
// signout user to clear error
store.dispatch("signUserOut");
}
}
}
},
});
Here is the updated typedef (old code commented out) from backend if that helps to identify the issue
const logoUploadTypeDefs = gql`
type File {
_id: ID!
path: String!
filename: String!
mimetype: String!
encoding: String!
user: User
}
# input Upload {
# name: String!
# type: String!
# size: Int!
# path: String!
# }
scalar Upload
type Mutation {
uploadLogo(file: Upload!): File
}
type Query {
info: String
logo: File!
}
`;
Now, the Node app crashes with the following log
I had to change "apollo-upload-server" to "graphql-upload"
change 1:
Commented out "apollo-upload-server" and used "graphql-upload"
// const { apolloUploadExpress } = require("apollo-upload-server");]
const {
graphqlUploadExpress, // A Koa implementation is also exported.
} = require("graphql-upload");
And in the middleware, used this
change 2:
app.use(graphqlUploadExpress());
await apolloServer.start();
instead of old code
app.use(apolloUploadExpress());// Not to be used
await apolloServer.start();
Also, in the resolver, I added this
change 3:
Import Upload from graphql-upload in the resolver file
const { GraphQLUpload } = require("graphql-upload");
....
....
const resolvers = {
// This maps the `Upload` scalar to the implementation provided
// by the `graphql-upload` package.
Upload: GraphQLUpload,
Query: {
....
}
Mutations: {
....
}
}
Refer to Apollo Docs for more details. This fixed the issue of Node crashing with error "Maximum call stack size exceeded at _openReadFs..."

Cloudinary\Error: Missing required parameter - file - Express and Postman

first time trying to upload images to Cloudinary and I have come across an issue when using Express via Postman.
Using form-data on setting 'file' to upload an image to Cloudinary
As of now, when I try to access the req.body I get an empty object, so I guess that has to do with why cloudinary.uploader.upload cannot read the file passed as its first param, since its req.body.file, as shown in the code below.
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_KEY,
api_secret: process.env.CLOUDINARY_SECRET
})
exports.upload = async (req, res) => {
try{
console.log(req.body);
const result = await cloudinary.uploader.upload(req.body.file, {
public_id: `${Date.now()}`,
resource_type: "auto"
})
return res.json({
public_id: result.public_id,
url: result.secure_url
})
}catch(err){
console.log(err)
}
}
The error message I get:
{
message: 'Missing required parameter - file',
name: 'Error',
http_code: 400
}
Any suggestions to solve this issue?
I solved it! I was not able to pass the form-data as req.body to the server, so I had to try and access it through req.files, but was not able to with that either, so I searched a bit and found a middleware 'express-fileupload', and that did the trick. I just added it in my app.js and used
const fileupload = require('express-fileupload');
app.use(fileupload({useTempFiles: true}))
So now I can access my req.files.
exports.upload = async (req, res) => {
const file = req.files.image
try{
console.log(file);
const result = await cloudinary.uploader.upload(file.tempFilePath, {
public_id: `${Date.now()}`,
resource_type: "auto"
})
res.json({
public_id: result.public_id,
url: result.secure_url
})
}catch(err){
console.log("Error", err)
return res.status(400).json({error: err})
}
}
The response I get is:
{
name: 'some-image.png',
data: <Buffer >,
size: 99770,
encoding: '7bit',
tempFilePath: ' **C:\\filepath\some-image.png** ',
truncated: false,
mimetype: 'image/png',
md5: 'b5f612a571442bf604952fd12c47c1bf',
mv: [Function: mv]
}
POST /cloudinary/upload-images 200 1617.944 ms - 119
And it is uploaded successfully to my Cloudinary.
const result = await cloudinary.uploader.upload(req.file, {
public_id: ${Date.now()},
resource_type: "auto"
})
and add file from form data and type should be File
Solved!
This is how i am setting the FormData
let myTestForm = new FormData();
myTestForm.set("name", name);
myTestForm.set("email", email);
myTestForm.set("Avatar", Avatar);
myTestForm.set("password", password);
This is how i am using the FormData
const config = {
headers: {
"Content-type": "multipart/form-data",
},
};
const { data } = await axios.post(`/api/v1/register`, userData, { config });
please don't pass it this way { userData} , had struggled for with this :/
This is how i am uploading image
const myCloud = await cloudinary.v2.uploader.upload(req.body.Avatar, {
folder: "Avatars",
width: 150,
crop: "scale",
public_id: `${Date.now()}`,
resource_type: "auto",
});
PS : in my case i had to upload only 1 image. Have not passed any parameter in app.js file
app.use(bodyParser.urlencoded({ extended: true }));
app.use(fileUpload());

Getting Error 500 (general-error) while uploading files in feathersjs

I am getting this error on Postman when I send request for uploading files on Feathersjs:
{
"name": "GeneralError",
"message": "ENOENT: no such file or directory, open 'public/uploads/pic'",
"code": 500,
"className": "general-error",
"data": {},
"errors": {}
}
My uploads.service.js:
const {Uploads} = require('./uploads.class');
const createModel = require('../../models/uploads.model');
const hooks = require('./uploads.hooks');
const multer = require('multer');
const storage = multer.diskStorage({
destination: (_req, _file, cb) => cb(null, 'public/uploads'), // where the files are being stored
filename: (_req, file, cb) => {
console.log(_req.body);
//cb(null, ${_req.body.name});
cb(null, `${_req.body.name}`); //
}, // getting the file name
});
const uploads = multer({
storage,
limits: {
fieldSize: 1e8,
fileSize: 1e7,
},
});
module.exports = function(app) {
const options = {
Model: createModel(app),
paginate: app.get('paginate'),
multi: true,
};
// Initialize our service with any options it requires
app.use(
'/uploads',
uploads.array('files'),
(req, _res, next) => {
const {method} = req;
if (method === 'POST' || method === 'PATCH') {
console.log(req.files);
console.log(req.body);
req.feathers.files = req.body.files;
const body = [];
for (const file of req.files)
body.push({
name: req.body.name,
newNameWithPath: file.path,
});
req.body = method === 'POST' ? body : body[0];
}
next();
},
new Uploads(options, app),
);
// Get our initialized service so that we can register hooks
const service = app.service('uploads');
service.hooks(hooks);
};
This is my uploads.model.js:
module.exports = function(app) {
const modelName = 'uploads';
const mongooseClient = app.get('mongooseClient');
const {Schema} = mongooseClient;
const schema = new Schema(
{
name: {type: String, required: true},
},
{
timestamps: true,
},
);
// This is necessary to avoid model compilation errors in watch mode
// see https://mongoosejs.com/docs/api/connection.html#connection_Connection-deleteModel
if (mongooseClient.modelNames().includes(modelName)) {
mongooseClient.deleteModel(modelName);
}
return mongooseClient.model(modelName, schema);
};
I really cannot figure out where exactly the problem is. According to me it is supposed to make the folder automatically when I upload the file.
I would really appreciate some help. Thank you in advance.
It was my own mistake. I made the uploads folder inside public folder myself and now it's working.

Hapi Js File Upload Struggling

today i trying to get some file upload with Hapi Js, i follow all Google Result with similarity of code.
this the code :
server.route({
method: "POST",
path: `${PUBLIC_URL}${THEME_URL}/create`,
handler: async (request: any, reply: ResponseToolkit) => {
console.log(request.payload.file, 'payload')
return reply.response(request.payload)
},
options: {
payload: {
output: 'stream',
allow: 'multipart/form-data',
parse: false,
}
}
})
with thats code i cant get request.payload my file or data, this is my request with postman:
post file with postman
enter image description here
i got undifined at request.payload.file
if i turn payload :{parse:true} i get unsuported media types
thanks for attention
If you are using the below version then you must be using the following syntax
#hapi/hapi: 18.x.x +
payload: {
parse: true,
multipart: {
output: 'stream'
},
maxBytes: 1000 * 1000 * 5, // 5 Mb
}
Also, you can also try using Joi to validate your payload.
{
method: 'POST',
path: '/upload',
options: {
payload: {
maxBytes: 209715200,
output: 'stream',
parse: true,
allow: 'multipart/form-data',
multipart: true // <-- this fixed the media type error
},
handler: async (req, reply) => {
try {
// await importData(req.payload)
// return reply.response("IMPORT SUCCESSFULLY")
const data = await req.payload;
// let final = await importFile(data)
// return reply.response("final", final)
if (data.file) {
let name = await data.file.hapi.filename;
console.log("FIlename: " + name);
let path = await __dirname + "/uploads/" + name;
let file = await fs.createWriteStream(path);
await data.file.pipe(file);
await data.file.on('end', async function (err) {
// var ret = {
// filename: data.file.hapi.filename,
// headers: data.file.hapi.headers
// }
if (typeof require !== 'undefined')
XLSX = require('xlsx');
const workbook = await XLSX.readFile(path);
var sheetName = workbook.SheetNames;
console.log("row======>>>>");
await sheetName.forEach(async () => {
let xlData = await XLSX.utils.sheet_to_json(workbook.Sheets[sheetName[0]]);
console.log("xlData", xlData);
for (let i = 0; i < xlData.length; i++) {
console.log("if condition", xlData[i].phone)
const userCheck = await getUserIdService({ where: { phone: xlData[i].phone } });
console.log("userCheck", userCheck.data)
console.log("test", !(userCheck.data === null));
if (!(userCheck.data === null)) {
console.log("finally ", userCheck.data?.phone)
await uploadUpdateService(xlData[i], { where: { phone: userCheck.data?.phone } });
// return finalUpdate
// return reply.response("updated")
}
else if (!xlData[i].customerID) {
await uploadCreate(xlData[i]);
// return finalCreate
}
}
})
})
}
} catch (err) {
console.log('Err----------------------' + err);
// error handling
return reply.response(Boom.badRequest(err.message, err))
// return reply.response(Boom.badRequest(err.message, err));
}
}
}
}