编辑:找到解决方案 嗨,csrf 令牌不适用于一条 POST 路线

EDIT: SOLUTION FOUND Hi, the csrf token is not working on only ONE POST route

它仅在 POST 上的所有路由中工作,添加产品不起作用,因为令牌未定义,即使我为它分配了中间件和本地人。

您是否知道为什么令牌仅在一条路线上未定义?一个星期以来,我一直在尝试任何事情,但没有任何效果。

下面有一个最小的可重现示例,在此先感谢您

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);
    
    // generating token on every view so that fake websites cannot trick the user into a fraud (log in into their account and make them do stuff)
    const csrf = require('csurf');
    
    // including error messages with connect flash
    const flash = require('connect-flash');
    
    // upload files
    const multer = require('multer');
    
    const errorController = require('./controllers/error');
    const User = require('./models/user');
    
    const MONGODB_URI = 'mongodb+srv://mauro:password@cluster0.kyrqs.mongodb.net/shop';
    
    const app = express();
    
    const store = new MongoDBStore({
    
      uri: MONGODB_URI,
      collection: 'sessions',
    
    });
    
    // handling files
    const fileStorage = multer.diskStorage({
    
      destination: (req, file, cb) => {
    
        // 1st argument error and 2nd where we want to store the files
        cb(null, './images/');
    
      },
    
      filename: (req, file, cb) => {
    
        // 1st argument is the error, 2nd name of the file. with this line we make sure that there are no duplicate names and that the file is stored correctly
        // this line will take out : from the name of the file
        cb(null, new Date().toISOString().replace(/[-T:\.Z]/g, "") + '-' + file.originalname);
    
      }
    
    });
    
    // we can filter the files
    const fileFilter = (req, file, cb) => {
    
      if(file.mimetype === 'image/png' || file.mimetype === 'image/jpg' || file.mimetype === 'image/jpeg') {
    
      // "true" stores the file "false" won't
      cb(null, true)
    
      } else {
    
        cb(null, 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(multer({ storage: fileStorage, fileFilter: fileFilter }).single('image'));
    
    // setting up the sessions
    app.use(
    
      session({
      
        secret: 'my secret',
        resave: false,
        saveUninitialized: false,
        store: store,
      
      })
    
    );
    
    app.use(bodyParser.urlencoded({ extended: false }));
    
    app.use(express.static(path.join(__dirname, './public')));
    
    
    app.use(csrf());
    
    // telling node to use the same "variables" in every path (this is in order to get a token for each page)
    app.use((req, res, next) => {
    
      // locals allows us to set local variables
      res.locals.isAuthenticated = req.session.isLoggedIn;
      res.locals.csrfToken = req.csrfToken();
      next();
    
    });
    
    // handling a single file (input named image). {dest: 'images'} will create a folder named images and store into it the file (I don't think that we need it if we use storage).
    // { storage: fileStorage } makes sure that the name of the file is stored correctly
    // fileFilter (line 55)
    
    // including error messages with connect flash
    app.use(flash());
    
    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.use(errorController.get404);
    
    // app.use(errorController.get500);
    
    // see next(error) on admin controller (this one is a error handling middleware)
    // so when we call next(error) on line 87 in the admin controller we skip all the middlewares that are not error middlewares
    // if we have more than one error middleware node will execute them from top to bottom (as a normal middleware)
    app.use((error, req, res, next) => {
    
      console.log('error middleware ' + error)
    
      res.status(500).render('500', {
    
        pageTitle: 'Something Went Wrong',
        path: '/500',
        isAuthenticated: req.session.isLoggedIn,
    
      });
    
    });
    
    mongoose
    
      .connect(MONGODB_URI, { useNewUrlParser: true })
      
      .then(result => {
             
        app.listen(3000);
      
      })
      
      .catch(err => {
      
        console.log(err);
      
      });

管理员控制器

exports.postAddProduct = (req, res, next) => {

  const title = req.body.title;

  // with multer we changed this variable so that we can handle files that the users upload
  const price = req.body.price;
  const image = req.file;
  const description = req.body.description;

  if(!image) {

    return res.status(422).render('admin/edit-product', {

      pageTitle: 'Add Product',
      path: '/admin/add-product',
      editing: false,
      hasError: true,

      product: {

        title: title,
        price: price,
        description: description

      },

      errorMessage: "file is not an image",
      validationErrors: []

    });
    
  }

  // getting all the errors that we might get from the auth routes (validation)
  const errors = validationResult(req);

  if (!errors.isEmpty()) {

    console.log('errors post add pro', errors);

    return res.status(422).render('admin/edit-product', {

      pageTitle: 'Add Product',
      path: '/admin/add-product',
      editing: false,
      hasError: true,

      product: {

        title: title,
        price: price,
        description: description,

      },

      errorMessage: errors.array()[0].msg,
      validationErrors: errors.array()

    });

  }

  const imageUrl = image.path;

  const product = new Product({

    title: title,
    price: price,
    description: description,
    imageUrl: imageUrl,
    userId: req.user,

  });
  
  product
  
    .save()
    
    .then(result => {
      // console.log(result);
      
      console.log('Created Product');
      res.redirect('/admin/products');
    
    })
    
    .catch(err => { 

      const error = new Error('error:' + err);
      error.httpStatusCode = 500;

      return next(error);  
    
    
    });

};

编辑-product.ejs

<%- include('../includes/head.ejs') %>
    <link rel="stylesheet" href="/css/forms.css">
    <link rel="stylesheet" href="/css/product.css">
</head>

<body>
   <%- include('../includes/navigation.ejs') %>

    <main>

        <% if (errorMessage) { %>

            <div class="centered"><%= errorMessage %></div>
        
        <% } %>

        <form class="product-form" action="/admin/<% if (editing) { %>edit-product<% } else { %>add-product<% } %>" method="POST" enctype="multipart/form-data">

            <input type="hidden" name="_csrf" value="<%= csrfToken %>">
            
            <div class="form-control">
                <label for="title">Title</label>
                <input 
                    class="<%= validationErrors.find(e => e.param === 'title') ? 'invalid' : '' %>"
                    type="text" 
                    name="title" 
                    id="title" 
                    value="<% if (editing || hasError) { %><%= product.title %><% } %>">
            </div>

            <div class="form-control">
                <label for="image">Image</label>

                <input 

                    type="file" 
                    name="image" 
                    id="image"
                
                >
                
            </div>

            <div class="form-control">
                <label for="price">Price</label>
                <input 
                    class="<%= validationErrors.find(e => e.param === 'price') ? 'invalid' : '' %>"
                    type="number" 
                    name="price" 
                    id="price" 
                    step="0.01" 
                    value="<% if (editing || hasError) { %><%= product.price %><% } %>">
            </div>
            <div class="form-control">
                <label for="description">Description</label>
                <textarea 
                    class="<%= validationErrors.find(e => e.param === 'description') ? 'invalid' : '' %>"
                    name="description" 
                    id="description" 
                    rows="5"><% if (editing || hasError) { %><%= product.description %><% } %></textarea>
            </div>
            <% if (editing) { %>
                <input type="hidden" value="<%= product._id %>" name="productId">
            <% } %>

            
            <button class="btn" type="submit"><% if (editing) { %>Update Product<% } else { %>Add Product<% } %></button>
        </form>
    </main>
<%- include('../includes/end.ejs') %>

我相信我在以下几行设置了一个令牌:

app.use(bodyParser.urlencoded({ extended: false })); 

app.use(express.static(path.join(__dirname, './public'))); 

app.use(csrf()); 

app.use((req, res, next) => { 

  res.locals.isAuthenticated = req.session.isLoggedIn; 
  res.locals.csrfToken = req.csrfToken(); 
  next(); 

});

而且我认为我正在用这一行检查令牌

<input type="hidden" name="_csrf" value="<%= csrfToken %>"> 

表格内

解决方案:

如果有人和我做同样的课程,确保中间件的顺序是正确的,并且你改变这一行 new Date().toISOString().replace(/[-T:.Z]/ g, "") + '-' + file.originalname) 到 uuidv4() + '-' + file.originalname 如果你使用 windows (不要忘记安装并要求这一行 const { v4: uuidv4 } = require('uuid'); app.js)。请查看 app.js 以查看中间件的顺序(我基本上移动了 app.use(multer({ storage: fileStorage, fileFilter: fileFilter }).single('image')); 上会话顶部)