Search code examples
node.jscsrfmulter

multer and csrf protection


If I have an error with multer, for example an invalid file format, then the csrf protection would failed (invalid token).

That's because the csrf protection is configured after multer.

But csrf protection also fails if I add it before multer. I get : 'invalid csrf token'.

How can I throw a multer error, but with keeping the csrf protection ?

Below the error is thrown at cb('INVALID FILE!!!', false);, and caught by the express error-handling middleware. But the csrf token will be missing.

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(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(
      session({
        secret: 'my secret',
        resave: false,
        saveUninitialized: false,
        store: store
      })
    );
    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) => {
      console.log(error);
      res.status(500).render('500', {
        pageTitle: 'Error!',
        path: '/500',
        isAuthenticated: req.session.isLoggedIn
      });
    });

In short : Why can't I set up csrf protection before the multer ?

And if I really can't, how can I throw a multer error, and keep using csrf in my app ?


Solution

  • 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).