From e34c85e527100c0b5c44567bd951e95be41b8d7e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 14 Nov 2017 10:57:56 +0100 Subject: [PATCH] Fix issues on server start --- .../custom-validators/activitypub/account.ts | 7 ++-- .../custom-validators/activitypub/misc.ts | 10 +++++- .../custom-validators/activitypub/videos.ts | 7 ++-- .../custom-validators/video-channels.ts | 8 ++++- server/helpers/custom-validators/videos.ts | 6 ++++ server/initializers/constants.ts | 11 ++++-- server/initializers/installer.ts | 20 ++++++----- server/lib/user.ts | 3 +- server/lib/video-channel.ts | 3 ++ server/models/account/account.ts | 36 +++++++++---------- server/models/account/user.ts | 21 ++++------- server/models/pod/pod.ts | 10 ------ server/models/video/video-channel.ts | 9 +++-- server/models/video/video.ts | 8 +++-- 14 files changed, 95 insertions(+), 64 deletions(-) diff --git a/server/helpers/custom-validators/activitypub/account.ts b/server/helpers/custom-validators/activitypub/account.ts index 8a7d1b7fe..acd2b8058 100644 --- a/server/helpers/custom-validators/activitypub/account.ts +++ b/server/helpers/custom-validators/activitypub/account.ts @@ -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) { diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index f049f5a8c..a94c36b51 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts @@ -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) { diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 9233a1359..8f6d50f50 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -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 }) } diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts index acc42f4a4..5787c3850 100644 --- a/server/helpers/custom-validators/video-channels.ts +++ b/server/helpers/custom-validators/video-channels.ts @@ -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 } diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 487b3d646..715119cf6 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -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 } diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 5d0d39395..e27d011fa 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -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 } diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index c617b16c9..5221b81a5 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts @@ -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 + } } // --------------------------------------------------------------------------- diff --git a/server/lib/user.ts b/server/lib/user.ts index 1094c2401..d2d599dfd 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts @@ -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) diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 459d9d4a8..13841ea33 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts @@ -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) diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 6ef29c8b7..cd6c822f1 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -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 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 diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 7390baf91..8f7c9b013 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -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 let isPasswordMatch: UserMethods.IsPasswordMatch diff --git a/server/models/pod/pod.ts b/server/models/pod/pod.ts index 7c8b49bf8..6d270ad7f 100644 --- a/server/models/pod/pod.ts +++ b/server/models/pod/pod.ts @@ -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() } diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 919ec916d..6d70f2aa2 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -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 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.') + } } } }, diff --git a/server/models/video/video.ts b/server/models/video/video.ts index ca71da375..dd73dd7ca 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -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.') + } } } }, -- 2.25.1