From 9bd2662976a75d3b03364cdbe6419e57c80f99a6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 Aug 2016 22:32:36 +0200 Subject: [PATCH] Implement user API (create, update, remove, list) --- server/controllers/api/v1/pods.js | 14 +- server/controllers/api/v1/users.js | 132 +++++++++- server/helpers/custom-validators/users.js | 15 +- server/helpers/custom-validators/videos.js | 2 +- server/initializers/constants.js | 8 +- server/initializers/installer.js | 9 +- server/middlewares/admin.js | 22 ++ server/middlewares/index.js | 22 +- server/middlewares/validators/index.js | 2 + server/middlewares/validators/users.js | 57 +++++ server/middlewares/validators/videos.js | 1 + server/models/user.js | 33 ++- server/models/video.js | 5 + server/tests/api/checkParams.js | 268 +++++++++++++++++++-- server/tests/api/users.js | 83 ++++++- server/tests/api/utils.js | 62 ++++- 16 files changed, 688 insertions(+), 47 deletions(-) create mode 100644 server/middlewares/admin.js create mode 100644 server/middlewares/validators/users.js diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js index 2bc761fef..f61f2a483 100644 --- a/server/controllers/api/v1/pods.js +++ b/server/controllers/api/v1/pods.js @@ -8,6 +8,7 @@ const waterfall = require('async/waterfall') const logger = require('../../../helpers/logger') const friends = require('../../../lib/friends') const middlewares = require('../../../middlewares') +const admin = middlewares.admin const oAuth = middlewares.oauth const validators = middlewares.validators.pods const signatureValidator = middlewares.validators.remote.signature @@ -18,8 +19,17 @@ const Video = mongoose.model('Video') router.get('/', listPodsUrl) router.post('/', validators.podsAdd, addPods) -router.get('/makefriends', oAuth.authenticate, validators.makeFriends, makeFriends) -router.get('/quitfriends', oAuth.authenticate, quitFriends) +router.get('/makefriends', + oAuth.authenticate, + admin.ensureIsAdmin, + validators.makeFriends, + makeFriends +) +router.get('/quitfriends', + oAuth.authenticate, + admin.ensureIsAdmin, + quitFriends +) // Post because this is a secured request router.post('/remove', signatureValidator, removePods) diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index fbbe6e472..e084974ce 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -1,18 +1,49 @@ 'use strict' +const each = require('async/each') const config = require('config') -const mongoose = require('mongoose') const express = require('express') +const mongoose = require('mongoose') +const waterfall = require('async/waterfall') -const oAuth = require('../../../middlewares').oauth +const constants = require('../../../initializers/constants') +const friends = require('../../../lib/friends') +const logger = require('../../../helpers/logger') +const middlewares = require('../../../middlewares') +const admin = middlewares.admin +const oAuth = middlewares.oauth +const validatorsUsers = middlewares.validators.users const Client = mongoose.model('OAuthClient') +const User = mongoose.model('User') +const Video = mongoose.model('Video') const router = express.Router() +router.get('/', listUsers) + +router.post('/', + oAuth.authenticate, + admin.ensureIsAdmin, + validatorsUsers.usersAdd, + createUser +) + +router.put('/:id', + oAuth.authenticate, + validatorsUsers.usersUpdate, + updateUser +) + +router.delete('/:username', + oAuth.authenticate, + admin.ensureIsAdmin, + validatorsUsers.usersRemove, + removeUser +) router.get('/client', getAngularClient) router.post('/token', oAuth.token, success) -// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged,, implement revoke token route +// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route // --------------------------------------------------------------------------- @@ -20,6 +51,20 @@ module.exports = router // --------------------------------------------------------------------------- +function createUser (req, res, next) { + const user = new User({ + username: req.body.username, + password: req.body.password, + role: constants.USER_ROLES.USER + }) + + user.save(function (err, createdUser) { + if (err) return next(err) + + return res.type('json').status(204).end() + }) +} + function getAngularClient (req, res, next) { const serverHost = config.get('webserver.host') const serverPort = config.get('webserver.port') @@ -44,6 +89,87 @@ function getAngularClient (req, res, next) { }) } +function listUsers (req, res, next) { + User.list(function (err, usersList) { + if (err) return next(err) + + res.json(getFormatedUsers(usersList)) + }) +} + +function removeUser (req, res, next) { + waterfall([ + function getUser (callback) { + User.loadByUsername(req.params.username, callback) + }, + + function getVideos (user, callback) { + Video.listOwnedByAuthor(user.username, function (err, videos) { + return callback(err, user, videos) + }) + }, + + function removeVideosFromDB (user, videos, callback) { + each(videos, function (video, callbackEach) { + video.remove(callbackEach) + }, function (err) { + return callback(err, user, videos) + }) + }, + + function sendInformationToFriends (user, videos, callback) { + videos.forEach(function (video) { + const params = { + name: video.name, + magnetUri: video.magnetUri + } + + friends.removeVideoToFriends(params) + }) + + return callback(null, user) + }, + + function removeUserFromDB (user, callback) { + user.remove(callback) + } + ], function andFinally (err) { + if (err) { + logger.error('Errors when removed the user.', { error: err }) + return next(err) + } + + return res.type('json').status(204).end() + }) +} + +function updateUser (req, res, next) { + User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { + if (err) return next(err) + + user.password = req.body.password + user.save(function (err) { + if (err) return next(err) + + return res.json('json').status(204).end() + }) + }) +} + function success (req, res, next) { res.end() } + +// --------------------------------------------------------------------------- + +function getFormatedUsers (users) { + const formatedUsers = [] + + users.forEach(function (user) { + formatedUsers.push(user.toFormatedJSON()) + }) + + return { + data: formatedUsers + } +} diff --git a/server/helpers/custom-validators/users.js b/server/helpers/custom-validators/users.js index 41e00d046..0e92989e5 100644 --- a/server/helpers/custom-validators/users.js +++ b/server/helpers/custom-validators/users.js @@ -1,16 +1,29 @@ 'use strict' const validator = require('express-validator').validator +const values = require('lodash/values') const constants = require('../../initializers/constants') const USERS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.USERS const usersValidators = { + isUserPasswordValid: isUserPasswordValid, + isUserRoleValid: isUserRoleValid, isUserUsernameValid: isUserUsernameValid } +function isUserPasswordValid (value) { + return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD) +} + +function isUserRoleValid (value) { + return values(constants.USER_ROLES).indexOf(value) !== -1 +} + function isUserUsernameValid (value) { - return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.USERNAME) + const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max + const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min + return validator.matches(value, new RegExp(`^[a-zA-Z0-9._]{${min},${max}}$`)) } // --------------------------------------------------------------------------- diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js index 39a19cbd7..cffa973f8 100644 --- a/server/helpers/custom-validators/videos.js +++ b/server/helpers/custom-validators/videos.js @@ -45,7 +45,7 @@ function isEachRemoteVideosValid (requests) { } function isVideoAuthorValid (value) { - return usersValidators.isUserUsernameValid(usersValidators) + return usersValidators.isUserUsernameValid(value) } function isVideoDateValid (value) { diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 5f4aeccc6..416356400 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -72,6 +72,11 @@ const THUMBNAILS_SIZE = '200x110' // Path for access to thumbnails with express router const THUMBNAILS_STATIC_PATH = '/static/thumbnails' +const USER_ROLES = { + ADMIN: 'admin', + USER: 'user' +} + // Special constants for a test instance if (isTestInstance() === true) { FRIEND_SCORE.BASE = 20 @@ -96,7 +101,8 @@ module.exports = { SEEDS_IN_PARALLEL: SEEDS_IN_PARALLEL, SORTABLE_COLUMNS: SORTABLE_COLUMNS, THUMBNAILS_SIZE: THUMBNAILS_SIZE, - THUMBNAILS_STATIC_PATH: THUMBNAILS_STATIC_PATH + THUMBNAILS_STATIC_PATH: THUMBNAILS_STATIC_PATH, + USER_ROLES: USER_ROLES } // --------------------------------------------------------------------------- diff --git a/server/initializers/installer.js b/server/initializers/installer.js index 32830d4da..c12187871 100644 --- a/server/initializers/installer.js +++ b/server/initializers/installer.js @@ -9,6 +9,7 @@ const path = require('path') const series = require('async/series') const checker = require('./checker') +const constants = require('./constants') const logger = require('../helpers/logger') const peertubeCrypto = require('../helpers/peertube-crypto') @@ -34,7 +35,7 @@ function installApplication (callback) { }, function createOAuthUser (callbackAsync) { - createOAuthUserIfNotExist(callbackAsync) + createOAuthAdminIfNotExist(callbackAsync) } ], callback) } @@ -80,7 +81,7 @@ function createOAuthClientIfNotExist (callback) { }) } -function createOAuthUserIfNotExist (callback) { +function createOAuthAdminIfNotExist (callback) { checker.usersExist(function (err, exist) { if (err) return callback(err) @@ -90,6 +91,7 @@ function createOAuthUserIfNotExist (callback) { logger.info('Creating the administrator.') const username = 'root' + const role = constants.USER_ROLES.ADMIN let password = '' // Do not generate a random password for tests @@ -105,7 +107,8 @@ function createOAuthUserIfNotExist (callback) { const user = new User({ username: username, - password: password + password: password, + role: role }) user.save(function (err, createdUser) { diff --git a/server/middlewares/admin.js b/server/middlewares/admin.js new file mode 100644 index 000000000..bcb60ab95 --- /dev/null +++ b/server/middlewares/admin.js @@ -0,0 +1,22 @@ +'use strict' + +const constants = require('../initializers/constants') +const logger = require('../helpers/logger') + +const adminMiddleware = { + ensureIsAdmin: ensureIsAdmin +} + +function ensureIsAdmin (req, res, next) { + const user = res.locals.oauth.token.user + if (user.role !== constants.USER_ROLES.ADMIN) { + logger.info('A non admin user is trying to access to an admin content.') + return res.sendStatus(403) + } + + return next() +} + +// --------------------------------------------------------------------------- + +module.exports = adminMiddleware diff --git a/server/middlewares/index.js b/server/middlewares/index.js index 0a233e701..1e294de5f 100644 --- a/server/middlewares/index.js +++ b/server/middlewares/index.js @@ -1,19 +1,21 @@ 'use strict' -const oauth = require('./oauth') -const pagination = require('./pagination') +const adminMiddleware = require('./admin') +const oauthMiddleware = require('./oauth') +const paginationMiddleware = require('./pagination') const validatorsMiddleware = require('./validators') -const search = require('./search') -const sort = require('./sort') +const searchMiddleware = require('./search') +const sortMiddleware = require('./sort') const secureMiddleware = require('./secure') const middlewares = { - oauth: oauth, - pagination: pagination, - validators: validatorsMiddleware, - search: search, - sort: sort, - secure: secureMiddleware + admin: adminMiddleware, + oauth: oauthMiddleware, + pagination: paginationMiddleware, + search: searchMiddleware, + secure: secureMiddleware, + sort: sortMiddleware, + validators: validatorsMiddleware } // --------------------------------------------------------------------------- diff --git a/server/middlewares/validators/index.js b/server/middlewares/validators/index.js index 0471b3f92..6c3a9c2b4 100644 --- a/server/middlewares/validators/index.js +++ b/server/middlewares/validators/index.js @@ -4,6 +4,7 @@ const paginationValidators = require('./pagination') const podsValidators = require('./pods') const remoteValidators = require('./remote') const sortValidators = require('./sort') +const usersValidators = require('./users') const videosValidators = require('./videos') const validators = { @@ -11,6 +12,7 @@ const validators = { pods: podsValidators, remote: remoteValidators, sort: sortValidators, + users: usersValidators, videos: videosValidators } diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js new file mode 100644 index 000000000..175d90bcb --- /dev/null +++ b/server/middlewares/validators/users.js @@ -0,0 +1,57 @@ +'use strict' + +const mongoose = require('mongoose') + +const checkErrors = require('./utils').checkErrors +const logger = require('../../helpers/logger') + +const User = mongoose.model('User') + +const validatorsUsers = { + usersAdd: usersAdd, + usersRemove: usersRemove, + usersUpdate: usersUpdate +} + +function usersAdd (req, res, next) { + req.checkBody('username', 'Should have a valid username').isUserUsernameValid() + req.checkBody('password', 'Should have a valid password').isUserPasswordValid() + + // TODO: check we don't have already the same username + + logger.debug('Checking usersAdd parameters', { parameters: req.body }) + + checkErrors(req, res, next) +} + +function usersRemove (req, res, next) { + req.checkParams('username', 'Should have a valid username').isUserUsernameValid() + + logger.debug('Checking usersRemove parameters', { parameters: req.params }) + + checkErrors(req, res, function () { + User.loadByUsername(req.params.username, function (err, user) { + if (err) { + logger.error('Error in usersRemove request validator.', { error: err }) + return res.sendStatus(500) + } + + if (!user) return res.status(404).send('User not found') + + next() + }) + }) +} + +function usersUpdate (req, res, next) { + // Add old password verification + req.checkBody('password', 'Should have a valid password').isUserPasswordValid() + + logger.debug('Checking usersUpdate parameters', { parameters: req.body }) + + checkErrors(req, res, next) +} + +// --------------------------------------------------------------------------- + +module.exports = validatorsUsers diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js index 422f3642f..9d21ee16f 100644 --- a/server/middlewares/validators/videos.js +++ b/server/middlewares/validators/videos.js @@ -18,6 +18,7 @@ const validatorsVideos = { function videosAdd (req, res, next) { req.checkFiles('videofile[0].originalname', 'Should have an input video').notEmpty() + // TODO: move to constants and function req.checkFiles('videofile[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i) req.checkBody('name', 'Should have a valid name').isVideoNameValid() req.checkBody('description', 'Should have a valid description').isVideoDescriptionValid() diff --git a/server/models/user.js b/server/models/user.js index 14ffecbff..0bbd638d4 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -1,28 +1,49 @@ const mongoose = require('mongoose') +const customUsersValidators = require('../helpers/custom-validators').users + // --------------------------------------------------------------------------- const UserSchema = mongoose.Schema({ password: String, - username: String + username: String, + role: String }) -UserSchema.path('password').required(true) -UserSchema.path('username').required(true) +UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) +UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) +UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) + +UserSchema.methods = { + toFormatedJSON: toFormatedJSON +} UserSchema.statics = { getByUsernameAndPassword: getByUsernameAndPassword, - list: list + list: list, + loadByUsername: loadByUsername } mongoose.model('User', UserSchema) // --------------------------------------------------------------------------- +function getByUsernameAndPassword (username, password) { + return this.findOne({ username: username, password: password }) +} + function list (callback) { return this.find(callback) } -function getByUsernameAndPassword (username, password) { - return this.findOne({ username: username, password: password }) +function loadByUsername (username, callback) { + return this.findOne({ username: username }, callback) +} + +function toFormatedJSON () { + return { + id: this._id, + username: this.username, + role: this.role + } } diff --git a/server/models/video.js b/server/models/video.js index acb8353c2..14bc91b16 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -64,6 +64,7 @@ VideoSchema.statics = { listByUrlAndMagnet: listByUrlAndMagnet, listByUrls: listByUrls, listOwned: listOwned, + listOwnedByAuthor: listOwnedByAuthor, listRemotes: listRemotes, load: load, search: search, @@ -211,6 +212,10 @@ function listOwned (callback) { this.find({ filename: { $ne: null } }, callback) } +function listOwnedByAuthor (author, callback) { + this.find({ filename: { $ne: null }, author: author }, callback) +} + function listRemotes (callback) { this.find({ filename: null }, callback) } diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js index c1ba9c2c0..bd7227e9c 100644 --- a/server/tests/api/checkParams.js +++ b/server/tests/api/checkParams.js @@ -11,9 +11,8 @@ const utils = require('./utils') describe('Test parameters validator', function () { let server = null - function makePostRequest (path, token, fields, attaches, done, fail) { - let statusCode = 400 - if (fail !== undefined && fail === false) statusCode = 204 + function makePostRequest (path, token, fields, attaches, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 const req = request(server.url) .post(path) @@ -38,18 +37,31 @@ describe('Test parameters validator', function () { req.attach(attach, value) }) - req.expect(statusCode, done) + req.expect(statusCodeExpected, done) } - function makePostBodyRequest (path, fields, done, fail) { - let statusCode = 400 - if (fail !== undefined && fail === false) statusCode = 200 + function makePostBodyRequest (path, token, fields, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 - request(server.url) + const req = request(server.url) .post(path) .set('Accept', 'application/json') - .send(fields) - .expect(statusCode, done) + + if (token) req.set('Authorization', 'Bearer ' + token) + + req.send(fields).expect(statusCodeExpected, done) + } + + function makePutBodyRequest (path, token, fields, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 + + const req = request(server.url) + .put(path) + .set('Accept', 'application/json') + + if (token) req.set('Authorization', 'Bearer ' + token) + + req.send(fields).expect(statusCodeExpected, done) } // --------------------------------------------------------------- @@ -85,21 +97,21 @@ describe('Test parameters validator', function () { describe('When adding a pod', function () { it('Should fail with nothing', function (done) { const data = {} - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) it('Should fail without public key', function (done) { const data = { url: 'http://coucou.com' } - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) it('Should fail without an url', function (done) { const data = { publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) it('Should fail with an incorrect url', function (done) { @@ -107,11 +119,11 @@ describe('Test parameters validator', function () { url: 'coucou.com', publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, data, function () { + makePostBodyRequest(path, null, data, function () { data.url = 'http://coucou' - makePostBodyRequest(path, data, function () { + makePostBodyRequest(path, null, data, function () { data.url = 'coucou' - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) }) }) @@ -121,7 +133,68 @@ describe('Test parameters validator', function () { url: 'http://coucou.com', publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, data, done, false) + makePostBodyRequest(path, null, data, done, 200) + }) + }) + + describe('For the friends API', function () { + let userAccessToken = null + + before(function (done) { + utils.createUser(server.url, server.accessToken, 'user1', 'password', function () { + server.user = { + username: 'user1', + password: 'password' + } + + utils.loginAndGetAccessToken(server, function (err, accessToken) { + if (err) throw err + + userAccessToken = accessToken + + done() + }) + }) + }) + + describe('When making friends', function () { + it('Should fail with a invalid token', function (done) { + request(server.url) + .get(path + '/makefriends') + .query({ start: 'hello' }) + .set('Authorization', 'Bearer faketoken') + .set('Accept', 'application/json') + .expect(401, done) + }) + + it('Should fail if the user is not an administrator', function (done) { + request(server.url) + .get(path + '/makefriends') + .query({ start: 'hello' }) + .set('Authorization', 'Bearer ' + userAccessToken) + .set('Accept', 'application/json') + .expect(403, done) + }) + }) + + describe('When quitting friends', function () { + it('Should fail with a invalid token', function (done) { + request(server.url) + .get(path + '/quitfriends') + .query({ start: 'hello' }) + .set('Authorization', 'Bearer faketoken') + .set('Accept', 'application/json') + .expect(401, done) + }) + + it('Should fail if the user is not an administrator', function (done) { + request(server.url) + .get(path + '/quitfriends') + .query({ start: 'hello' }) + .set('Authorization', 'Bearer ' + userAccessToken) + .set('Accept', 'application/json') + .expect(403, done) + }) }) }) }) @@ -361,7 +434,7 @@ describe('Test parameters validator', function () { attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4') makePostRequest(path, server.accessToken, data, attach, function () { attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv') - makePostRequest(path, server.accessToken, data, attach, done, false) + makePostRequest(path, server.accessToken, data, attach, done, 204) }, false) }, false) }) @@ -429,6 +502,165 @@ describe('Test parameters validator', function () { }) }) + describe('Of the users API', function () { + const path = '/api/v1/users/' + + describe('When adding a new user', function () { + it('Should fail with a too small username', function (done) { + const data = { + username: 'ji', + password: 'mysuperpassword' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with a too long username', function (done) { + const data = { + username: 'mysuperusernamewhichisverylong', + password: 'mysuperpassword' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with an incorrect username', function (done) { + const data = { + username: 'my username', + password: 'mysuperpassword' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with a too small password', function (done) { + const data = { + username: 'myusername', + password: 'bla' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with a too long password', function (done) { + const data = { + username: 'myusername', + password: 'my super long password 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 veryv 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' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with an non authenticated user', function (done) { + const data = { + username: 'myusername', + password: 'my super password' + } + + makePostBodyRequest(path, 'super token', data, done, 401) + }) + + it('Should succeed with the correct params', function (done) { + const data = { + username: 'user1', + password: 'my super password' + } + + makePostBodyRequest(path, server.accessToken, data, done, 204) + }) + + it('Should fail with a non admin user', function (done) { + server.user = { + username: 'user1', + password: 'my super password' + } + + utils.loginAndGetAccessToken(server, function (err, accessToken) { + if (err) throw err + + const data = { + username: 'user2', + password: 'my super password' + } + + makePostBodyRequest(path, accessToken, data, done, 403) + }) + }) + }) + + describe('When updating a user', function () { + let userId = null + + before(function (done) { + utils.getUsersList(server.url, function (err, res) { + if (err) throw err + + userId = res.body.data[1].id + done() + }) + }) + + it('Should fail with a too small password', function (done) { + const data = { + password: 'bla' + } + + makePutBodyRequest(path + '/' + userId, server.accessToken, data, done) + }) + + it('Should fail with a too long password', function (done) { + const data = { + password: 'my super long password 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 veryv 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' + } + + makePutBodyRequest(path + '/' + userId, server.accessToken, data, done) + }) + + it('Should fail with an non authenticated user', function (done) { + const data = { + password: 'my super password' + } + + makePutBodyRequest(path + '/' + userId, 'super token', data, done, 401) + }) + + it('Should succeed with the correct params', function (done) { + const data = { + password: 'my super password' + } + + makePutBodyRequest(path + '/' + userId, server.accessToken, data, done, 204) + }) + }) + + describe('When removing an user', function () { + it('Should fail with an incorrect username', function (done) { + request(server.url) + .delete(path + 'bla-bla') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400, done) + }) + + it('Should return 404 with a non existing username', function (done) { + request(server.url) + .delete(path + 'qzzerg') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(404, done) + }) + + it('Should success with the correct parameters', function (done) { + request(server.url) + .delete(path + 'user1') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(204, done) + }) + }) + }) + describe('Of the remote videos API', function () { describe('When making a secure request', function () { it('Should check a secure request') diff --git a/server/tests/api/users.js b/server/tests/api/users.js index 68ba9de33..c711d6b64 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js @@ -13,7 +13,9 @@ const utils = require('./utils') describe('Test users', function () { let server = null let accessToken = null - let videoId + let accessTokenUser = null + let videoId = null + let userId = null before(function (done) { this.timeout(20000) @@ -158,6 +160,85 @@ describe('Test users', function () { it('Should be able to upload a video again') + it('Should be able to create a new user', function (done) { + utils.createUser(server.url, accessToken, 'user_1', 'super password', done) + }) + + it('Should be able to login with this user', function (done) { + server.user = { + username: 'user_1', + password: 'super password' + } + + utils.loginAndGetAccessToken(server, function (err, token) { + if (err) throw err + + accessTokenUser = token + + done() + }) + }) + + it('Should be able to upload a video with this user', function (done) { + this.timeout(5000) + + const name = 'my super name' + const description = 'my super description' + const tags = [ 'tag1', 'tag2', 'tag3' ] + const file = 'video_short.webm' + utils.uploadVideo(server.url, accessTokenUser, name, description, tags, file, done) + }) + + it('Should list all the users', function (done) { + utils.getUsersList(server.url, function (err, res) { + if (err) throw err + + const users = res.body.data + + expect(users).to.be.an('array') + expect(users.length).to.equal(2) + + const rootUser = users[0] + expect(rootUser.username).to.equal('root') + + const user = users[1] + expect(user.username).to.equal('user_1') + userId = user.id + + done() + }) + }) + + it('Should update the user password', function (done) { + utils.updateUser(server.url, userId, accessTokenUser, 'new password', function (err, res) { + if (err) throw err + + server.user.password = 'new password' + utils.login(server.url, server.client, server.user, 200, done) + }) + }) + + it('Should be able to remove this user', function (done) { + utils.removeUser(server.url, accessToken, 'user_1', done) + }) + + it('Should not be able to login with this user', function (done) { + // server.user is already set to user 1 + utils.login(server.url, server.client, server.user, 400, done) + }) + + it('Should not have videos of this user', function (done) { + utils.getVideosList(server.url, function (err, res) { + if (err) throw err + + expect(res.body.total).to.equal(1) + const video = res.body.data[0] + expect(video.author).to.equal('root') + + done() + }) + }) + after(function (done) { process.kill(-server.app.pid) diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js index 3cc769f26..f34b81e4a 100644 --- a/server/tests/api/utils.js +++ b/server/tests/api/utils.js @@ -8,11 +8,13 @@ const pathUtils = require('path') const request = require('supertest') const testUtils = { + createUser: createUser, dateIsValid: dateIsValid, flushTests: flushTests, getAllVideosListBy: getAllVideosListBy, getClient: getClient, getFriendsList: getFriendsList, + getUsersList: getUsersList, getVideo: getVideo, getVideosList: getVideosList, getVideosListPagination: getVideosListPagination, @@ -21,6 +23,7 @@ const testUtils = { loginAndGetAccessToken: loginAndGetAccessToken, makeFriends: makeFriends, quitFriends: quitFriends, + removeUser: removeUser, removeVideo: removeVideo, flushAndRunMultipleServers: flushAndRunMultipleServers, runServer: runServer, @@ -28,11 +31,29 @@ const testUtils = { searchVideoWithPagination: searchVideoWithPagination, searchVideoWithSort: searchVideoWithSort, testImage: testImage, - uploadVideo: uploadVideo + uploadVideo: uploadVideo, + updateUser: updateUser } // ---------------------- Export functions -------------------- +function createUser (url, accessToken, username, password, specialStatus, end) { + if (!end) { + end = specialStatus + specialStatus = 204 + } + + const path = '/api/v1/users' + + request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ username: username, password: password }) + .expect(specialStatus) + .end(end) +} + function dateIsValid (dateString) { const dateToCheck = new Date(dateString) const now = new Date() @@ -72,6 +93,17 @@ function getClient (url, end) { .end(end) } +function getUsersList (url, end) { + const path = '/api/v1/users' + + request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + function getFriendsList (url, end) { const path = '/api/v1/pods/' @@ -209,6 +241,22 @@ function quitFriends (url, accessToken, expectedStatus, callback) { }) } +function removeUser (url, token, username, expectedStatus, end) { + if (!end) { + end = expectedStatus + expectedStatus = 204 + } + + const path = '/api/v1/users' + + request(url) + .delete(path + '/' + username) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) + .end(end) +} + function removeVideo (url, token, id, expectedStatus, end) { if (!end) { end = expectedStatus @@ -414,6 +462,18 @@ function uploadVideo (url, accessToken, name, description, tags, fixture, specia .end(end) } +function updateUser (url, userId, accessToken, newPassword, end) { + const path = '/api/v1/users/' + userId + + request(url) + .put(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ password: newPassword }) + .expect(200) + .end(end) +} + // --------------------------------------------------------------------------- module.exports = testUtils