diff --git a/middlewares/reqValidators/index.js b/middlewares/reqValidators/index.js new file mode 100644 index 000000000..1ea611031 --- /dev/null +++ b/middlewares/reqValidators/index.js @@ -0,0 +1,11 @@ +;(function () { + 'use strict' + + var reqValidator = { + videos: require('./videos'), + pods: require('./pods'), + remote: require('./remote') + } + + module.exports = reqValidator +})() diff --git a/middlewares/reqValidators/pods.js b/middlewares/reqValidators/pods.js new file mode 100644 index 000000000..31eaf8449 --- /dev/null +++ b/middlewares/reqValidators/pods.js @@ -0,0 +1,19 @@ +;(function () { + 'use strict' + + var checkErrors = require('./utils').checkErrors + var logger = require('../../src/logger') + + var pods = {} + + pods.podsAdd = function (req, res, next) { + req.checkBody('data.url', 'Should have an url').notEmpty().isURL({ require_protocol: true }) + req.checkBody('data.publicKey', 'Should have a public key').notEmpty() + + logger.debug('Checking podsAdd parameters', { parameters: req.body }) + + checkErrors(req, res, next) + } + + module.exports = pods +})() diff --git a/middlewares/reqValidators/remote.js b/middlewares/reqValidators/remote.js new file mode 100644 index 000000000..e851b49a4 --- /dev/null +++ b/middlewares/reqValidators/remote.js @@ -0,0 +1,40 @@ +;(function () { + 'use strict' + + var checkErrors = require('./utils').checkErrors + var logger = require('../../src/logger') + + var remote = {} + + remote.secureRequest = function (req, res, next) { + req.checkBody('signature.url', 'Should have a signature url').isURL() + req.checkBody('signature.signature', 'Should have a signature').notEmpty() + req.checkBody('key', 'Should have a key').notEmpty() + req.checkBody('data', 'Should have data').notEmpty() + + logger.debug('Checking secureRequest parameters', { parameters: req.body }) + + checkErrors(req, res, next) + } + + remote.remoteVideosAdd = function (req, res, next) { + req.checkBody('data.name', 'Should have a name').isLength(1, 50) + req.checkBody('data.description', 'Should have a description').isLength(1, 250) + req.checkBody('data.magnetUri', 'Should have a magnetUri').notEmpty() + req.checkBody('data.podUrl', 'Should have a podUrl').isURL() + + logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body }) + + checkErrors(req, res, next) + } + + remote.remoteVideosRemove = function (req, res, next) { + req.checkBody('data.magnetUri', 'Should have a magnetUri').notEmpty() + + logger.debug('Checking remoteVideosRemove parameters', { parameters: req.body }) + + checkErrors(req, res, next) + } + + module.exports = remote +})() diff --git a/middlewares/reqValidators/utils.js b/middlewares/reqValidators/utils.js new file mode 100644 index 000000000..91ead27a5 --- /dev/null +++ b/middlewares/reqValidators/utils.js @@ -0,0 +1,22 @@ +;(function () { + 'use strict' + + var util = require('util') + var logger = require('../../src/logger') + + var utils = {} + + utils.checkErrors = function (req, res, next, status_code) { + if (status_code === undefined) status_code = 400 + var errors = req.validationErrors() + + if (errors) { + logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors }) + return res.status(status_code).send('There have been validation errors: ' + util.inspect(errors)) + } + + return next() + } + + module.exports = utils +})() diff --git a/middlewares/reqValidators/videos.js b/middlewares/reqValidators/videos.js new file mode 100644 index 000000000..3763a657c --- /dev/null +++ b/middlewares/reqValidators/videos.js @@ -0,0 +1,67 @@ +;(function () { + 'use strict' + + var checkErrors = require('./utils').checkErrors + var VideosDB = require('../../src/database').VideosDB + var logger = require('../../src/logger') + + var videos = {} + + function findVideoById (id, callback) { + VideosDB.findById(id, { _id: 1, namePath: 1 }).limit(1).exec(function (err, video) { + if (err) throw err + + callback(video) + }) + } + + videos.videosSearch = function (req, res, next) { + req.checkParams('name', 'Should have a name').notEmpty() + + logger.debug('Checking videosSearch parameters', { parameters: req.params }) + + checkErrors(req, res, next) + } + + videos.videosAdd = function (req, res, next) { + req.checkFiles('input_video.originalname', 'Should have an input video').notEmpty() + req.checkFiles('input_video.mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i) + req.checkBody('name', 'Should have a name').isLength(1, 50) + req.checkBody('description', 'Should have a description').isLength(1, 250) + + logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) + + checkErrors(req, res, next) + } + + videos.videosGet = function (req, res, next) { + req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() + + logger.debug('Checking videosGet parameters', { parameters: req.params }) + + checkErrors(req, res, function () { + findVideoById(req.params.id, function (video) { + if (!video) return res.status(404).send('Video not found') + + next() + }) + }) + } + + videos.videosRemove = function (req, res, next) { + req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() + + logger.debug('Checking videosRemove parameters', { parameters: req.params }) + + checkErrors(req, res, function () { + findVideoById(req.params.id, function (video) { + if (!video) return res.status(404).send('Video not found') + else if (video.namePath === null) return res.status(403).send('Cannot remove video of another pod') + + next() + }) + }) + } + + module.exports = videos +})() diff --git a/routes/api/v1/pods.js b/routes/api/v1/pods.js index 961388fcb..8ae676834 100644 --- a/routes/api/v1/pods.js +++ b/routes/api/v1/pods.js @@ -4,6 +4,7 @@ var express = require('express') var router = express.Router() var middleware = require('../../../middlewares') + var reqValidator = require('../../../middlewares/reqValidators').pods var pods = require('../../../src/pods') function listPods (req, res, next) { @@ -32,7 +33,7 @@ router.get('/', middleware.cache(false), listPods) router.get('/makefriends', middleware.cache(false), makeFriends) - router.post('/', middleware.cache(false), addPods) + router.post('/', reqValidator.podsAdd, middleware.cache(false), addPods) module.exports = router })() diff --git a/routes/api/v1/remoteVideos.js b/routes/api/v1/remoteVideos.js index 88b8e879b..23bdcbe24 100644 --- a/routes/api/v1/remoteVideos.js +++ b/routes/api/v1/remoteVideos.js @@ -4,6 +4,7 @@ var express = require('express') var router = express.Router() var middleware = require('../../../middlewares') + var requestValidator = require('../../../middlewares/reqValidators').remote var videos = require('../../../src/videos') function addRemoteVideos (req, res, next) { @@ -22,8 +23,8 @@ }) } - router.post('/add', middleware.cache(false), middleware.decryptBody, addRemoteVideos) - router.post('/remove', middleware.cache(false), middleware.decryptBody, removeRemoteVideo) + router.post('/add', requestValidator.secureRequest, middleware.decryptBody, requestValidator.remoteVideosAdd, middleware.cache(false), addRemoteVideos) + router.post('/remove', requestValidator.secureRequest, middleware.decryptBody, requestValidator.remoteVideosRemove, middleware.cache(false), removeRemoteVideo) module.exports = router })() diff --git a/routes/api/v1/videos.js b/routes/api/v1/videos.js index 246620ac6..06b248244 100644 --- a/routes/api/v1/videos.js +++ b/routes/api/v1/videos.js @@ -4,6 +4,7 @@ var express = require('express') var router = express.Router() var middleware = require('../../../middlewares') + var reqValidator = require('../../../middlewares/reqValidators').videos var videos = require('../../../src/videos') function listVideos (req, res, next) { @@ -52,10 +53,10 @@ } router.get('/', middleware.cache(false), listVideos) - router.post('/', middleware.cache(false), addVideos) - router.get('/search/:name', middleware.cache(false), searchVideos) - router.get('/:id', middleware.cache(false), getVideos) - router.delete('/:id', middleware.cache(false), removeVideo) + router.post('/', reqValidator.videosAdd, middleware.cache(false), addVideos) + router.get('/search/:name', reqValidator.videosSearch, middleware.cache(false), searchVideos) + router.get('/:id', reqValidator.videosGet, middleware.cache(false), getVideos) + router.delete('/:id', reqValidator.videosRemove, middleware.cache(false), removeVideo) module.exports = router })() diff --git a/server.js b/server.js index d3718f8de..317d8c3cd 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,7 @@ // ----------- Node modules ----------- var express = require('express') + var expressValidator = require('express-validator') var path = require('path') var morgan = require('morgan') var bodyParser = require('body-parser') @@ -47,6 +48,7 @@ app.use(bodyParser.json()) app.use(multer({ dest: uploads })) app.use(bodyParser.urlencoded({ extended: false })) + app.use(expressValidator()) // ----------- Views, routes and static files ----------- diff --git a/test/api/checkParams.js b/test/api/checkParams.js new file mode 100644 index 000000000..a06e32bbc --- /dev/null +++ b/test/api/checkParams.js @@ -0,0 +1,295 @@ +;(function () { + 'use strict' + + var request = require('supertest') + var chai = require('chai') + var expect = chai.expect + + var utils = require('../utils') + + describe('Test parameters validator', function () { + var app = null + var url = '' + + before(function (done) { + this.timeout(20000) + + utils.flushTests(function () { + utils.runServer(1, function (app1, url1) { + app = app1 + url = url1 + done() + }) + }) + }) + + function makePostRequest (path, fields, attach, done, fail) { + var status_code = 400 + if (fail !== undefined && fail === false) status_code = 200 + + var req = request(url) + .post(path) + .set('Accept', 'application/json') + + Object.keys(fields).forEach(function (field) { + var value = fields[field] + req.field(field, value) + }) + + req.expect(status_code, done) + } + + function makePostBodyRequest (path, fields, done, fail) { + var status_code = 400 + if (fail !== undefined && fail === false) status_code = 200 + + request(url) + .post(path) + .set('Accept', 'application/json') + .send(fields) + .expect(status_code, done) + } + + describe('Of the pods API', function () { + var path = '/api/v1/pods/' + + describe('When adding a pod', function () { + it('Should fail with nothing', function (done) { + var data = {} + makePostBodyRequest(path, data, done) + }) + + it('Should fail without public key', function (done) { + var data = { + data: { + url: 'http://coucou.com' + } + } + makePostBodyRequest(path, data, done) + }) + + it('Should fail without an url', function (done) { + var data = { + data: { + publicKey: 'mysuperpublickey' + } + } + makePostBodyRequest(path, data, done) + }) + + it('Should fail with an incorrect url', function (done) { + var data = { + data: { + url: 'coucou.com', + publicKey: 'mysuperpublickey' + } + } + makePostBodyRequest(path, data, function () { + data.data.url = 'http://coucou' + makePostBodyRequest(path, data, function () { + data.data.url = 'coucou' + makePostBodyRequest(path, data, done) + }) + }) + }) + + it('Should succeed with the correct parameters', function (done) { + var data = { + data: { + url: 'http://coucou.com', + publicKey: 'mysuperpublickey' + } + } + makePostBodyRequest(path, data, done, false) + }) + }) + }) + + describe('Of the videos API', function () { + var path = '/api/v1/videos/' + + describe('When searching a video', function () { + it('Should fail with nothing', function (done) { + request(url) + .get(path + '/search/') + .set('Accept', 'application/json') + .expect(400, done) + }) + }) + + describe('When adding a video', function () { + it('Should fail with nothing', function (done) { + var data = {} + var attach = {} + makePostRequest(path, data, attach, done) + }) + + it('Should fail without name', function (done) { + var data = { + description: 'my super description' + } + var attach = { + 'input_video': __dirname + '/../fixtures/video_short.webm' + } + makePostRequest(path, data, attach, done) + }) + + it('Should fail with a long name', function (done) { + var data = { + name: 'My very very very very very very very very very very very very very very very very long name', + description: 'my super description' + } + var attach = { + 'input_video': __dirname + '/../fixtures/video_short.webm' + } + makePostRequest(path, data, attach, done) + }) + + it('Should fail without description', function (done) { + var data = { + name: 'my super name' + } + var attach = { + 'input_video': __dirname + '/../fixtures/video_short.webm' + } + makePostRequest(path, data, attach, done) + }) + + it('Should fail with a long description', function (done) { + var data = { + name: 'my super name', + description: 'my super description which is very very very very very very very very very very very very very very' + + 'very very very very very very very very very very very very very very very very very very very very very' + + 'very very very very very very very very very very very very very very very long' + } + var attach = { + 'input_video': __dirname + '/../fixtures/video_short.webm' + } + makePostRequest(path, data, attach, done) + }) + + it('Should fail without an input file', function (done) { + var data = { + name: 'my super name', + description: 'my super description' + } + var attach = {} + makePostRequest(path, data, attach, done) + }) + + it('Should fail without an incorrect input file', function (done) { + var data = { + name: 'my super name', + description: 'my super description' + } + var attach = { + 'input_video': __dirname + '/../fixtures/video_short_fake.webm' + } + makePostRequest(path, data, attach, done) + }) + + it('Should succeed with the correct parameters', function (done) { + var data = { + name: 'my super name', + description: 'my super description' + } + var attach = { + 'input_video': __dirname + '/../fixtures/video_short.webm' + } + makePostRequest(path, data, attach, function () { + attach.input_video = __dirname + '/../fixtures/video_short.mp4' + makePostRequest(path, data, attach, function () { + attach.input_video = __dirname + '/../fixtures/video_short.ogv' + makePostRequest(path, data, attach, done, true) + }, true) + }, true) + }) + }) + + describe('When getting a video', function () { + it('Should return the list of the videos with nothing', function (done) { + request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(function (err, res) { + if (err) throw err + + expect(res.body).to.be.an('array') + expect(res.body.length).to.equal(0) + + done() + }) + }) + + it('Should fail without a mongodb id', function (done) { + request(url) + .get(path + 'coucou') + .set('Accept', 'application/json') + .expect(400, done) + }) + + it('Should return 404 with an incorrect video', function (done) { + request(url) + .get(path + '123456789012345678901234') + .set('Accept', 'application/json') + .expect(404, done) + }) + + it('Should succeed with the correct parameters') + }) + + describe('When removing a video', function () { + it('Should have 404 with nothing', function (done) { + request(url) + .delete(path) + .expect(404, done) + }) + + it('Should fail without a mongodb id', function (done) { + request(url) + .delete(path + 'hello') + .expect(400, done) + }) + + it('Should fail with a video which does not exist', function (done) { + request(url) + .delete(path + '123456789012345678901234') + .expect(404, done) + }) + + it('Should fail with a video of another pod') + + it('Should succeed with the correct parameters') + }) + }) + + describe('Of the remote videos API', function () { + describe('When making a secure request', function () { + it('Should check a secure request') + }) + + describe('When adding a video', function () { + it('Should check when adding a video') + }) + + describe('When removing a video', function () { + it('Should check when removing a video') + }) + }) + + after(function (done) { + process.kill(-app.pid) + + // Keep the logs if the test failed + if (this.ok) { + utils.flushTests(function () { + done() + }) + } else { + done() + } + }) + }) +})() diff --git a/test/fixtures/video_short.mp4 b/test/fixtures/video_short.mp4 new file mode 100644 index 000000000..35678362b Binary files /dev/null and b/test/fixtures/video_short.mp4 differ diff --git a/test/fixtures/video_short.ogv b/test/fixtures/video_short.ogv new file mode 100644 index 000000000..9e253da82 Binary files /dev/null and b/test/fixtures/video_short.ogv differ diff --git a/test/fixtures/video_short_fake.webm b/test/fixtures/video_short_fake.webm new file mode 100644 index 000000000..d85290ae5 --- /dev/null +++ b/test/fixtures/video_short_fake.webm @@ -0,0 +1 @@ +this is a fake video mouahahah