Fix issues on server start
authorChocobozzz <florian.bigard@gmail.com>
Tue, 14 Nov 2017 09:57:56 +0000 (10:57 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 27 Nov 2017 18:40:51 +0000 (19:40 +0100)
14 files changed:
server/helpers/custom-validators/activitypub/account.ts
server/helpers/custom-validators/activitypub/misc.ts
server/helpers/custom-validators/activitypub/videos.ts
server/helpers/custom-validators/video-channels.ts
server/helpers/custom-validators/videos.ts
server/initializers/constants.ts
server/initializers/installer.ts
server/lib/user.ts
server/lib/video-channel.ts
server/models/account/account.ts
server/models/account/user.ts
server/models/pod/pod.ts
server/models/video/video-channel.ts
server/models/video/video.ts

index 8a7d1b7febd0c893c2693750f27d04e3efe9897c..acd2b805838b465b46c91726ff7c1c192afebbc8 100644 (file)
@@ -3,6 +3,7 @@ import * as validator from 'validator'
 import { exists, isUUIDValid } from '../misc'
 import { isActivityPubUrlValid } from './misc'
 import { isUserUsernameValid } from '../users'
+import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
 
 function isAccountEndpointsObjectValid (endpointObject: any) {
   return isAccountSharedInboxValid(endpointObject.sharedInbox)
@@ -34,7 +35,8 @@ function isAccountPublicKeyValid (publicKey: string) {
   return exists(publicKey) &&
     typeof publicKey === 'string' &&
     publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
-    publicKey.endsWith('-----END PUBLIC KEY-----')
+    publicKey.endsWith('-----END PUBLIC KEY-----') &&
+    validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY)
 }
 
 function isAccountIdValid (id: string) {
@@ -73,7 +75,8 @@ function isAccountPrivateKeyValid (privateKey: string) {
   return exists(privateKey) &&
     typeof privateKey === 'string' &&
     privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
-    privateKey.endsWith('-----END RSA PRIVATE KEY-----')
+    privateKey.endsWith('-----END RSA PRIVATE KEY-----') &&
+    validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY)
 }
 
 function isRemoteAccountValid (remoteAccount: any) {
index f049f5a8cdb5610a27680aea0267093b3e2092a4..a94c36b514328d5989aaf766df9a197822d612c8 100644 (file)
@@ -1,4 +1,7 @@
+import * as validator from 'validator'
 import { exists } from '../misc'
+import { isTestInstance } from '../../core-utils'
+import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
 
 function isActivityPubUrlValid (url: string) {
   const isURLOptions = {
@@ -9,7 +12,12 @@ function isActivityPubUrlValid (url: string) {
     protocols: [ 'http', 'https' ]
   }
 
-  return exists(url) && validator.isURL(url, isURLOptions)
+  // We validate 'localhost', so we don't have the top level domain
+  if (isTestInstance()) {
+    isURLOptions.require_tld = false
+  }
+
+  return exists(url) && validator.isURL(url, isURLOptions) && validator.isLength(url, CONSTRAINTS_FIELDS.ACCOUNTS.URL)
 }
 
 function isBaseActivityValid (activity: any, type: string) {
index 9233a1359aa719e4c7aa3e1908ad158afd7d7c2a..8f6d50f509706e18a9892d33226028e764edcc35 100644 (file)
@@ -10,7 +10,8 @@ import {
   isVideoTruncatedDescriptionValid,
   isVideoDurationValid,
   isVideoNameValid,
-  isVideoTagValid
+  isVideoTagValid,
+  isVideoUrlValid
 } from '../videos'
 import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
 import { isBaseActivityValid } from './misc'
@@ -93,7 +94,7 @@ function isRemoteVideoContentValid (mediaType: string, content: string) {
 
 function isRemoteVideoIconValid (icon: any) {
   return icon.type === 'Image' &&
-    validator.isURL(icon.url) &&
+    isVideoUrlValid(icon.url) &&
     icon.mediaType === 'image/jpeg' &&
     validator.isInt(icon.width, { min: 0 }) &&
     validator.isInt(icon.height, { min: 0 })
@@ -111,7 +112,7 @@ function setValidRemoteVideoUrls (video: any) {
 function isRemoteVideoUrlValid (url: any) {
   return url.type === 'Link' &&
     ACTIVITY_PUB.VIDEO_URL_MIME_TYPES.indexOf(url.mimeType) !== -1 &&
-    validator.isURL(url.url) &&
+    isVideoUrlValid(url.url) &&
     validator.isInt(url.width, { min: 0 }) &&
     validator.isInt(url.size, { min: 0 })
 }
index acc42f4a49e136c4b6f3508b8c7b2b7cb9665f53..5787c3850cbace61a59be7f62d011ee5cbc1592a 100644 (file)
@@ -8,9 +8,14 @@ import { database as db, CONSTRAINTS_FIELDS } from '../../initializers'
 import { VideoChannelInstance } from '../../models'
 import { logger } from '../logger'
 import { exists } from './misc'
+import { isActivityPubUrlValid } from './index'
 
 const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS
 
+function isVideoChannelUrlValid (value: string) {
+  return isActivityPubUrlValid(value)
+}
+
 function isVideoChannelDescriptionValid (value: string) {
   return value === null || validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.DESCRIPTION)
 }
@@ -53,5 +58,6 @@ export {
   isVideoChannelDescriptionValid,
   isVideoChannelNameValid,
   isVideoChannelUUIDValid,
-  checkVideoChannelExists
+  checkVideoChannelExists,
+  isVideoChannelUrlValid
 }
index 487b3d6464205104a14960b51b99f49192de50a9..715119cf68d7003d1523a4ef5790c818dbaabf82 100644 (file)
@@ -19,6 +19,7 @@ import { isArray, exists } from './misc'
 import { VideoInstance } from '../../models'
 import { logger } from '../../helpers'
 import { VideoRateType } from '../../../shared'
+import { isActivityPubUrlValid } from './activitypub/misc'
 
 const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
 const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
@@ -33,6 +34,10 @@ function isRemoteVideoCategoryValid (value: string) {
   return validator.isInt('' + value)
 }
 
+function isVideoUrlValid (value: string) {
+  return isActivityPubUrlValid(value)
+}
+
 function isVideoLicenceValid (value: number) {
   return VIDEO_LICENCES[value] !== undefined
 }
@@ -219,5 +224,6 @@ export {
   isVideoTagValid,
   isRemoteVideoCategoryValid,
   isRemoteVideoLicenceValid,
+  isVideoUrlValid,
   isRemoteVideoLanguageValid
 }
index 5d0d39395ec10d346042e2539c128d1cb052f3eb..e27d011fa9cd084c31836a76c4ce2af981d8da07 100644 (file)
@@ -121,7 +121,8 @@ const CONSTRAINTS_FIELDS = {
   },
   VIDEO_CHANNELS: {
     NAME: { min: 3, max: 120 }, // Length
-    DESCRIPTION: { min: 3, max: 250 } // Length
+    DESCRIPTION: { min: 3, max: 250 }, // Length
+    URL: { min: 3, max: 2000 } // Length
   },
   VIDEOS: {
     NAME: { min: 3, max: 120 }, // Length
@@ -137,7 +138,13 @@ const CONSTRAINTS_FIELDS = {
     VIEWS: { min: 0 },
     LIKES: { min: 0 },
     DISLIKES: { min: 0 },
-    FILE_SIZE: { min: 10, max: 1024 * 1024 * 1024 * 3 /* 3Go */ }
+    FILE_SIZE: { min: 10, max: 1024 * 1024 * 1024 * 3 /* 3Go */ },
+    URL: { min: 3, max: 2000 } // Length
+  },
+  ACCOUNTS: {
+    PUBLIC_KEY: { min: 10, max: 5000 }, // Length
+    PRIVATE_KEY: { min: 10, max: 5000 }, // Length
+    URL: { min: 3, max: 2000 } // Length
   },
   VIDEO_EVENTS: {
     COUNT: { min: 0 }
index c617b16c9dc4e1576e85a64636770f2eb110cfa7..5221b81a5bc3de95042f848f1b8403b4f5c215a6 100644 (file)
@@ -1,21 +1,25 @@
 import * as passwordGenerator from 'password-generator'
 import { UserRole } from '../../shared'
 import { logger, mkdirpPromise, rimrafPromise } from '../helpers'
-import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto'
 import { createUserAccountAndChannel } from '../lib'
+import { createLocalAccount } from '../lib/user'
 import { clientsExist, usersExist } from './checker'
 import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants'
 
 import { database as db } from './database'
-import { createLocalAccount } from '../lib/user'
 
 async function installApplication () {
-  await db.sequelize.sync()
-  await removeCacheDirectories()
-  await createDirectoriesIfNotExist()
-  await createOAuthClientIfNotExist()
-  await createOAuthAdminIfNotExist()
-  await createApplicationIfNotExist()
+  try {
+    await db.sequelize.sync()
+    await removeCacheDirectories()
+    await createDirectoriesIfNotExist()
+    await createOAuthClientIfNotExist()
+    await createOAuthAdminIfNotExist()
+    await createApplicationIfNotExist()
+  } catch (err) {
+    logger.error('Cannot install application.', err)
+    throw err
+  }
 }
 
 // ---------------------------------------------------------------------------
index 1094c2401de8b6a2dd9a423f937d2efe64ff55fd..d2d599dfd74d935d66ac554ae4eb6697d33fa4d5 100644 (file)
@@ -16,8 +16,9 @@ async function createUserAccountAndChannel (user: UserInstance, validateUser = t
     const userCreated = await user.save(userOptions)
     const accountCreated = await createLocalAccount(user.username, user.id, null, t)
 
+    const videoChannelName = `Default ${userCreated.username} channel`
     const videoChannelInfo = {
-      name: `Default ${userCreated.username} channel`
+      name: videoChannelName
     }
     const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t)
 
index 459d9d4a8456cf7bb7b3dc8a56137cb475949b9d..13841ea33b7548348fc8c44e71db7feafbd13f66 100644 (file)
@@ -5,6 +5,7 @@ import { logger } from '../helpers'
 import { AccountInstance } from '../models'
 import { VideoChannelCreate } from '../../shared/models'
 import { sendCreateVideoChannel } from './activitypub/send-request'
+import { getActivityPubUrl } from '../helpers/activitypub'
 
 async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) {
   const videoChannelData = {
@@ -15,6 +16,8 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
   }
 
   const videoChannel = db.VideoChannel.build(videoChannelData)
+  videoChannel.set('url', getActivityPubUrl('videoChannel', videoChannel.uuid))
+
   const options = { transaction: t }
 
   const videoChannelCreated = await videoChannel.save(options)
index 6ef29c8b733e5a56fd9653aee024a5e3428f9674..cd6c822f161780deb4dc159215753b319e006fce 100644 (file)
@@ -22,8 +22,8 @@ import {
 
   AccountMethods
 } from './account-interface'
-import LoadApplication = AccountMethods.LoadApplication
 import { sendDeleteAccount } from '../../lib/activitypub/send-request'
+import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
 
 let Account: Sequelize.Model<AccountInstance, AccountAttributes>
 let loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID
@@ -60,14 +60,14 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         type: DataTypes.STRING,
         allowNull: false,
         validate: {
-          usernameValid: value => {
+          nameValid: value => {
             const res = isUserUsernameValid(value)
-            if (res === false) throw new Error('Username is not valid.')
+            if (res === false) throw new Error('Name is not valid.')
           }
         }
       },
       url: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           urlValid: value => {
@@ -77,7 +77,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         }
       },
       publicKey: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max),
         allowNull: false,
         validate: {
           publicKeyValid: value => {
@@ -87,7 +87,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         }
       },
       privateKey: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max),
         allowNull: false,
         validate: {
           privateKeyValid: value => {
@@ -110,14 +110,14 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         type: DataTypes.INTEGER,
         allowNull: false,
         validate: {
-          followersCountValid: value => {
+          followingCountValid: value => {
             const res = isAccountFollowingCountValid(value)
             if (res === false) throw new Error('Following count is not valid.')
           }
         }
       },
       inboxUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           inboxUrlValid: value => {
@@ -127,7 +127,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         }
       },
       outboxUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           outboxUrlValid: value => {
@@ -137,7 +137,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         }
       },
       sharedInboxUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           sharedInboxUrlValid: value => {
@@ -147,7 +147,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         }
       },
       followersUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           followersUrlValid: value => {
@@ -157,7 +157,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         }
       },
       followingUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           followingUrlValid: value => {
@@ -241,7 +241,7 @@ function associate (models) {
 
   Account.belongsTo(models.Application, {
     foreignKey: {
-      name: 'userId',
+      name: 'applicationId',
       allowNull: true
     },
     onDelete: 'cascade'
@@ -256,7 +256,7 @@ function associate (models) {
     hooks: true
   })
 
-  Account.hasMany(models.AccountFollower, {
+  Account.hasMany(models.AccountFollow, {
     foreignKey: {
       name: 'accountId',
       allowNull: false
@@ -265,7 +265,7 @@ function associate (models) {
     onDelete: 'cascade'
   })
 
-  Account.hasMany(models.AccountFollower, {
+  Account.hasMany(models.AccountFollow, {
     foreignKey: {
       name: 'targetAccountId',
       allowNull: false
@@ -329,7 +329,7 @@ getFollowerSharedInboxUrls = function (this: AccountInstance) {
     attributes: [ 'sharedInboxUrl' ],
     include: [
       {
-        model: Account['sequelize'].models.AccountFollower,
+        model: Account['sequelize'].models.AccountFollow,
         where: {
           targetAccountId: this.id
         }
@@ -523,9 +523,9 @@ async function createListAcceptedFollowForApiQuery (type: 'followers' | 'followi
 
   for (const selection of selections) {
     let query = 'SELECT ' + selection + ' FROM "Account" ' +
-      'INNER JOIN "AccountFollower" ON "AccountFollower"."' + firstJoin + '" = "Account"."id" ' +
+      'INNER JOIN "AccountFollow" ON "AccountFollow"."' + firstJoin + '" = "Account"."id" ' +
       'INNER JOIN "Account" AS "Follows" ON "Followers"."id" = "Follows"."' + secondJoin + '" ' +
-      'WHERE "Account"."id" = $id AND "AccountFollower"."state" = \'accepted\' ' +
+      'WHERE "Account"."id" = $id AND "AccountFollow"."state" = \'accepted\' ' +
       'LIMIT ' + start
 
     if (count !== undefined) query += ', ' + count
index 7390baf91d0ecfb001d7d0142b21cece9c735167..8f7c9b01338a1e13e354766f8ef231a5ec3b0007 100644 (file)
@@ -1,23 +1,16 @@
 import * as Sequelize from 'sequelize'
-
-import { getSort, addMethodsToModel } from '../utils'
+import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
 import {
-  cryptPassword,
   comparePassword,
+  cryptPassword,
+  isUserDisplayNSFWValid,
   isUserPasswordValid,
+  isUserRoleValid,
   isUserUsernameValid,
-  isUserDisplayNSFWValid,
-  isUserVideoQuotaValid,
-  isUserRoleValid
+  isUserVideoQuotaValid
 } from '../../helpers'
-import { UserRight, USER_ROLE_LABELS, hasUserRight } from '../../../shared'
-
-import {
-  UserInstance,
-  UserAttributes,
-
-  UserMethods
-} from './user-interface'
+import { addMethodsToModel, getSort } from '../utils'
+import { UserAttributes, UserInstance, UserMethods } from './user-interface'
 
 let User: Sequelize.Model<UserInstance, UserAttributes>
 let isPasswordMatch: UserMethods.IsPasswordMatch
index 7c8b49bf808e7785680eaf1e4ddd52eb97ab8f2e..6d270ad7fdc7dfeb3eeb949f44d3edb7120aad37 100644 (file)
@@ -63,8 +63,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
   )
 
   const classMethods = [
-    associate,
-
     countAll,
     incrementScores,
     list,
@@ -98,14 +96,6 @@ toFormattedJSON = function (this: PodInstance) {
 
 // ------------------------------ Statics ------------------------------
 
-function associate (models) {
-  Pod.belongsToMany(models.Request, {
-    foreignKey: 'podId',
-    through: models.RequestToPod,
-    onDelete: 'cascade'
-  })
-}
-
 countAll = function () {
   return Pod.count()
 }
index 919ec916dad0eb0669e3a1dacdabf8c4d91f9dad..6d70f2aa2e77f23b4570bc474491fa3945a2ed29 100644 (file)
@@ -10,6 +10,8 @@ import {
   VideoChannelMethods
 } from './video-channel-interface'
 import { sendDeleteVideoChannel } from '../../lib/activitypub/send-request'
+import { isVideoChannelUrlValid } from '../../helpers/custom-validators/video-channels'
+import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
 
 let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
 let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
@@ -65,10 +67,13 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
         defaultValue: false
       },
       url: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max),
         allowNull: false,
         validate: {
-          isUrl: true
+          urlValid: value => {
+            const res = isVideoChannelUrlValid(value)
+            if (res === false) throw new Error('Video channel URL is not valid.')
+          }
         }
       }
     },
index ca71da375811f316245d071b24d303cfa7e49187..dd73dd7caa7a813607232069a9c2a4a3d621a1d8 100644 (file)
@@ -46,6 +46,7 @@ import { TagInstance } from './tag-interface'
 import { VideoFileInstance, VideoFileModel } from './video-file-interface'
 import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
 import { sendDeleteVideo } from '../../lib/activitypub/send-request'
+import { isVideoUrlValid } from '../../helpers/custom-validators/videos'
 
 const Buffer = safeBuffer.Buffer
 
@@ -220,10 +221,13 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
         defaultValue: false
       },
       url: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max),
         allowNull: false,
         validate: {
-          isUrl: true
+          urlValid: value => {
+            const res = isVideoUrlValid(value)
+            if (res === false) throw new Error('Video URL is not valid.')
+          }
         }
       }
     },