diff --git a/packages/xpub-aws/package.json b/packages/xpub-aws/package.json index 936ae8b913b29d9eaa65a7a3347c29f0a00ce71b..b52ac0f4944e2df63521b96a78fb41c5ade312b6 100644 --- a/packages/xpub-aws/package.json +++ b/packages/xpub-aws/package.json @@ -15,9 +15,12 @@ }, "dependencies": { "aws-sdk": "^2.185.0", + "aws-sdk-mock": "^1.7.0", "body-parser": "^1.17.2", + "mock-aws-s3": "^2.6.0", "multer": "^1.3.0", "multer-s3": "^2.7.0", + "node-mocks-http": "^1.6.6", "nodemailer": "^4.4.2" }, "peerDependencies": { diff --git a/packages/xpub-aws/src/AWSBackend.js b/packages/xpub-aws/src/AWSBackend.js index 904452bfa2f193e7ca30a76d53836ef25eb358ef..7b709513d936327a4f4f9cc236d79100a90dceab 100644 --- a/packages/xpub-aws/src/AWSBackend.js +++ b/packages/xpub-aws/src/AWSBackend.js @@ -1,7 +1,5 @@ const bodyParser = require('body-parser') const AWS = require('aws-sdk') -const multer = require('multer') -const multerS3 = require('multer-s3') const uuid = require('uuid') const nodemailer = require('nodemailer') @@ -16,48 +14,14 @@ const AWSBackend = app => { region: process.env.AWS_REGION, }) const s3 = new AWS.S3() - const upload = multer({ - storage: multerS3({ - s3, - bucket: process.env.AWS_BUCKET, - contentType: (req, file, cb) => { - cb(null, file.mimetype) - }, - key: (req, file, cb) => { - const fileKey = `${req.body.fragmentId}/${uuid.v4()}` - cb(null, fileKey) - }, - }), - fileFilter: (req, file, cb) => { - if ( - req.body.fileType === 'manuscripts' || - req.body.fileType === 'coverLetter' - ) { - if ( - file.mimetype === 'application/pdf' || - file.mimetype === 'application/msword' || - file.mimetype === - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - ) { - return cb(null, true) - } - req.fileValidationError = 'Only Word documents and PDFs are allowed' - return cb(null, false) - } + const upload = require('./middeware/upload').setupMulter(s3) - return cb(null, true) - }, - }) - app.post('/api/aws', authBearer, upload.single('file'), async (req, res) => { - if (req.fileValidationError !== undefined) { - return res.status(400).json({ error: req.fileValidationError }) - } - res.status(200).json({ - id: req.file.key, - name: req.file.originalname, - size: req.file.size, - }) - }) + app.post( + '/api/aws', + authBearer, + upload.single('file'), + require('./routeHandlers/postFile'), + ) app.get('/api/aws/:fragmentId/:fileId', authBearer, async (req, res) => { const params = { Bucket: process.env.AWS_BUCKET, diff --git a/packages/xpub-aws/src/AWSBackend.test.js b/packages/xpub-aws/src/AWSBackend.test.js new file mode 100644 index 0000000000000000000000000000000000000000..250e90381ab533fbb563a82784f3b10f063109e6 --- /dev/null +++ b/packages/xpub-aws/src/AWSBackend.test.js @@ -0,0 +1,89 @@ +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +process.env.SUPPRESS_NO_CONFIG_WARNING = true + +const httpMocks = require('node-mocks-http') + +describe('ValidateFile for multer fileFilter', () => { + it('should return TRUE when fileType is supplementary', () => { + const validateFile = require('./middeware/upload').validateFile( + ...buildValidateFileParams('supplementary', 'image/png'), + ) + expect(validateFile).toBe(true) + }) + it('should return TRUE when fileType is manuscripts or coverLetter and the file is either Word Doc or PDF', () => { + const randFileType = getRandValueFromArray(['manuscripts', 'coverLetter']) + const randMimeType = getRandValueFromArray([ + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ]) + + const validateFile = require('./middeware/upload').validateFile( + ...buildValidateFileParams(randFileType, randMimeType), + ) + expect(validateFile).toBe(true) + }) + it('should return FALSE when fileType is manuscripts or coverLetter and the file is neither Word Doc or PDF', () => { + const randFileType = getRandValueFromArray(['manuscripts', 'coverLetter']) + const randMimeType = getRandValueFromArray([ + 'text/plain', + 'text/html', + 'image/jpeg', + 'image/png', + ]) + + const validateFile = require('./middeware/upload').validateFile( + ...buildValidateFileParams(randFileType, randMimeType), + ) + expect(validateFile).toBe(false) + }) +}) + +describe('Upload file route handler', () => { + it('should return success when the file passed validation', async () => { + const file = { + key: '123abc', + originalname: 'file.txt', + size: 128, + } + const req = httpMocks.createRequest({ + file, + }) + const res = httpMocks.createResponse() + await require('./routeHandlers/postFile')(req, res) + expect(res.statusCode).toBe(200) + const data = JSON.parse(res._getData()) + expect(data.id).toEqual(file.key) + expect(data.name).toEqual(file.originalname) + expect(data.size).toEqual(file.size) + }) + it('should return an error when the file failed validation', async () => { + const req = httpMocks.createRequest({ + fileValidationError: 'Only Word documents and PDFs are allowed', + }) + const res = httpMocks.createResponse() + await require('./routeHandlers/postFile')(req, res) + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual(req.fileValidationError) + }) +}) + +const getRandValueFromArray = arr => arr[Math.floor(Math.random() * arr.length)] + +const buildValidateFileParams = (fileType, mimetype) => { + const req = { + body: { + fileType, + }, + } + const file = { + mimetype, + } + const cb = (p1, p2) => { + if (p2 === true) return true + return false + } + + return [req, file, cb] +} diff --git a/packages/xpub-aws/src/middeware/upload.js b/packages/xpub-aws/src/middeware/upload.js new file mode 100644 index 0000000000000000000000000000000000000000..e15c07ff36589eecaf6ded895da7e2a417fe2fa2 --- /dev/null +++ b/packages/xpub-aws/src/middeware/upload.js @@ -0,0 +1,46 @@ +const multer = require('multer') +const multerS3 = require('multer-s3') +const uuid = require('uuid') + +const setupMulter = s3 => { + const upload = multer({ + storage: multerS3({ + s3, + bucket: process.env.AWS_BUCKET, + contentType: (req, file, cb) => { + cb(null, file.mimetype) + }, + key: (req, file, cb) => { + const fileKey = `${req.body.fragmentId}/${uuid.v4()}` + cb(null, fileKey) + }, + }), + fileFilter: (req, file, cb) => validateFile(req, file, cb), + }) + + return upload +} + +const validateFile = (req, file, cb) => { + if ( + req.body.fileType === 'manuscripts' || + req.body.fileType === 'coverLetter' + ) { + if ( + file.mimetype === 'application/pdf' || + file.mimetype === 'application/msword' || + file.mimetype === + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ) { + return cb(null, true) + } + req.fileValidationError = 'Only Word documents and PDFs are allowed' + return cb(null, false) + } + return cb(null, true) +} + +module.exports = { + setupMulter, + validateFile, +} diff --git a/packages/xpub-aws/src/routeHandlers/postFile.js b/packages/xpub-aws/src/routeHandlers/postFile.js new file mode 100644 index 0000000000000000000000000000000000000000..326bd61aa295bc04affaad4ce2c4040c0df55f2e --- /dev/null +++ b/packages/xpub-aws/src/routeHandlers/postFile.js @@ -0,0 +1,10 @@ +module.exports = async (req, res) => { + if (req.fileValidationError !== undefined) { + return res.status(400).json({ error: req.fileValidationError }) + } + res.status(200).json({ + id: req.file.key, + name: req.file.originalname, + size: req.file.size, + }) +} diff --git a/yarn.lock b/yarn.lock index 19e5c7265fddb20eb02a7d6bf7f1a0c064eba5cd..0ca5f7e847f990fedbb01f46aa37101adb01e6d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -172,7 +172,7 @@ abstract-leveldown@^3.0.0, abstract-leveldown@~3.0.0: dependencies: xtend "~4.0.0" -accepts@~1.3.4: +accepts@^1.3.3, accepts@~1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" dependencies: @@ -546,7 +546,15 @@ autoprefixer@^7.1.2: postcss "^6.0.14" postcss-value-parser "^3.2.3" -aws-sdk@^2.185.0: +aws-sdk-mock@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-1.7.0.tgz#7698b3ba82f493f71ff060ae2123cd0806ad8676" + dependencies: + aws-sdk "^2.3.0" + sinon "^1.17.3" + traverse "^0.6.6" + +aws-sdk@^2.185.0, aws-sdk@^2.3.0: version "2.188.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.188.0.tgz#9062abc7dba6393459fa2f3423cf5d294f004611" dependencies: @@ -2732,6 +2740,10 @@ depd@1.1.1, depd@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" +depd@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -3912,6 +3924,12 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" +formatio@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" + dependencies: + samsam "~1.1" + formidable@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" @@ -3924,7 +3942,7 @@ frameguard@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9" -fresh@0.5.2: +fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -3935,6 +3953,15 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-extra@0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.6.4.tgz#f46f0c75b7841f8d200b3348cd4d691d5a099d15" + dependencies: + jsonfile "~1.0.1" + mkdirp "0.3.x" + ncp "~0.4.2" + rimraf "~2.2.0" + fs-extra@^4.0.1, fs-extra@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -5703,6 +5730,10 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-1.0.1.tgz#ea5efe40b83690b98667614a7392fc60e842c0dd" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -6287,6 +6318,10 @@ loglevel@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.0.tgz#ae0caa561111498c5ba13723d6fb631d24003934" +lolex@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" + longest-streak@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" @@ -6471,7 +6506,7 @@ meow@^4.0.0: redent "^2.0.0" trim-newlines "^2.0.0" -merge-descriptors@1.0.1: +merge-descriptors@1.0.1, merge-descriptors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -6485,7 +6520,7 @@ merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" -methods@^1.1.1, methods@~1.1.2: +methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -6532,7 +6567,7 @@ mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" -mime@^1.4.1, mime@^1.5.0: +mime@^1.3.4, mime@^1.4.1, mime@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -6615,12 +6650,23 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp@0.3.x: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + mkdirp@0.5.x, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" +mock-aws-s3@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mock-aws-s3/-/mock-aws-s3-2.6.0.tgz#759868f2718b0c9b2ba5d5ed70ed9a244d9ccc7c" + dependencies: + fs-extra "0.6.4" + underscore "1.8.3" + modify-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.0.tgz#e2b6cdeb9ce19f99317a53722f3dbf5df5eaaab2" @@ -6723,7 +6769,7 @@ nconf@0.6.9: ini "1.x.x" optimist "0.6.0" -ncp@0.4.x: +ncp@0.4.x, ncp@~0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/ncp/-/ncp-0.4.2.tgz#abcc6cbd3ec2ed2a729ff6e7c1fa8f01784a8574" @@ -6743,6 +6789,10 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +net@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/net/-/net-1.0.2.tgz#d1757ec9a7fb2371d83cf4755ce3e27e10829388" + no-case@^2.2.0: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" @@ -6826,6 +6876,21 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" +node-mocks-http@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.6.6.tgz#0fdeef866cc122a80051bbd89a876d3c4cd21e13" + dependencies: + accepts "^1.3.3" + depd "^1.1.0" + fresh "^0.5.2" + merge-descriptors "^1.0.1" + methods "^1.1.2" + mime "^1.3.4" + net "^1.0.2" + parseurl "^1.3.1" + range-parser "^1.2.0" + type-is "^1.6.14" + node-notifier@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" @@ -7336,7 +7401,7 @@ parse5@^3.0.1, parse5@^3.0.2: dependencies: "@types/node" "*" -parseurl@~1.3.2: +parseurl@^1.3.1, parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -8619,7 +8684,7 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@^1.0.3, range-parser@~1.2.0: +range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" @@ -9586,7 +9651,7 @@ rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6 dependencies: glob "^7.0.5" -rimraf@~2.2.6: +rimraf@~2.2.0, rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" @@ -9644,6 +9709,14 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +samsam@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" + +samsam@~1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" + sane@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" @@ -9834,6 +9907,15 @@ simple-get@^1.4.2: unzip-response "^1.0.0" xtend "^4.0.0" +sinon@^1.17.3: + version "1.17.7" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" + dependencies: + formatio "1.1.1" + lolex "1.3.2" + samsam "1.1.2" + util ">=0.10.3 <1" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -10665,6 +10747,10 @@ tr46@^1.0.0: dependencies: punycode "^2.1.0" +traverse@^0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" + trim-left-x@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/trim-left-x/-/trim-left-x-3.0.0.tgz#356cf055896726b9754425e841398842e90b4cdf" @@ -10750,7 +10836,7 @@ type-detect@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" -type-is@^1.6.4, type-is@~1.6.15: +type-is@^1.6.14, type-is@^1.6.4, type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" dependencies: @@ -10843,6 +10929,10 @@ uncontrollable@^4.1.0: dependencies: invariant "^2.1.0" +underscore@1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" + underscore@~1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" @@ -11001,7 +11091,7 @@ util.promisify@^1.0.0: define-properties "^1.1.2" object.getownpropertydescriptors "^2.0.3" -util@0.10.3, util@^0.10.3: +util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: