编辑:找到解决方案 嗨,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')); 上会话顶部)
它仅在 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')); 上会话顶部)