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)
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) {
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) {
+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 = {
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) {
isVideoTruncatedDescriptionValid,
isVideoDurationValid,
isVideoNameValid,
- isVideoTagValid
+ isVideoTagValid,
+ isVideoUrlValid
} from '../videos'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
import { isBaseActivityValid } from './misc'
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 })
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 })
}
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)
}
isVideoChannelDescriptionValid,
isVideoChannelNameValid,
isVideoChannelUUIDValid,
- checkVideoChannelExists
+ checkVideoChannelExists,
+ isVideoChannelUrlValid
}
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
return validator.isInt('' + value)
}
+function isVideoUrlValid (value: string) {
+ return isActivityPubUrlValid(value)
+}
+
function isVideoLicenceValid (value: number) {
return VIDEO_LICENCES[value] !== undefined
}
isVideoTagValid,
isRemoteVideoCategoryValid,
isRemoteVideoLicenceValid,
+ isVideoUrlValid,
isRemoteVideoLanguageValid
}
},
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
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 }
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
+ }
}
// ---------------------------------------------------------------------------
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)
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 = {
}
const videoChannel = db.VideoChannel.build(videoChannelData)
+ videoChannel.set('url', getActivityPubUrl('videoChannel', videoChannel.uuid))
+
const options = { transaction: t }
const videoChannelCreated = await videoChannel.save(options)
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
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 => {
}
},
publicKey: {
- type: DataTypes.STRING,
+ type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max),
allowNull: false,
validate: {
publicKeyValid: value => {
}
},
privateKey: {
- type: DataTypes.STRING,
+ type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max),
allowNull: false,
validate: {
privateKeyValid: value => {
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 => {
}
},
outboxUrl: {
- type: DataTypes.STRING,
+ type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
allowNull: false,
validate: {
outboxUrlValid: value => {
}
},
sharedInboxUrl: {
- type: DataTypes.STRING,
+ type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
allowNull: false,
validate: {
sharedInboxUrlValid: value => {
}
},
followersUrl: {
- type: DataTypes.STRING,
+ type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
allowNull: false,
validate: {
followersUrlValid: value => {
}
},
followingUrl: {
- type: DataTypes.STRING,
+ type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
allowNull: false,
validate: {
followingUrlValid: value => {
Account.belongsTo(models.Application, {
foreignKey: {
- name: 'userId',
+ name: 'applicationId',
allowNull: true
},
onDelete: 'cascade'
hooks: true
})
- Account.hasMany(models.AccountFollower, {
+ Account.hasMany(models.AccountFollow, {
foreignKey: {
name: 'accountId',
allowNull: false
onDelete: 'cascade'
})
- Account.hasMany(models.AccountFollower, {
+ Account.hasMany(models.AccountFollow, {
foreignKey: {
name: 'targetAccountId',
allowNull: false
attributes: [ 'sharedInboxUrl' ],
include: [
{
- model: Account['sequelize'].models.AccountFollower,
+ model: Account['sequelize'].models.AccountFollow,
where: {
targetAccountId: this.id
}
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
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
)
const classMethods = [
- associate,
-
countAll,
incrementScores,
list,
// ------------------------------ Statics ------------------------------
-function associate (models) {
- Pod.belongsToMany(models.Request, {
- foreignKey: 'podId',
- through: models.RequestToPod,
- onDelete: 'cascade'
- })
-}
-
countAll = function () {
return Pod.count()
}
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
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.')
+ }
}
}
},
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
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.')
+ }
}
}
},