Add limitations to file uploads

master
Mark Moffat 2018-06-01 21:01:23 +09:30
parent 6345ef8318
commit 65b18cfe42
5 changed files with 232 additions and 34 deletions

View File

@ -35,6 +35,17 @@ const restrictedRoutes = [
{route: '/admin/file/delete', response: 'json'}
];
// Allowed mime types for product images
exports.allowedMimeType = [
'image/jpeg',
'image/png',
'image/gif',
'image/bmp',
'image/webp'
];
exports.fileSizeLimit = 10485760;
// common functions
exports.restrict = (req, res, next) => {
exports.checkLogin(req, res, next);

188
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "express-cart",
"version": "1.1.5",
"version": "1.1.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -499,6 +499,12 @@
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
"dev": true
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"atob": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz",
@ -1968,6 +1974,15 @@
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM="
},
"combined-stream": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"dev": true,
"requires": {
"delayed-stream": "1.0.0"
}
},
"commander": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz",
@ -2140,6 +2155,12 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"cookiejar": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz",
"integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==",
"dev": true
},
"copy-descriptor": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@ -2353,6 +2374,12 @@
}
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"depd": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
@ -2672,8 +2699,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"escope": {
"version": "3.6.0",
@ -3468,6 +3494,23 @@
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
},
"form-data": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"dev": true,
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.6",
"mime-types": "2.1.17"
}
},
"formidable": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
"integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==",
"dev": true
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -5450,6 +5493,14 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"inherits-ex": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/inherits-ex/-/inherits-ex-1.2.3.tgz",
"integrity": "sha512-DCZqD7BpjXqaha8IKcoAE3ZZr6Hi12ropV1h+3pBnirE14mNRwLuYySvYxUSBemTQ40SjAxPL8BTk2Xw/3IF9w==",
"requires": {
"xtend": "4.0.1"
}
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
@ -6600,9 +6651,20 @@
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
},
"mime-db": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
"integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
},
"mime-type": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/mime-type/-/mime-type-3.0.5.tgz",
"integrity": "sha1-ftKSan2oImifgSVPWYf+lQNiLpo=",
"requires": {
"media-typer": "0.3.0",
"minimatch": "3.0.4",
"path.js": "1.0.7",
"util-ex": "0.3.15"
}
},
"mime-types": {
"version": "2.1.17",
@ -6610,6 +6672,13 @@
"integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
"requires": {
"mime-db": "1.30.0"
},
"dependencies": {
"mime-db": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
"integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
}
}
},
"mimic-fn": {
@ -6673,21 +6742,54 @@
"integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg=="
},
"mongodb": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.0.tgz",
"integrity": "sha1-GN2P+S14k97MQI2virDrQIR1Ip8=",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.3.tgz",
"integrity": "sha1-SSztvnOHVSyc8uT1vEDreOBiA6s=",
"requires": {
"mongodb-core": "3.0.0"
"es6-promise": "3.0.2",
"mongodb-core": "2.0.6",
"readable-stream": "1.0.31"
},
"dependencies": {
"bson": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/bson/-/bson-0.5.7.tgz",
"integrity": "sha1-DRH+CTbB/uAp4R9wY/XQqyQi6j4="
},
"es6-promise": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y="
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"mongodb-core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.0.tgz",
"integrity": "sha1-tNBsvoj6iPaola+lJCpBvVMJvHQ=",
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.0.6.tgz",
"integrity": "sha1-fxvnnYhklcitaW1w476DTWHhuEM=",
"requires": {
"bson": "1.0.4",
"bson": "0.5.7",
"require_optional": "1.0.1"
}
},
"readable-stream": {
"version": "1.0.31",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz",
"integrity": "sha1-jyUC4LyeOw2huUUgqrtOJgPsr64=",
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "0.0.1",
"string_decoder": "0.10.31"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
@ -7325,6 +7427,16 @@
"pify": "2.3.0"
}
},
"path.js": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path.js/-/path.js-1.0.7.tgz",
"integrity": "sha1-fRNrYH3hm/2YugaIdJJih+ZTSTk=",
"requires": {
"escape-string-regexp": "1.0.5",
"inherits-ex": "1.2.3",
"util-ex": "0.3.15"
}
},
"paypal-rest-sdk": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/paypal-rest-sdk/-/paypal-rest-sdk-1.8.1.tgz",
@ -8553,6 +8665,35 @@
"safe-buffer": "5.1.1"
}
},
"superagent": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz",
"integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==",
"dev": true,
"requires": {
"component-emitter": "1.2.1",
"cookiejar": "2.1.2",
"debug": "3.1.0",
"extend": "3.0.1",
"form-data": "2.3.2",
"formidable": "1.2.1",
"methods": "1.1.2",
"mime": "1.4.1",
"qs": "6.5.1",
"readable-stream": "2.3.3"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"supertap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz",
@ -8589,6 +8730,16 @@
}
}
},
"supertest": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-3.1.0.tgz",
"integrity": "sha512-O44AMnmJqx294uJQjfUmEyYOg7d9mylNFsMw/Wkz4evKd1njyPrtCN+U6ZIC7sKtfEVQhfTqFFijlXx8KP/Czw==",
"dev": true,
"requires": {
"methods": "1.1.2",
"superagent": "3.8.2"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
@ -9195,6 +9346,15 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"util-ex": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/util-ex/-/util-ex-0.3.15.tgz",
"integrity": "sha1-+SYc2hPEMn0HQMvme+Eife2LAFg=",
"requires": {
"inherits-ex": "1.2.3",
"xtend": "4.0.1"
}
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "express-cart",
"version": "1.1.6",
"version": "1.1.7",
"description": "A fully functioning Node.js shopping cart with Stripe, PayPal and Authorize.net payments.",
"private": false,
"scripts": {
@ -27,6 +27,8 @@
"html-entities": "^1.2.0",
"lodash": "^4.13.1",
"lunr": "^2.1.5",
"mime-db": "^1.33.0",
"mime-type": "^3.0.5",
"moment": "^2.15.2",
"mongodb": "2.2.3",
"morgan": "^1.9.0",

View File

@ -7,6 +7,7 @@ const fs = require('fs');
const path = require('path');
const multer = require('multer');
const glob = require('glob');
const mime = require('mime-type/with-db');
const router = express.Router();
// Admin section
@ -419,33 +420,54 @@ router.post('/admin/file/upload', common.restrict, common.checkAccess, upload.si
const db = req.app.db;
if(req.file){
// check for upload select
let uploadDir = path.join('public/uploads', req.body.directory);
// Check directory and create (if needed)
common.checkDirectorySync(uploadDir);
let file = req.file;
let source = fs.createReadStream(file.path);
let dest = fs.createWriteStream(path.join(uploadDir, file.originalname.replace(/ /g, '_')));
// save the new file
source.pipe(dest);
source.on('end', () => { });
// Get the mime type of the file
const mimeType = mime.lookup(file.originalname);
// Check for allowed mime type and file size
if(!common.allowedMimeType.includes(mimeType) || file.size > common.fileSizeLimit){
// Remove temp file
fs.unlinkSync(file.path);
// delete the temp file.
fs.unlink(file.path, (err) => {
if(err){
console.info(err.stack);
}
});
// Redirect to error
req.session.message = 'File type not allowed or too large. Please try again.';
req.session.messageType = 'danger';
res.redirect('/admin/product/edit/' + req.body.productId);
return;
}
// get the product form the DB
db.products.findOne({_id: common.getId(req.body.productId)}, (err, product) => {
if(err){
console.info(err.stack);
// delete the temp file.
fs.unlinkSync(file.path);
// Redirect to error
req.session.message = 'File upload error. Please try again.';
req.session.messageType = 'danger';
res.redirect('/admin/product/edit/' + req.body.productId);
return;
}
let imagePath = path.join('/uploads', req.body.directory, file.originalname.replace(/ /g, '_'));
const productPath = product.productPermalink;
let uploadDir = path.join('public/uploads', productPath);
// Check directory and create (if needed)
common.checkDirectorySync(uploadDir);
let source = fs.createReadStream(file.path);
let dest = fs.createWriteStream(path.join(uploadDir, file.originalname.replace(/ /g, '_')));
// save the new file
source.pipe(dest);
source.on('end', () => { });
// delete the temp file.
fs.unlinkSync(file.path);
let imagePath = path.join('/uploads', productPath, file.originalname.replace(/ /g, '_'));
// if there isn't a product featured image, set this one
if(!product.productImage){
@ -464,6 +486,10 @@ router.post('/admin/file/upload', common.restrict, common.checkAccess, upload.si
}
});
}else{
// delete the temp file.
fs.unlinkSync(file.path);
// Redirect to error
req.session.message = 'File upload error. Please select a file.';
req.session.messageType = 'danger';
res.redirect('/admin/product/edit/' + req.body.productId);

View File

@ -149,7 +149,6 @@
Select file<input type="file" name="upload_file" id="upload_file">
</span>
<input type="hidden" id="productId" name="productId" value="{{result._id}}"/>
<input type="hidden" id="directory" name="directory" value="{{result.productPermalink}}"/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>