Server: add database field validations
authorChocobozzz <florian.bigard@gmail.com>
Wed, 28 Dec 2016 14:49:23 +0000 (15:49 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Wed, 28 Dec 2016 14:49:23 +0000 (15:49 +0100)
13 files changed:
server/helpers/custom-validators/pods.js
server/helpers/custom-validators/videos.js
server/initializers/constants.js
server/initializers/installer.js
server/models/application.js
server/models/author.js
server/models/oauth-client.js
server/models/oauth-token.js
server/models/pod.js
server/models/request.js
server/models/tag.js
server/models/user.js
server/models/video.js

index 0154a242432b1bf43b6bc35ab94bb105864f82e8..8bb3733ffae4465afd4eac17131b1f288c695cba 100644 (file)
@@ -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)
     })
 }
 
index be8256a80e9934e309b0dac4317a2a357b3a7dac..da857ba5f379871f0b25b41f576df9a1d512a1e8 100644 (file)
@@ -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) &&
index fc501845a9fce53037c38b4fb0ac16d032e2365e..0af7aca3c39040cb0ed1d61411c0f6a3b0477739 100644 (file)
@@ -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
index d5382364ed77d4ccc47c8624232a0ae53af56ebd..fb63b81ac64c1e962446bc4adf283c60f4d6836e 100644 (file)
@@ -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)
index 4114ed76de12b10981994646aee0cdaa4ca6700c..46dcfde33bf88fb3103db5b0ef80709ed1e1c8e4 100644 (file)
@@ -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
+        }
       }
     },
     {
index 493c2ca112efecb9fd7548da0d5e83ed583b8f41..e0ac868eab7384536670305cffa9258e73735c43 100644 (file)
@@ -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.')
+          }
+        }
       }
     },
     {
index 15118591a72350e08edc47958d98f8afbba2fdb2..b56838d4c9918126a35b82c2f38d52caf5c77eb9 100644 (file)
@@ -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) {
index c9108bf9578d7c11a1d6f71ecf9e5d8468e30604..f8de4e9161613b5f672b07c871521e0f437f46dc 100644 (file)
@@ -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) {
index fff6970a7e6a6fd96725488e5c0796795aed6915..84f78f2007ac00f38edea0bd68c413f67e2db0de 100644 (file)
@@ -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) {
index 70aa32610b174d531198320957e4fb09e6396352..e18f8fe3df5978176d50e4abf3f8dfed8d97877c 100644 (file)
@@ -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)
     })
   }
index 874e88842c106f6714aea6f83effeee3e75cd497..d6c2d3bb12ff2c75a7a489951d2d97411ecb0af5 100644 (file)
@@ -6,7 +6,8 @@ module.exports = function (sequelize, DataTypes) {
   const Tag = sequelize.define('Tag',
     {
       name: {
-        type: DataTypes.STRING
+        type: DataTypes.STRING,
+        allowNull: false
       }
     },
     {
index e50eb96ea514b876232a18a2f7076762a1d8772b..944986a44dfb9ffbe7ec0c4eb83d4730b7921b51 100644 (file)
@@ -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)
index 04478c8d74202965ec4e227493d2f9c4b6f3dc1b..3ebc48ad4d66ec5bc4faa8a370c5178e1abedf8c 100644 (file)
@@ -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)
           })
         })
       },