fileFilter error, and using csurf
My csrf protection middleware, using "csurf", is configured after multer, as it should (I guess).
If I have an error, for example, in the fileFilter function :
app.use(multer({ storage: fileStorage, fileFilter: fileFilter }).single('image'));
Then, it goes straight to the express error-handling middleware :
app.use((error, req, res, next) => {
res.status(500).render('500', {
pageTitle: 'Error!',
path: '/500',
isAuthenticated: req.session.isLoggedIn
});
});
But in the page I render, I use the csrf token defined in a previous middleware, so it's undefined ('invalid csrf token').
How can I then use multer with csurf, for those errors ?
Here's the fileFilter function :
const fileFilter = (req, file, cb) => {
if (
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpg' ||
file.mimetype === 'image/jpeg'
) {
cb(null, true);
} else {
cb('INVALID FILE!!!!', false);
}
};
As I said, it is triggered before the request is handled by the csrf middleware, hence the issue.
I believe csrf should be checked before reaching multer middleware! Any operations ( write/update/delete ) operation should verify the csrf before actually performing it in server. [ General principle ]
Ok, but how ? If the "csurf" middleware is placed before "multer", then the app crashes because of unprocessed file data.
File data must be processed by "multer" before. Then, "csurf" and the rest of the app can move on.
So, how can I throw a multer error, and still having "csurf" working ?
Maybe you sorted fileFilter after you declare upload. I declare fileFilter before declare upload and it works
@minhnvgch17079 But you are not using csrf (csurf package) in your example.
My problem is that I get "csrfToken is not defined". Multer throws the "invalid file" error, then my app goes straight to the error handling middleware :
app.use((error, req, res, next) => {
res.status(500).render('500', {
pageTitle: 'Error!',
path: '/500',
isAuthenticated: req.session.isLoggedIn
});
});
So the rendered view does not know about the csrfToken.
Look where I define csurf and "res.locals.csrfToken".
I cannot define this BEFORE multer, because I'm getting "ForbiddenError: invalid csrf token" on the server side (even if VALID file type).
Here's my full app.js :
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const csrf = require('csurf');
const flash = require('connect-flash');
const multer = require('multer');
const errorController = require('./controllers/error');
const User = require('./models/user');
const MONGODB_URI =
'mongodb+srv://blabla:[email protected]/shop?retryWrites=true&w=majority';
const app = express();
const store = new MongoDBStore({
uri: MONGODB_URI,
collection: 'sessions'
});
const csrfProtection = csrf();
const fileStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'images');
},
filename: (req, file, cb) => {
cb(null, new Date().toISOString().replace(/:/g, '-') + '-' + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
if (
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpg' ||
file.mimetype === 'image/jpeg'
) {
cb(null, true);
} else {
cb('invalid file', false);
}
};
app.set('view engine', 'ejs');
app.set('views', 'views');
const adminRoutes = require('./routes/admin');
const shopRoutes = require('./routes/shop');
const authRoutes = require('./routes/auth');
app.use(
session({
secret: 'my secret',
resave: false,
saveUninitialized: false,
store: store
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(
multer({ storage: fileStorage, fileFilter: fileFilter }).single('image')
);
app.use(express.static(path.join(__dirname, 'public')));
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use(csrfProtection);
app.use(flash());
app.use((req, res, next) => {
res.locals.isAuthenticated = req.session.isLoggedIn;
res.locals.csrfToken = req.csrfToken();
next();
});
app.use((req, res, next) => {
if (!req.session.user) {
return next();
}
User.findById(req.session.user._id)
.then(user => {
if (!user) {
return next();
}
req.user = user;
next();
})
.catch(err => {
next(new Error(err));
});
});
app.use('/admin', adminRoutes);
app.use(shopRoutes);
app.use(authRoutes);
app.get('/500', errorController.get500);
app.use(errorController.get404);
app.use((error, req, res, next) => {
res.status(500).render('500', {
pageTitle: 'Error!',
path: '/500',
isAuthenticated: req.session.isLoggedIn
});
});
mongoose
.connect(MONGODB_URI, { useNewUrlParser : true })
.then(result => {
app.listen(3000);
})
.catch(err => {
console.log(err);
});
I have figured out all the explanations.
multer looks at each input in order. If there's an error in the image field (like my "invalid file" error), it will throw an error, but in my error handling middleware, my req.body will only contain the form data up to, but excluding, the image field.
That means I can move, in my form html, the hidden csrf input field anywhere before the image input field.
So now, when multer throws an error, I have access to the current csrf value with req.body._csrf . Note that in this case, I should not use req.csrfToken() in the error handling middleware, since that will set a new token to be used in the next rendered view. Here, it is a "post" route, so I need to use the csrf token that was first loaded in the html ("get" route).
@trogne This was resolved for you? If you've sorted things feel free to close the issue, if you have more questions or more information we can keep this open 👍
I have figured out all the explanations.
multer looks at each input in order. If there's an error in the image field (like my "invalid file" error), it will throw an error, but in my error handling middleware, my req.body will only contain the form data up to, but excluding, the image field.
That means I can move, in my form html, the hidden csrf input field anywhere before the image input field.
So now, when multer throws an error, I have access to the current csrf value with req.body._csrf . Note that in this case, I should not use req.csrfToken() in the error handling middleware, since that will set a new token to be used in the next rendered view. Here, it is a "post" route, so I need to use the csrf token that was first loaded in the html ("get" route).
Hi, I'm having this exact issue. When I tried to upload it failed and then goes straight to the error handling as well leaving all the required local variables for the templating engine to be undefined and failed to render the ejs file. Looking at your code, I assume that you're taking the NodeJS course from Udemy at the time so maybe you can help me with this. I tried what you say but unfortunately no luck.
I have figured out all the explanations. multer looks at each input in order. If there's an error in the image field (like my "invalid file" error), it will throw an error, but in my error handling middleware, my req.body will only contain the form data up to, but excluding, the image field. That means I can move, in my form html, the hidden csrf input field anywhere before the image input field. So now, when multer throws an error, I have access to the current csrf value with req.body._csrf . Note that in this case, I should not use req.csrfToken() in the error handling middleware, since that will set a new token to be used in the next rendered view. Here, it is a "post" route, so I need to use the csrf token that was first loaded in the html ("get" route).
Hi, I'm having this exact issue. When I tried to upload it failed and then goes straight to the error handling as well leaving all the required local variables for the templating engine to be undefined and failed to render the ejs file. Looking at your code, I assume that you're taking the NodeJS course from Udemy at the time so maybe you can help me with this. I tried what you say but unfortunately no luck.
I don't know if it's too late, but moving app.use(csrfProtection) below bodyParser and multer works for me.
I have figured out all the explanations. multer looks at each input in order. If there's an error in the image field (like my "invalid file" error), it will throw an error, but in my error handling middleware, my req.body will only contain the form data up to, but excluding, the image field. That means I can move, in my form html, the hidden csrf input field anywhere before the image input field. So now, when multer throws an error, I have access to the current csrf value with req.body._csrf . Note that in this case, I should not use req.csrfToken() in the error handling middleware, since that will set a new token to be used in the next rendered view. Here, it is a "post" route, so I need to use the csrf token that was first loaded in the html ("get" route).
Hi, I'm having this exact issue. When I tried to upload it failed and then goes straight to the error handling as well leaving all the required local variables for the templating engine to be undefined and failed to render the ejs file. Looking at your code, I assume that you're taking the NodeJS course from Udemy at the time so maybe you can help me with this. I tried what you say but unfortunately no luck.
I don't know if it's too late, but moving app.use(csrfProtection) below bodyParser and multer works for me.
I managed to solve the problem already, but thanks anyway!
I have figured out all the explanations. multer looks at each input in order. If there's an error in the image field (like my "invalid file" error), it will throw an error, but in my error handling middleware, my req.body will only contain the form data up to, but excluding, the image field. That means I can move, in my form html, the hidden csrf input field anywhere before the image input field. So now, when multer throws an error, I have access to the current csrf value with req.body._csrf . Note that in this case, I should not use req.csrfToken() in the error handling middleware, since that will set a new token to be used in the next rendered view. Here, it is a "post" route, so I need to use the csrf token that was first loaded in the html ("get" route).
Hi, I'm having this exact issue. When I tried to upload it failed and then goes straight to the error handling as well leaving all the required local variables for the templating engine to be undefined and failed to render the ejs file. Looking at your code, I assume that you're taking the NodeJS course from Udemy at the time so maybe you can help me with this. I tried what you say but unfortunately no luck.
I don't know if it's too late, but moving app.use(csrfProtection) below bodyParser and multer works for me.
I managed to solve the problem already, but thanks anyway!
What was the solution?
Perhaps someone may be getting error isLoggedIn undefined error. That error is occurring because new Date().toISOString() bypasses the session middleware to central error middleware where our session middleware can't set request object fully. And whenever we do submit of content-type: multipart/form-data then on submission the session get's reset somehow. So isLoggedIn data is lost that's why we get undefined.
Simple solution to this problem will be just remove new Date().toISOString() part and other string concatenation part because that is main reason to cause this error. filename: (req, file, cb) => { cb( null, file.originalname.replace(/ /g, "_") ); },
Just like this your error will be solved.