编辑:找到的解决方案嗨,csrf 令牌仅在一个 POST 路由上不起作用

问题描述

它在所有路由中都有效,仅在 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,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,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,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,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,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',description: description,},errorMessage: errors.array()[0].msg,validationErrors: errors.array()

    });

  }

  const imageUrl = image.path;

  const product = new Product({

    title: title,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);  
    
    
    });

};

edit-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,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')); 在会话的顶部)