From 67bf9b96bbcd92b069fe86d9223fe0f8b9c6e677 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 28 Dec 2016 15:49:23 +0100 Subject: [PATCH] Server: add database field validations --- server/helpers/custom-validators/pods.js | 9 ++- server/helpers/custom-validators/videos.js | 6 -- server/initializers/constants.js | 2 +- server/initializers/installer.js | 10 ++- server/models/application.js | 8 ++- server/models/author.js | 13 +++- server/models/oauth-client.js | 11 ++-- server/models/oauth-token.js | 19 +++--- server/models/pod.js | 34 ++++++---- server/models/request.js | 14 ++-- server/models/tag.js | 3 +- server/models/user.js | 32 +++++++--- server/models/video.js | 74 ++++++++++++++++------ 13 files changed, 162 insertions(+), 73 deletions(-) diff --git a/server/helpers/custom-validators/pods.js b/server/helpers/custom-validators/pods.js index 0154a2424..8bb3733ff 100644 --- a/server/helpers/custom-validators/pods.js +++ b/server/helpers/custom-validators/pods.js @@ -5,14 +5,19 @@ const validator = require('express-validator').validator const miscValidators = require('./misc') const podsValidators = { - isEachUniqueHostValid + isEachUniqueHostValid, + isHostValid +} + +function isHostValid (host) { + return validator.isURL(host) && host.split('://').length === 1 } function isEachUniqueHostValid (hosts) { return miscValidators.isArray(hosts) && hosts.length !== 0 && hosts.every(function (host) { - return validator.isURL(host) && host.split('://').length === 1 && hosts.indexOf(host) === hosts.lastIndexOf(host) + return isHostValid(host) && hosts.indexOf(host) === hosts.lastIndexOf(host) }) } diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js index be8256a80..da857ba5f 100644 --- a/server/helpers/custom-validators/videos.js +++ b/server/helpers/custom-validators/videos.js @@ -15,7 +15,6 @@ const videosValidators = { isVideoDurationValid, isVideoInfoHashValid, isVideoNameValid, - isVideoPodHostValid, isVideoTagsValid, isVideoThumbnailValid, isVideoThumbnail64Valid @@ -74,11 +73,6 @@ function isVideoNameValid (value) { return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) } -function isVideoPodHostValid (value) { - // TODO: set options (TLD...) - return validator.isURL(value) -} - function isVideoTagsValid (tags) { return miscValidators.isArray(tags) && validator.isInt(tags.length, VIDEOS_CONSTRAINTS_FIELDS.TAGS) && diff --git a/server/initializers/constants.js b/server/initializers/constants.js index fc501845a..0af7aca3c 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -69,7 +69,7 @@ const CONSTRAINTS_FIELDS = { NAME: { min: 3, max: 50 }, // Length DESCRIPTION: { min: 3, max: 250 }, // Length EXTNAME: [ '.mp4', '.ogv', '.webm' ], - INFO_HASH: { min: 10, max: 50 }, // Length + INFO_HASH: { min: 40, max: 40 }, // Length, infohash is 20 bytes length but we represent it in hexa so 20 * 2 DURATION: { min: 1, max: 7200 }, // Number TAGS: { min: 1, max: 3 }, // Number of total tags TAG: { min: 2, max: 10 }, // Length diff --git a/server/initializers/installer.js b/server/initializers/installer.js index d5382364e..fb63b81ac 100644 --- a/server/initializers/installer.js +++ b/server/initializers/installer.js @@ -96,6 +96,7 @@ function createOAuthAdminIfNotExist (callback) { const username = 'root' const role = constants.USER_ROLES.ADMIN + const createOptions = {} let password = '' // Do not generate a random password for tests @@ -105,17 +106,20 @@ function createOAuthAdminIfNotExist (callback) { if (process.env.NODE_APP_INSTANCE) { password += process.env.NODE_APP_INSTANCE } + + // Our password is weak so do not validate it + createOptions.validate = false } else { password = passwordGenerator(8, true) } - const user = db.User.build({ + const userData = { username, password, role - }) + } - user.save().asCallback(function (err, createdUser) { + db.User.create(userData, createOptions).asCallback(function (err, createdUser) { if (err) return callback(err) logger.info('Username: ' + username) diff --git a/server/models/application.js b/server/models/application.js index 4114ed76d..46dcfde33 100644 --- a/server/models/application.js +++ b/server/models/application.js @@ -1,9 +1,15 @@ +'use strict' + module.exports = function (sequelize, DataTypes) { const Application = sequelize.define('Application', { migrationVersion: { type: DataTypes.INTEGER, - defaultValue: 0 + defaultValue: 0, + allowNull: false, + validate: { + isInt: true + } } }, { diff --git a/server/models/author.js b/server/models/author.js index 493c2ca11..e0ac868ea 100644 --- a/server/models/author.js +++ b/server/models/author.js @@ -1,8 +1,19 @@ +'use strict' + +const customUsersValidators = require('../helpers/custom-validators').users + module.exports = function (sequelize, DataTypes) { const Author = sequelize.define('Author', { name: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false, + validate: { + usernameValid: function (value) { + const res = customUsersValidators.isUserUsernameValid(value) + if (res === false) throw new Error('Username is not valid.') + } + } } }, { diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js index 15118591a..b56838d4c 100644 --- a/server/models/oauth-client.js +++ b/server/models/oauth-client.js @@ -1,11 +1,15 @@ +'use strict' + module.exports = function (sequelize, DataTypes) { const OAuthClient = sequelize.define('OAuthClient', { clientId: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false }, clientSecret: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false }, grants: { type: DataTypes.ARRAY(DataTypes.STRING) @@ -28,9 +32,6 @@ module.exports = function (sequelize, DataTypes) { return OAuthClient } -// TODO: validation -// OAuthClientSchema.path('clientSecret').required(true) - // --------------------------------------------------------------------------- function associate (models) { diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js index c9108bf95..f8de4e916 100644 --- a/server/models/oauth-token.js +++ b/server/models/oauth-token.js @@ -1,3 +1,5 @@ +'use strict' + const logger = require('../helpers/logger') // --------------------------------------------------------------------------- @@ -6,16 +8,20 @@ module.exports = function (sequelize, DataTypes) { const OAuthToken = sequelize.define('OAuthToken', { accessToken: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false }, accessTokenExpiresAt: { - type: DataTypes.DATE + type: DataTypes.DATE, + allowNull: false }, refreshToken: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false }, refreshTokenExpiresAt: { - type: DataTypes.DATE + type: DataTypes.DATE, + allowNull: false } }, { @@ -33,11 +39,6 @@ module.exports = function (sequelize, DataTypes) { return OAuthToken } -// TODO: validation -// OAuthTokenSchema.path('accessToken').required(true) -// OAuthTokenSchema.path('client').required(true) -// OAuthTokenSchema.path('user').required(true) - // --------------------------------------------------------------------------- function associate (models) { diff --git a/server/models/pod.js b/server/models/pod.js index fff6970a7..84f78f200 100644 --- a/server/models/pod.js +++ b/server/models/pod.js @@ -3,6 +3,7 @@ const map = require('lodash/map') const constants = require('../initializers/constants') +const customPodsValidators = require('../helpers/custom-validators').pods // --------------------------------------------------------------------------- @@ -10,14 +11,27 @@ module.exports = function (sequelize, DataTypes) { const Pod = sequelize.define('Pod', { host: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false, + validate: { + isHost: function (value) { + const res = customPodsValidators.isHostValid(value) + if (res === false) throw new Error('Host not valid.') + } + } }, publicKey: { - type: DataTypes.STRING(5000) + type: DataTypes.STRING(5000), + allowNull: false }, score: { type: DataTypes.INTEGER, - defaultValue: constants.FRIEND_SCORE.BASE + defaultValue: constants.FRIEND_SCORE.BASE, + allowNull: false, + validate: { + isInt: true, + max: constants.FRIEND_SCORE.MAX + } } }, { @@ -42,12 +56,6 @@ module.exports = function (sequelize, DataTypes) { return Pod } -// TODO: max score -> constants.FRIENDS_SCORE.MAX -// TODO: validation -// PodSchema.path('host').validate(validator.isURL) -// PodSchema.path('publicKey').required(true) -// PodSchema.path('score').validate(function (value) { return !isNaN(value) }) - // ------------------------------ METHODS ------------------------------ function toFormatedJSON () { @@ -82,15 +90,17 @@ function incrementScores (ids, value, callback) { score: this.sequelize.literal('score +' + value) } - const query = { + const options = { where: { id: { $in: ids } - } + }, + // In this case score is a literal and not an integer so we do not validate it + validate: false } - return this.update(update, query).asCallback(callback) + return this.update(update, options).asCallback(callback) } function list (callback) { diff --git a/server/models/request.js b/server/models/request.js index 70aa32610..e18f8fe3d 100644 --- a/server/models/request.js +++ b/server/models/request.js @@ -3,6 +3,7 @@ const each = require('async/each') const eachLimit = require('async/eachLimit') const waterfall = require('async/waterfall') +const values = require('lodash/values') const constants = require('../initializers/constants') const logger = require('../helpers/logger') @@ -17,11 +18,12 @@ module.exports = function (sequelize, DataTypes) { const Request = sequelize.define('Request', { request: { - type: DataTypes.JSON + type: DataTypes.JSON, + allowNull: false }, endpoint: { - // TODO: enum? - type: DataTypes.STRING + type: DataTypes.ENUM(values(constants.REQUEST_ENDPOINTS)), + allowNull: false } }, { @@ -196,7 +198,7 @@ function makeRequests () { makeRequest(toPod, requestToMake.endpoint, requestToMake.datas, function (success) { if (success === true) { - logger.debug('Removing requests for %s pod.', requestToMake.toPodId, { requestsIds: requestToMake.ids }) + logger.debug('Removing requests for pod %s.', requestToMake.toPodId, { requestsIds: requestToMake.ids }) goodPods.push(requestToMake.toPodId) @@ -261,13 +263,13 @@ function updatePodsScore (goodPods, badPods) { if (goodPods.length !== 0) { Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { - if (err) logger.error('Cannot increment scores of good pods.') + if (err) logger.error('Cannot increment scores of good pods.', { error: err }) }) } if (badPods.length !== 0) { Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { - if (err) logger.error('Cannot decrement scores of bad pods.') + if (err) logger.error('Cannot decrement scores of bad pods.', { error: err }) removeBadPods.call(self) }) } diff --git a/server/models/tag.js b/server/models/tag.js index 874e88842..d6c2d3bb1 100644 --- a/server/models/tag.js +++ b/server/models/tag.js @@ -6,7 +6,8 @@ module.exports = function (sequelize, DataTypes) { const Tag = sequelize.define('Tag', { name: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false } }, { diff --git a/server/models/user.js b/server/models/user.js index e50eb96ea..944986a44 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -1,5 +1,11 @@ +'use strict' + +const values = require('lodash/values') + const modelUtils = require('./utils') +const constants = require('../initializers/constants') const peertubeCrypto = require('../helpers/peertube-crypto') +const customUsersValidators = require('../helpers/custom-validators').users // --------------------------------------------------------------------------- @@ -7,13 +13,28 @@ module.exports = function (sequelize, DataTypes) { const User = sequelize.define('User', { password: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false, + validate: { + passwordValid: function (value) { + const res = customUsersValidators.isUserPasswordValid(value) + if (res === false) throw new Error('Password not valid.') + } + } }, username: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false, + validate: { + usernameValid: function (value) { + const res = customUsersValidators.isUserUsernameValid(value) + if (res === false) throw new Error('Username not valid.') + } + } }, role: { - type: DataTypes.STRING + type: DataTypes.ENUM(values(constants.USER_ROLES)), + allowNull: false } }, { @@ -41,11 +62,6 @@ module.exports = function (sequelize, DataTypes) { return User } -// TODO: Validation -// UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) -// UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) -// UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) - function beforeCreateOrUpdate (user, options, next) { peertubeCrypto.cryptPassword(user.password, function (err, hash) { if (err) return next(err) diff --git a/server/models/video.js b/server/models/video.js index 04478c8d7..3ebc48ad4 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -8,10 +8,12 @@ const map = require('lodash/map') const parallel = require('async/parallel') const parseTorrent = require('parse-torrent') const pathUtils = require('path') +const values = require('lodash/values') const constants = require('../initializers/constants') const logger = require('../helpers/logger') const modelUtils = require('./utils') +const customVideosValidators = require('../helpers/custom-validators').videos // --------------------------------------------------------------------------- @@ -22,26 +24,61 @@ module.exports = function (sequelize, DataTypes) { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, - primaryKey: true + primaryKey: true, + validate: { + isUUID: 4 + } }, name: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false, + validate: { + nameValid: function (value) { + const res = customVideosValidators.isVideoNameValid(value) + if (res === false) throw new Error('Video name is not valid.') + } + } }, extname: { - // TODO: enum? - type: DataTypes.STRING + type: DataTypes.ENUM(values(constants.CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)), + allowNull: false }, remoteId: { - type: DataTypes.UUID + type: DataTypes.UUID, + allowNull: true, + validate: { + isUUID: 4 + } }, description: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false, + validate: { + descriptionValid: function (value) { + const res = customVideosValidators.isVideoDescriptionValid(value) + if (res === false) throw new Error('Video description is not valid.') + } + } }, infoHash: { - type: DataTypes.STRING + type: DataTypes.STRING, + allowNull: false, + validate: { + infoHashValid: function (value) { + const res = customVideosValidators.isVideoInfoHashValid(value) + if (res === false) throw new Error('Video info hash is not valid.') + } + } }, duration: { - type: DataTypes.INTEGER + type: DataTypes.INTEGER, + allowNull: false, + validate: { + durationValid: function (value) { + const res = customVideosValidators.isVideoDurationValid(value) + if (res === false) throw new Error('Video duration is not valid.') + } + } } }, { @@ -71,6 +108,7 @@ module.exports = function (sequelize, DataTypes) { toRemoteJSON }, hooks: { + beforeValidate, beforeCreate, afterDestroy } @@ -80,13 +118,14 @@ module.exports = function (sequelize, DataTypes) { return Video } -// TODO: Validation -// VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid) -// VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid) -// VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid) -// VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid) -// VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid) -// VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) +function beforeValidate (video, options, next) { + if (video.isOwned()) { + // 40 hexa length + video.infoHash = '0123456789abcdef0123456789abcdef01234567' + } + + return next(null) +} function beforeCreate (video, options, next) { const tasks = [] @@ -113,9 +152,8 @@ function beforeCreate (video, options, next) { if (err) return callback(err) const parsedTorrent = parseTorrent(torrent) - video.infoHash = parsedTorrent.infoHash - - callback(null) + video.set('infoHash', parsedTorrent.infoHash) + video.validate().asCallback(callback) }) }) }, -- 2.41.0