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'} {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 // common functions
exports.restrict = (req, res, next) => { exports.restrict = (req, res, next) => {
exports.checkLogin(req, res, next); exports.checkLogin(req, res, next);

188
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "express-cart", "name": "express-cart",
"version": "1.1.5", "version": "1.1.6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -499,6 +499,12 @@
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
"dev": true "dev": true
}, },
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"atob": { "atob": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", "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", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" "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": { "commander": {
"version": "2.12.2", "version": "2.12.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", "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", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" "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": { "copy-descriptor": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "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": { "depd": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
@ -2672,8 +2699,7 @@
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
"dev": true
}, },
"escope": { "escope": {
"version": "3.6.0", "version": "3.6.0",
@ -3468,6 +3494,23 @@
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" "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": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "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", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "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": { "ini": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
@ -6600,9 +6651,20 @@
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
}, },
"mime-db": { "mime-db": {
"version": "1.30.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
"integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" "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": { "mime-types": {
"version": "2.1.17", "version": "2.1.17",
@ -6610,6 +6672,13 @@
"integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
"requires": { "requires": {
"mime-db": "1.30.0" "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": { "mimic-fn": {
@ -6673,21 +6742,54 @@
"integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg=="
}, },
"mongodb": { "mongodb": {
"version": "3.0.0", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.3.tgz",
"integrity": "sha1-GN2P+S14k97MQI2virDrQIR1Ip8=", "integrity": "sha1-SSztvnOHVSyc8uT1vEDreOBiA6s=",
"requires": { "requires": {
"mongodb-core": "3.0.0" "es6-promise": "3.0.2",
"mongodb-core": "2.0.6",
"readable-stream": "1.0.31"
}, },
"dependencies": { "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": { "mongodb-core": {
"version": "3.0.0", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.0.6.tgz",
"integrity": "sha1-tNBsvoj6iPaola+lJCpBvVMJvHQ=", "integrity": "sha1-fxvnnYhklcitaW1w476DTWHhuEM=",
"requires": { "requires": {
"bson": "1.0.4", "bson": "0.5.7",
"require_optional": "1.0.1" "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" "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": { "paypal-rest-sdk": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/paypal-rest-sdk/-/paypal-rest-sdk-1.8.1.tgz", "resolved": "https://registry.npmjs.org/paypal-rest-sdk/-/paypal-rest-sdk-1.8.1.tgz",
@ -8553,6 +8665,35 @@
"safe-buffer": "5.1.1" "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": { "supertap": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz", "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": { "supports-color": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "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": { "utils-merge": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View File

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

View File

@ -7,6 +7,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const multer = require('multer'); const multer = require('multer');
const glob = require('glob'); const glob = require('glob');
const mime = require('mime-type/with-db');
const router = express.Router(); const router = express.Router();
// Admin section // Admin section
@ -419,13 +420,43 @@ router.post('/admin/file/upload', common.restrict, common.checkAccess, upload.si
const db = req.app.db; const db = req.app.db;
if(req.file){ if(req.file){
// check for upload select let file = req.file;
let uploadDir = path.join('public/uploads', req.body.directory);
// 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);
// 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;
}
const productPath = product.productPermalink;
let uploadDir = path.join('public/uploads', productPath);
// Check directory and create (if needed) // Check directory and create (if needed)
common.checkDirectorySync(uploadDir); common.checkDirectorySync(uploadDir);
let file = req.file;
let source = fs.createReadStream(file.path); let source = fs.createReadStream(file.path);
let dest = fs.createWriteStream(path.join(uploadDir, file.originalname.replace(/ /g, '_'))); let dest = fs.createWriteStream(path.join(uploadDir, file.originalname.replace(/ /g, '_')));
@ -434,18 +465,9 @@ router.post('/admin/file/upload', common.restrict, common.checkAccess, upload.si
source.on('end', () => { }); source.on('end', () => { });
// delete the temp file. // delete the temp file.
fs.unlink(file.path, (err) => { fs.unlinkSync(file.path);
if(err){
console.info(err.stack);
}
});
// get the product form the DB let imagePath = path.join('/uploads', productPath, file.originalname.replace(/ /g, '_'));
db.products.findOne({_id: common.getId(req.body.productId)}, (err, product) => {
if(err){
console.info(err.stack);
}
let imagePath = path.join('/uploads', req.body.directory, file.originalname.replace(/ /g, '_'));
// if there isn't a product featured image, set this one // if there isn't a product featured image, set this one
if(!product.productImage){ if(!product.productImage){
@ -464,6 +486,10 @@ router.post('/admin/file/upload', common.restrict, common.checkAccess, upload.si
} }
}); });
}else{ }else{
// delete the temp file.
fs.unlinkSync(file.path);
// Redirect to error
req.session.message = 'File upload error. Please select a file.'; req.session.message = 'File upload error. Please select a file.';
req.session.messageType = 'danger'; req.session.messageType = 'danger';
res.redirect('/admin/product/edit/' + req.body.productId); 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"> Select file<input type="file" name="upload_file" id="upload_file">
</span> </span>
<input type="hidden" id="productId" name="productId" value="{{result._id}}"/> <input type="hidden" id="productId" name="productId" value="{{result._id}}"/>
<input type="hidden" id="directory" name="directory" value="{{result.productPermalink}}"/>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>