Refractor validators

This commit is contained in:
Chocobozzz 2017-11-27 17:30:46 +01:00
parent fcaf1e0aa8
commit a2431b7dcb
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
33 changed files with 478 additions and 643 deletions

View File

@ -19,22 +19,22 @@ import { VideoShareInstance } from '../../models/video/video-share-interface'
const activityPubClientRouter = express.Router()
activityPubClientRouter.get('/account/:name',
executeIfActivityPub(localAccountValidator),
executeIfActivityPub(asyncMiddleware(localAccountValidator)),
executeIfActivityPub(accountController)
)
activityPubClientRouter.get('/account/:name/followers',
executeIfActivityPub(localAccountValidator),
executeIfActivityPub(asyncMiddleware(localAccountValidator)),
executeIfActivityPub(asyncMiddleware(accountFollowersController))
)
activityPubClientRouter.get('/account/:name/following',
executeIfActivityPub(localAccountValidator),
executeIfActivityPub(asyncMiddleware(localAccountValidator)),
executeIfActivityPub(asyncMiddleware(accountFollowingController))
)
activityPubClientRouter.get('/videos/watch/:id',
executeIfActivityPub(videosGetValidator),
executeIfActivityPub(asyncMiddleware(videosGetValidator)),
executeIfActivityPub(videoController)
)
@ -44,7 +44,7 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:accountId',
)
activityPubClientRouter.get('/video-channels/:id',
executeIfActivityPub(videoChannelsGetValidator),
executeIfActivityPub(asyncMiddleware(videoChannelsGetValidator)),
executeIfActivityPub(asyncMiddleware(videoChannelController))
)

View File

@ -41,7 +41,7 @@ serverFollowsRouter.post('/following',
serverFollowsRouter.delete('/following/:accountId',
authenticate,
ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
removeFollowingValidator,
asyncMiddleware(removeFollowingValidator),
asyncMiddleware(removeFollow)
)

View File

@ -43,7 +43,7 @@ usersRouter.get('/me/videos',
usersRouter.get('/me/videos/:videoId/rating',
authenticate,
usersVideoRatingValidator,
asyncMiddleware(usersVideoRatingValidator),
asyncMiddleware(getUserVideoRating)
)
@ -56,20 +56,20 @@ usersRouter.get('/',
)
usersRouter.get('/:id',
usersGetValidator,
asyncMiddleware(usersGetValidator),
getUser
)
usersRouter.post('/',
authenticate,
ensureUserHasRight(UserRight.MANAGE_USERS),
usersAddValidator,
createUserRetryWrapper
asyncMiddleware(usersAddValidator),
asyncMiddleware(createUserRetryWrapper)
)
usersRouter.post('/register',
ensureUserRegistrationAllowed,
usersRegisterValidator,
asyncMiddleware(ensureUserRegistrationAllowed),
asyncMiddleware(usersRegisterValidator),
asyncMiddleware(registerUserRetryWrapper)
)
@ -82,14 +82,14 @@ usersRouter.put('/me',
usersRouter.put('/:id',
authenticate,
ensureUserHasRight(UserRight.MANAGE_USERS),
usersUpdateValidator,
asyncMiddleware(usersUpdateValidator),
asyncMiddleware(updateUser)
)
usersRouter.delete('/:id',
authenticate,
ensureUserHasRight(UserRight.MANAGE_USERS),
usersRemoveValidator,
asyncMiddleware(usersRemoveValidator),
asyncMiddleware(removeUser)
)

View File

@ -33,7 +33,7 @@ abuseVideoRouter.get('/abuse',
)
abuseVideoRouter.post('/:id/abuse',
authenticate,
videoAbuseReportValidator,
asyncMiddleware(videoAbuseReportValidator),
asyncMiddleware(reportVideoAbuseRetryWrapper)
)

View File

@ -21,7 +21,7 @@ const blacklistRouter = express.Router()
blacklistRouter.post('/:videoId/blacklist',
authenticate,
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
videosBlacklistAddValidator,
asyncMiddleware(videosBlacklistAddValidator),
asyncMiddleware(addVideoToBlacklist)
)
@ -38,7 +38,7 @@ blacklistRouter.get('/blacklist',
blacklistRouter.delete('/:videoId/blacklist',
authenticate,
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
videosBlacklistRemoveValidator,
asyncMiddleware(videosBlacklistRemoveValidator),
asyncMiddleware(removeVideoFromBlacklistController)
)

View File

@ -3,6 +3,7 @@ import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
import { database as db } from '../../../initializers'
import { createVideoChannel } from '../../../lib'
import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update'
import {
asyncMiddleware,
authenticate,
@ -10,14 +11,13 @@ import {
paginationValidator,
setPagination,
setVideoChannelsSort,
videoChannelsGetValidator,
videoChannelsAddValidator,
videoChannelsGetValidator,
videoChannelsRemoveValidator,
videoChannelsSortValidator,
videoChannelsUpdateValidator
} from '../../../middlewares'
import { AccountInstance, VideoChannelInstance } from '../../../models'
import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update'
const videoChannelRouter = express.Router()
@ -30,7 +30,7 @@ videoChannelRouter.get('/channels',
)
videoChannelRouter.get('/accounts/:accountId/channels',
listVideoAccountChannelsValidator,
asyncMiddleware(listVideoAccountChannelsValidator),
asyncMiddleware(listVideoAccountChannels)
)
@ -42,18 +42,18 @@ videoChannelRouter.post('/channels',
videoChannelRouter.put('/channels/:id',
authenticate,
videoChannelsUpdateValidator,
asyncMiddleware(videoChannelsUpdateValidator),
updateVideoChannelRetryWrapper
)
videoChannelRouter.delete('/channels/:id',
authenticate,
videoChannelsRemoveValidator,
asyncMiddleware(videoChannelsRemoveValidator),
asyncMiddleware(removeVideoChannelRetryWrapper)
)
videoChannelRouter.get('/channels/:id',
videoChannelsGetValidator,
asyncMiddleware(videoChannelsGetValidator),
asyncMiddleware(getVideoChannel)
)

View File

@ -86,7 +86,7 @@ videosRouter.get('/',
)
videosRouter.put('/:id',
authenticate,
videosUpdateValidator,
asyncMiddleware(videosUpdateValidator),
asyncMiddleware(updateVideoRetryWrapper)
)
videosRouter.post('/upload',
@ -97,17 +97,17 @@ videosRouter.post('/upload',
)
videosRouter.get('/:id/description',
videosGetValidator,
asyncMiddleware(videosGetValidator),
asyncMiddleware(getVideoDescription)
)
videosRouter.get('/:id',
videosGetValidator,
asyncMiddleware(videosGetValidator),
getVideo
)
videosRouter.delete('/:id',
authenticate,
videosRemoveValidator,
asyncMiddleware(videosRemoveValidator),
asyncMiddleware(removeVideoRetryWrapper)
)

View File

@ -12,7 +12,7 @@ const rateVideoRouter = express.Router()
rateVideoRouter.put('/:id/rate',
authenticate,
videoRateValidator,
asyncMiddleware(videoRateValidator),
asyncMiddleware(rateVideoRetryWrapper)
)

View File

@ -1,12 +1,16 @@
import * as express from 'express'
import { CONFIG, PREVIEWS_SIZE, EMBED_SIZE } from '../initializers'
import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers'
import { oembedValidator } from '../middlewares'
import { asyncMiddleware } from '../middlewares/async'
import { VideoInstance } from '../models'
const servicesRouter = express.Router()
servicesRouter.use('/oembed', oembedValidator, generateOEmbed)
servicesRouter.use('/oembed',
asyncMiddleware(oembedValidator),
generateOEmbed
)
// ---------------------------------------------------------------------------

View File

@ -1,11 +1,12 @@
import * as express from 'express'
import { asyncMiddleware } from '../middlewares/async'
import { webfingerValidator } from '../middlewares/validators/webfinger'
import { AccountInstance } from '../models/account/account-interface'
const webfingerRouter = express.Router()
webfingerRouter.get('/.well-known/webfinger',
webfingerValidator,
asyncMiddleware(webfingerValidator),
webfingerController
)

View File

@ -1,17 +1,16 @@
import * as Bluebird from 'bluebird'
import * as express from 'express'
import { Response } from 'express'
import 'express-validator'
import * as validator from 'validator'
import { database as db } from '../../initializers'
import { AccountInstance } from '../../models'
import { logger } from '../logger'
import { isUserUsernameValid } from './users'
function isAccountNameValid (value: string) {
return isUserUsernameValid(value)
}
function checkAccountIdExists (id: number | string, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
function isAccountIdExist (id: number | string, res: Response) {
let promise: Bluebird<AccountInstance>
if (validator.isInt('' + id)) {
@ -20,36 +19,35 @@ function checkAccountIdExists (id: number | string, res: express.Response, callb
promise = db.Account.loadByUUID('' + id)
}
return checkAccountExists(promise, res, callback)
return isAccountExist(promise, res)
}
function checkLocalAccountNameExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
const p = db.Account.loadLocalByName(name)
function isLocalAccountNameExist (name: string, res: Response) {
const promise = db.Account.loadLocalByName(name)
return checkAccountExists(p, res, callback)
return isAccountExist(promise, res)
}
function checkAccountExists (p: Bluebird<AccountInstance>, res: express.Response, callback: (err: Error, account: AccountInstance) => any) {
p.then(account => {
if (!account) {
return res.status(404)
.send({ error: 'Account not found' })
.end()
}
async function isAccountExist (p: Bluebird<AccountInstance>, res: Response) {
const account = await p
res.locals.account = account
return callback(null, account)
})
.catch(err => {
logger.error('Error in account request validator.', err)
return res.sendStatus(500)
})
if (!account) {
res.status(404)
.send({ error: 'Account not found' })
.end()
return false
}
res.locals.account = account
return true
}
// ---------------------------------------------------------------------------
export {
checkAccountIdExists,
checkLocalAccountNameExists,
isAccountIdExist,
isLocalAccountNameExist,
isAccountNameValid
}

View File

@ -5,31 +5,19 @@ import { exists, isUUIDValid } from '../misc'
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
function isAccountEndpointsObjectValid (endpointObject: any) {
return isAccountSharedInboxValid(endpointObject.sharedInbox)
}
function isAccountSharedInboxValid (sharedInbox: string) {
return isActivityPubUrlValid(sharedInbox)
return isActivityPubUrlValid(endpointObject.sharedInbox)
}
function isAccountPublicKeyObjectValid (publicKeyObject: any) {
return isAccountPublicKeyIdValid(publicKeyObject.id) &&
isAccountPublicKeyOwnerValid(publicKeyObject.owner) &&
return isActivityPubUrlValid(publicKeyObject.id) &&
isActivityPubUrlValid(publicKeyObject.owner) &&
isAccountPublicKeyValid(publicKeyObject.publicKeyPem)
}
function isAccountPublicKeyIdValid (id: string) {
return isActivityPubUrlValid(id)
}
function isAccountTypeValid (type: string) {
return type === 'Person' || type === 'Application'
}
function isAccountPublicKeyOwnerValid (owner: string) {
return isActivityPubUrlValid(owner)
}
function isAccountPublicKeyValid (publicKey: string) {
return exists(publicKey) &&
typeof publicKey === 'string' &&
@ -38,34 +26,10 @@ function isAccountPublicKeyValid (publicKey: string) {
validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY)
}
function isAccountIdValid (id: string) {
return isActivityPubUrlValid(id)
}
function isAccountFollowingValid (id: string) {
return isActivityPubUrlValid(id)
}
function isAccountFollowersValid (id: string) {
return isActivityPubUrlValid(id)
}
function isAccountInboxValid (inbox: string) {
return isActivityPubUrlValid(inbox)
}
function isAccountOutboxValid (outbox: string) {
return isActivityPubUrlValid(outbox)
}
function isAccountPreferredUsernameValid (preferredUsername: string) {
return isAccountNameValid(preferredUsername)
}
function isAccountUrlValid (url: string) {
return isActivityPubUrlValid(url)
}
function isAccountPrivateKeyValid (privateKey: string) {
return exists(privateKey) &&
typeof privateKey === 'string' &&
@ -75,15 +39,15 @@ function isAccountPrivateKeyValid (privateKey: string) {
}
function isRemoteAccountValid (remoteAccount: any) {
return isAccountIdValid(remoteAccount.id) &&
return isActivityPubUrlValid(remoteAccount.id) &&
isUUIDValid(remoteAccount.uuid) &&
isAccountTypeValid(remoteAccount.type) &&
isAccountFollowingValid(remoteAccount.following) &&
isAccountFollowersValid(remoteAccount.followers) &&
isAccountInboxValid(remoteAccount.inbox) &&
isAccountOutboxValid(remoteAccount.outbox) &&
isActivityPubUrlValid(remoteAccount.following) &&
isActivityPubUrlValid(remoteAccount.followers) &&
isActivityPubUrlValid(remoteAccount.inbox) &&
isActivityPubUrlValid(remoteAccount.outbox) &&
isAccountPreferredUsernameValid(remoteAccount.preferredUsername) &&
isAccountUrlValid(remoteAccount.url) &&
isActivityPubUrlValid(remoteAccount.url) &&
isAccountPublicKeyObjectValid(remoteAccount.publicKey) &&
isAccountEndpointsObjectValid(remoteAccount.endpoints)
}
@ -113,19 +77,10 @@ function isAccountAcceptActivityValid (activity: any) {
export {
isAccountEndpointsObjectValid,
isAccountSharedInboxValid,
isAccountPublicKeyObjectValid,
isAccountPublicKeyIdValid,
isAccountTypeValid,
isAccountPublicKeyOwnerValid,
isAccountPublicKeyValid,
isAccountIdValid,
isAccountFollowingValid,
isAccountFollowersValid,
isAccountInboxValid,
isAccountOutboxValid,
isAccountPreferredUsernameValid,
isAccountUrlValid,
isAccountPrivateKeyValid,
isRemoteAccountValid,
isAccountFollowingCountValid,

View File

@ -8,7 +8,6 @@ import {
isVideoNSFWValid,
isVideoTagValid,
isVideoTruncatedDescriptionValid,
isVideoUrlValid,
isVideoViewsValid
} from '../videos'
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
@ -77,12 +76,11 @@ export {
function setValidRemoteTags (video: any) {
if (Array.isArray(video.tag) === false) return false
const newTag = video.tag.filter(t => {
video.tag = video.tag.filter(t => {
return t.type === 'Hashtag' &&
isVideoTagValid(t.name)
})
video.tag = newTag
return true
}
@ -96,7 +94,7 @@ function isRemoteVideoContentValid (mediaType: string, content: string) {
function isRemoteVideoIconValid (icon: any) {
return icon.type === 'Image' &&
isVideoUrlValid(icon.url) &&
isActivityPubUrlValid(icon.url) &&
icon.mediaType === 'image/jpeg' &&
validator.isInt(icon.width + '', { min: 0 }) &&
validator.isInt(icon.height + '', { min: 0 })
@ -105,8 +103,7 @@ function isRemoteVideoIconValid (icon: any) {
function setValidRemoteVideoUrls (video: any) {
if (Array.isArray(video.url) === false) return false
const newUrl = video.url.filter(u => isRemoteVideoUrlValid(u))
video.url = newUrl
video.url = video.url.filter(u => isRemoteVideoUrlValid(u))
return true
}
@ -115,13 +112,13 @@ function isRemoteVideoUrlValid (url: any) {
return url.type === 'Link' &&
(
ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.indexOf(url.mimeType) !== -1 &&
isVideoUrlValid(url.url) &&
isActivityPubUrlValid(url.url) &&
validator.isInt(url.width + '', { min: 0 }) &&
validator.isInt(url.size + '', { min: 0 })
) ||
(
ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.indexOf(url.mimeType) !== -1 &&
isVideoUrlValid(url.url) &&
isActivityPubUrlValid(url.url) &&
validator.isInt(url.width + '', { min: 0 })
) ||
(

View File

@ -1,21 +1,13 @@
import * as Bluebird from 'bluebird'
import * as express from 'express'
import 'express-validator'
import 'multer'
import * as validator from 'validator'
import { CONSTRAINTS_FIELDS, database as db } from '../../initializers'
import { VideoChannelInstance } from '../../models'
import { logger } from '../logger'
import { isActivityPubUrlValid } from './index'
import { exists } from './misc'
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)
}
@ -24,31 +16,7 @@ function isVideoChannelNameValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.NAME)
}
function checkVideoChannelExists (id: string, res: express.Response, callback: () => void) {
let promise: Bluebird<VideoChannelInstance>
if (validator.isInt(id)) {
promise = db.VideoChannel.loadAndPopulateAccount(+id)
} else { // UUID
promise = db.VideoChannel.loadByUUIDAndPopulateAccount(id)
}
promise.then(videoChannel => {
if (!videoChannel) {
return res.status(404)
.json({ error: 'Video channel not found' })
.end()
}
res.locals.videoChannel = videoChannel
callback()
})
.catch(err => {
logger.error('Error in video channel request validator.', err)
return res.sendStatus(500)
})
}
async function isVideoChannelExistsPromise (id: string, res: express.Response) {
async function isVideoChannelExist (id: string, res: express.Response) {
let videoChannel: VideoChannelInstance
if (validator.isInt(id)) {
videoChannel = await db.VideoChannel.loadAndPopulateAccount(+id)
@ -72,8 +40,6 @@ async function isVideoChannelExistsPromise (id: string, res: express.Response) {
export {
isVideoChannelDescriptionValid,
checkVideoChannelExists,
isVideoChannelNameValid,
isVideoChannelExistsPromise,
isVideoChannelUrlValid
isVideoChannelExist
}

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird'
import { Response } from 'express'
import 'express-validator'
import { values } from 'lodash'
@ -6,12 +5,10 @@ import 'multer'
import * as validator from 'validator'
import { VideoRateType } from '../../../shared'
import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_RATE_TYPES } from '../../initializers'
import { VIDEO_PRIVACIES } from '../../initializers/constants'
import { database as db } from '../../initializers/database'
import { VideoInstance } from '../../models/video/video-interface'
import { logger } from '../logger'
import { isActivityPubUrlValid } from './activitypub/misc'
import { exists, isArray } from './misc'
import { VIDEO_PRIVACIES } from '../../initializers/constants'
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
@ -20,10 +17,6 @@ function isVideoCategoryValid (value: number) {
return VIDEO_CATEGORIES[value] !== undefined
}
function isVideoUrlValid (value: string) {
return isActivityPubUrlValid(value)
}
function isVideoLicenceValid (value: number) {
return VIDEO_LICENCES[value] !== undefined
}
@ -106,31 +99,7 @@ function isVideoFileSizeValid (value: string) {
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
}
function checkVideoExists (id: string, res: Response, callback: () => void) {
let promise: Bluebird<VideoInstance>
if (validator.isInt(id)) {
promise = db.Video.loadAndPopulateAccountAndServerAndTags(+id)
} else { // UUID
promise = db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(id)
}
promise.then(video => {
if (!video) {
return res.status(404)
.json({ error: 'Video not found' })
.end()
}
res.locals.video = video
callback()
})
.catch(err => {
logger.error('Error in video request validator.', err)
return res.sendStatus(500)
})
}
async function isVideoExistsPromise (id: string, res: Response) {
async function isVideoExist (id: string, res: Response) {
let video: VideoInstance
if (validator.isInt(id)) {
@ -169,10 +138,8 @@ export {
isVideoRatingTypeValid,
isVideoDurationValid,
isVideoTagValid,
isVideoUrlValid,
isVideoPrivacyValid,
isVideoFileResolutionValid,
isVideoFileSizeValid,
checkVideoExists,
isVideoExistsPromise
isVideoExist
}

View File

@ -1,9 +1,12 @@
import { Request, Response, NextFunction, RequestHandler } from 'express'
import { eachSeries } from 'async'
import { NextFunction, Request, RequestHandler, Response } from 'express'
// Syntactic sugar to avoid try/catch in express controllers
// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
function asyncMiddleware (fun: RequestHandler | RequestHandler[]) {
export type RequestPromiseHandler = (req: Request, res: Response, next: NextFunction) => Promise<any>
function asyncMiddleware (fun: RequestPromiseHandler | RequestPromiseHandler[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (Array.isArray(fun) === true) {
return eachSeries(fun as RequestHandler[], (f, cb) => {

View File

@ -1,18 +1,19 @@
import * as express from 'express'
import { param } from 'express-validator/check'
import { logger } from '../../helpers'
import { checkLocalAccountNameExists, isAccountNameValid } from '../../helpers/custom-validators/accounts'
import { checkErrors } from './utils'
import { logger, isLocalAccountNameExist } from '../../helpers'
import { isAccountNameValid } from '../../helpers/custom-validators/accounts'
import { areValidationErrors } from './utils'
const localAccountValidator = [
param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking localAccountValidator parameters', { parameters: req.params })
checkErrors(req, res, () => {
checkLocalAccountNameExists(req.params.name, res, next)
})
if (areValidationErrors(req, res)) return
if (!await isLocalAccountNameExist(req.params.name, res)) return
return next()
}
]

View File

@ -1,7 +1,7 @@
import * as express from 'express'
import { body } from 'express-validator/check'
import { isRootActivityValid, logger } from '../../../helpers'
import { checkErrors } from '../utils'
import { areValidationErrors } from '../utils'
const activityPubValidator = [
body('').custom((value, { req }) => isRootActivityValid(req.body)),
@ -9,7 +9,9 @@ const activityPubValidator = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking activity pub parameters', { parameters: req.body })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]

View File

@ -1,14 +1,7 @@
import { body } from 'express-validator/check'
import * as express from 'express'
import {
logger,
isDateValid,
isSignatureTypeValid,
isSignatureCreatorValid,
isSignatureValueValid
} from '../../../helpers'
import { checkErrors } from '../utils'
import { body } from 'express-validator/check'
import { isDateValid, isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid, logger } from '../../../helpers'
import { areValidationErrors } from '../utils'
const signatureValidator = [
body('signature.type').custom(isSignatureTypeValid).withMessage('Should have a valid signature type'),
@ -19,7 +12,9 @@ const signatureValidator = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking activitypub signature parameter', { parameters: { signature: req.body.signature } })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]

View File

@ -4,7 +4,7 @@ import { isTestInstance } from '../../helpers/core-utils'
import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
import { logger } from '../../helpers/logger'
import { CONFIG, database as db } from '../../initializers'
import { checkErrors } from './utils'
import { areValidationErrors } from './utils'
import { getServerAccount } from '../../helpers/utils'
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
@ -23,34 +23,30 @@ const followValidator = [
logger.debug('Checking follow parameters', { parameters: req.body })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]
const removeFollowingValidator = [
param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking unfollow parameters', { parameters: req.params })
checkErrors(req, res, async () => {
try {
const serverAccount = await getServerAccount()
const follow = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId)
if (areValidationErrors(req, res)) return
if (!follow) {
return res.status(404)
.end()
}
const serverAccount = await getServerAccount()
const follow = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId)
res.locals.follow = follow
if (!follow) {
return res.status(404)
.end()
}
return next()
} catch (err) {
logger.error('Error in remove following validator.', err)
return res.sendStatus(500)
}
})
res.locals.follow = follow
return next()
}
]

View File

@ -1,15 +1,10 @@
import { query } from 'express-validator/check'
import * as express from 'express'
import { query } from 'express-validator/check'
import { join } from 'path'
import { checkErrors } from './utils'
import { isIdOrUUIDValid, isTestInstance, logger } from '../../helpers'
import { CONFIG } from '../../initializers'
import {
logger,
isTestInstance,
checkVideoExists,
isIdOrUUIDValid
} from '../../helpers'
import { areValidationErrors } from './utils'
import { isVideoExist } from '../../helpers/custom-validators/videos'
const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/'
const videoWatchRegex = new RegExp('([^/]+)$')
@ -29,33 +24,35 @@ const oembedValidator = [
query('maxheight').optional().isInt().withMessage('Should have a valid max height'),
query('format').optional().isIn([ 'xml', 'json' ]).withMessage('Should have a valid format'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking oembed parameters', { parameters: req.query })
checkErrors(req, res, () => {
if (req.query.format !== undefined && req.query.format !== 'json') {
return res.status(501)
.json({ error: 'Requested format is not implemented on server.' })
.end()
}
if (areValidationErrors(req, res)) return
const startIsOk = req.query.url.startsWith(urlShouldStartWith)
const matches = videoWatchRegex.exec(req.query.url)
if (startIsOk === false || matches === null) {
return res.status(400)
.json({ error: 'Invalid url.' })
.end()
}
if (req.query.format !== undefined && req.query.format !== 'json') {
return res.status(501)
.json({ error: 'Requested format is not implemented on server.' })
.end()
}
const videoId = matches[1]
if (isIdOrUUIDValid(videoId) === false) {
return res.status(400)
.json({ error: 'Invalid video id.' })
.end()
}
const startIsOk = req.query.url.startsWith(urlShouldStartWith)
const matches = videoWatchRegex.exec(req.query.url)
if (startIsOk === false || matches === null) {
return res.status(400)
.json({ error: 'Invalid url.' })
.end()
}
checkVideoExists(videoId, res, next)
})
const videoId = matches[1]
if (isIdOrUUIDValid(videoId) === false) {
return res.status(400)
.json({ error: 'Invalid video id.' })
.end()
}
if (!await isVideoExist(videoId, res)) return
return next()
}
]

View File

@ -1,8 +1,7 @@
import { query } from 'express-validator/check'
import * as express from 'express'
import { checkErrors } from './utils'
import { query } from 'express-validator/check'
import { logger } from '../../helpers'
import { areValidationErrors } from './utils'
const paginationValidator = [
query('start').optional().isInt().withMessage('Should have a number start'),
@ -11,7 +10,9 @@ const paginationValidator = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking pagination parameters', { parameters: req.query })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]

View File

@ -1,9 +1,9 @@
import { query } from 'express-validator/check'
import * as express from 'express'
import { checkErrors } from './utils'
import { logger } from '../../helpers'
import { SORTABLE_COLUMNS } from '../../initializers'
import { areValidationErrors } from './utils'
// Initialize constants here for better performances
const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
@ -43,7 +43,9 @@ function checkSort (sortableColumns: string[]) {
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking sort parameters', { parameters: req.query })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]
}

View File

@ -1,22 +1,19 @@
import { body, param } from 'express-validator/check'
import 'express-validator'
import * as express from 'express'
import * as Promise from 'bluebird'
import * as validator from 'validator'
import { database as db } from '../../initializers/database'
import { checkErrors } from './utils'
import 'express-validator'
import { body, param } from 'express-validator/check'
import {
isSignupAllowed,
logger,
isUserUsernameValid,
isUserPasswordValid,
isUserVideoQuotaValid,
isUserDisplayNSFWValid,
isIdOrUUIDValid,
isUserRoleValid
isSignupAllowed,
isUserDisplayNSFWValid,
isUserPasswordValid,
isUserRoleValid,
isUserUsernameValid,
isUserVideoQuotaValid,
logger
} from '../../helpers'
import { UserInstance, VideoInstance } from '../../models'
import { isVideoExist } from '../../helpers/custom-validators/videos'
import { database as db } from '../../initializers/database'
import { areValidationErrors } from './utils'
const usersAddValidator = [
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@ -25,12 +22,13 @@ const usersAddValidator = [
body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersAdd parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkUserDoesNotAlreadyExist(req.body.username, req.body.email, res, next)
})
if (areValidationErrors(req, res)) return
if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
return next()
}
]
@ -39,37 +37,33 @@ const usersRegisterValidator = [
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
body('email').isEmail().withMessage('Should have a valid email'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersRegister parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkUserDoesNotAlreadyExist(req.body.username, req.body.email, res, next)
})
if (areValidationErrors(req, res)) return
if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
return next()
}
]
const usersRemoveValidator = [
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersRemove parameters', { parameters: req.params })
checkErrors(req, res, () => {
checkUserExists(req.params.id, res, (err, user) => {
if (err) {
logger.error('Error in usersRemoveValidator.', err)
return res.sendStatus(500)
}
if (areValidationErrors(req, res)) return
if (!await checkUserIdExist(req.params.id, res)) return
if (user.username === 'root') {
return res.status(400)
.send({ error: 'Cannot remove the root user' })
.end()
}
const user = res.locals.user
if (user.username === 'root') {
return res.status(400)
.send({ error: 'Cannot remove the root user' })
.end()
}
return next()
})
})
return next()
}
]
@ -79,12 +73,13 @@ const usersUpdateValidator = [
body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersUpdate parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkUserExists(req.params.id, res, next)
})
if (areValidationErrors(req, res)) return
if (!await checkUserIdExist(req.params.id, res)) return
return next()
}
]
@ -97,64 +92,48 @@ const usersUpdateMeValidator = [
// TODO: Add old password verification
logger.debug('Checking usersUpdateMe parameters', { parameters: req.body })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]
const usersGetValidator = [
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
checkErrors(req, res, () => {
checkUserExists(req.params.id, res, next)
})
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersGet parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
if (!await checkUserIdExist(req.params.id, res)) return
return next()
}
]
const usersVideoRatingValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
checkErrors(req, res, () => {
let videoPromise: Promise<VideoInstance>
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return
if (validator.isUUID(req.params.videoId)) {
videoPromise = db.Video.loadByUUID(req.params.videoId)
} else {
videoPromise = db.Video.load(req.params.videoId)
}
videoPromise
.then(video => {
if (!video) {
return res.status(404)
.json({ error: 'Video not found' })
.end()
}
return next()
})
.catch(err => {
logger.error('Error in user request validator.', err)
return res.sendStatus(500)
})
})
return next()
}
]
const ensureUserRegistrationAllowed = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
isSignupAllowed().then(allowed => {
if (allowed === false) {
return res.status(403)
.send({ error: 'User registration is not enabled or user limit is reached.' })
.end()
}
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
const allowed = await isSignupAllowed()
if (allowed === false) {
return res.status(403)
.send({ error: 'User registration is not enabled or user limit is reached.' })
.end()
}
return next()
})
return next()
}
]
@ -173,37 +152,30 @@ export {
// ---------------------------------------------------------------------------
function checkUserExists (id: number, res: express.Response, callback: (err: Error, user: UserInstance) => void) {
db.User.loadById(id)
.then(user => {
if (!user) {
return res.status(404)
.send({ error: 'User not found' })
.end()
}
async function checkUserIdExist (id: number, res: express.Response) {
const user = await db.User.loadById(id)
res.locals.user = user
return callback(null, user)
})
.catch(err => {
logger.error('Error in user request validator.', err)
return res.sendStatus(500)
})
if (!user) {
res.status(404)
.send({ error: 'User not found' })
.end()
return false
}
res.locals.user = user
return true
}
function checkUserDoesNotAlreadyExist (username: string, email: string, res: express.Response, callback: () => void) {
db.User.loadByUsernameOrEmail(username, email)
.then(user => {
if (user) {
return res.status(409)
.send({ error: 'User with this username of email already exists.' })
.end()
}
async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
const user = await db.User.loadByUsernameOrEmail(username, email)
return callback()
})
.catch(err => {
logger.error('Error in usersAdd request validator.', err)
return res.sendStatus(500)
})
if (user) {
res.status(409)
.send({ error: 'User with this username of email already exists.' })
.end()
return false
}
return true
}

View File

@ -1,19 +1,8 @@
import { validationResult } from 'express-validator/check'
import * as express from 'express'
import { validationResult } from 'express-validator/check'
import { logger } from '../../helpers'
function checkErrors (req: express.Request, res: express.Response, next: express.NextFunction) {
const errors = validationResult(req)
if (!errors.isEmpty()) {
logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() })
return res.status(400).json({ errors: errors.mapped() })
}
return next()
}
function areValidationErrors (req: express.Request, res: express.Response) {
const errors = validationResult(req)
@ -30,6 +19,5 @@ function areValidationErrors (req: express.Request, res: express.Response) {
// ---------------------------------------------------------------------------
export {
checkErrors,
areValidationErrors
}

View File

@ -1,35 +1,36 @@
import { param } from 'express-validator/check'
import * as express from 'express'
import { param } from 'express-validator/check'
import { isIdOrUUIDValid, logger } from '../../helpers'
import { isVideoExist } from '../../helpers/custom-validators/videos'
import { database as db } from '../../initializers/database'
import { checkErrors } from './utils'
import { logger, isIdOrUUIDValid, checkVideoExists } from '../../helpers'
import { VideoInstance } from '../../models/video/video-interface'
import { areValidationErrors } from './utils'
const videosBlacklistRemoveValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
checkErrors(req, res, () => {
checkVideoExists(req.params.videoId, res, () => {
checkVideoIsBlacklisted(req, res, next)
})
})
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return
if (!await checkVideoIsBlacklisted(res.locals.video, res)) return
return next()
}
]
const videosBlacklistAddValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosBlacklist parameters', { parameters: req.params })
checkErrors(req, res, () => {
checkVideoExists(req.params.videoId, res, () => {
checkVideoIsBlacklistable(req, res, next)
})
})
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return
if (!checkVideoIsBlacklistable(res.locals.video, res)) return
return next()
}
]
@ -41,27 +42,27 @@ export {
}
// ---------------------------------------------------------------------------
function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) {
if (res.locals.video.isOwned() === true) {
return res.status(403)
function checkVideoIsBlacklistable (video: VideoInstance, res: express.Response) {
if (video.isOwned() === true) {
res.status(403)
.json({ error: 'Cannot blacklist a local video' })
.end()
return false
}
callback()
return true
}
function checkVideoIsBlacklisted (req: express.Request, res: express.Response, callback: () => void) {
db.BlacklistedVideo.loadByVideoId(res.locals.video.id)
.then(blacklistedVideo => {
if (!blacklistedVideo) return res.status(404).send('Blacklisted video not found')
async function checkVideoIsBlacklisted (video: VideoInstance, res: express.Response) {
const blacklistedVideo = await db.BlacklistedVideo.loadByVideoId(video.id)
if (!blacklistedVideo) {
res.status(404)
.send('Blacklisted video not found')
res.locals.blacklistedVideo = blacklistedVideo
return false
}
callback()
})
.catch(err => {
logger.error('Error in blacklistRemove request validator', { error: err })
return res.sendStatus(500)
})
res.locals.blacklistedVideo = blacklistedVideo
return true
}

View File

@ -1,29 +1,30 @@
import * as express from 'express'
import { body, param } from 'express-validator/check'
import { UserRight } from '../../../shared'
import { checkAccountIdExists } from '../../helpers/custom-validators/accounts'
import { isIdValid } from '../../helpers/custom-validators/misc'
import {
checkVideoChannelExists,
isVideoChannelDescriptionValid,
isVideoChannelExistsPromise,
isVideoChannelExist,
isVideoChannelNameValid
} from '../../helpers/custom-validators/video-channels'
import { isIdOrUUIDValid } from '../../helpers/index'
import { logger } from '../../helpers/logger'
import { database as db } from '../../initializers'
import { UserInstance } from '../../models'
import { areValidationErrors, checkErrors } from './utils'
import { areValidationErrors } from './utils'
import { isAccountIdExist } from '../../helpers/custom-validators/accounts'
import { VideoChannelInstance } from '../../models/video/video-channel-interface'
const listVideoAccountChannelsValidator = [
param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkAccountIdExists(req.params.accountId, res, next)
})
if (areValidationErrors(req, res)) return
if (!await isAccountIdExist(req.params.accountId, res)) return
return next()
}
]
@ -34,7 +35,9 @@ const videoChannelsAddValidator = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]
@ -43,56 +46,56 @@ const videoChannelsUpdateValidator = [
body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkVideoChannelExists(req.params.id, res, () => {
// We need to make additional checks
if (res.locals.videoChannel.isOwned() === false) {
return res.status(403)
.json({ error: 'Cannot update video channel of another server' })
.end()
}
if (areValidationErrors(req, res)) return
if (!await isVideoChannelExist(req.params.id, res)) return
if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) {
return res.status(403)
.json({ error: 'Cannot update video channel of another user' })
.end()
}
// We need to make additional checks
if (res.locals.videoChannel.isOwned() === false) {
return res.status(403)
.json({ error: 'Cannot update video channel of another server' })
.end()
}
next()
})
})
if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) {
return res.status(403)
.json({ error: 'Cannot update video channel of another user' })
.end()
}
return next()
}
]
const videoChannelsRemoveValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
checkErrors(req, res, () => {
checkVideoChannelExists(req.params.id, res, () => {
// Check if the user who did the request is able to delete the video
checkUserCanDeleteVideoChannel(res, () => {
checkVideoChannelIsNotTheLastOne(res, next)
})
})
})
if (areValidationErrors(req, res)) return
if (!await isVideoChannelExist(req.params.id, res)) return
// Check if the user who did the request is able to delete the video
if (!checkUserCanDeleteVideoChannel(res.locals.user, res.locals.videoChannel, res)) return
if (!await checkVideoChannelIsNotTheLastOne(res)) return
return next()
}
]
const videoChannelsGetValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsGet parameters', { parameters: req.params })
checkErrors(req, res, () => {
checkVideoChannelExists(req.params.id, res, next)
})
if (areValidationErrors(req, res)) return
if (!await isVideoChannelExist(req.params.id, res)) return
return next()
}
]
@ -104,7 +107,7 @@ const videoChannelsShareValidator = [
logger.debug('Checking videoChannelShare parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await isVideoChannelExistsPromise(req.params.id, res)) return
if (!await isVideoChannelExist(req.params.id, res)) return
const share = await db.VideoChannelShare.load(res.locals.video.id, req.params.accountId)
if (!share) {
@ -131,38 +134,40 @@ export {
// ---------------------------------------------------------------------------
function checkUserCanDeleteVideoChannel (res: express.Response, callback: () => void) {
const user: UserInstance = res.locals.oauth.token.User
function checkUserCanDeleteVideoChannel (user: UserInstance, videoChannel: VideoChannelInstance, res: express.Response) {
// Retrieve the user who did the request
if (res.locals.videoChannel.isOwned() === false) {
return res.status(403)
if (videoChannel.isOwned() === false) {
res.status(403)
.json({ error: 'Cannot remove video channel of another server.' })
.end()
return false
}
// Check if the user can delete the video channel
// The user can delete it if s/he is an admin
// Or if s/he is the video channel's account
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && res.locals.videoChannel.Account.userId !== user.id) {
return res.status(403)
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) {
res.status(403)
.json({ error: 'Cannot remove video channel of another user' })
.end()
return false
}
// If we reach this comment, we can delete the video
callback()
return true
}
function checkVideoChannelIsNotTheLastOne (res: express.Response, callback: () => void) {
db.VideoChannel.countByAccount(res.locals.oauth.token.User.Account.id)
.then(count => {
if (count <= 1) {
return res.status(409)
.json({ error: 'Cannot remove the last channel of this user' })
.end()
}
async function checkVideoChannelIsNotTheLastOne (res: express.Response) {
const count = await db.VideoChannel.countByAccount(res.locals.oauth.token.User.Account.id)
callback()
})
if (count <= 1) {
res.status(409)
.json({ error: 'Cannot remove the last channel of this user' })
.end()
return false
}
return true
}

View File

@ -3,11 +3,11 @@ import { body, param, query } from 'express-validator/check'
import { UserRight, VideoPrivacy } from '../../../shared'
import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
import {
checkVideoExists,
isVideoAbuseReasonValid,
isVideoCategoryValid,
isVideoDescriptionValid,
isVideoDurationValid,
isVideoExist,
isVideoFile,
isVideoLanguageValid,
isVideoLicenceValid,
@ -20,12 +20,11 @@ import {
import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
import { logger } from '../../helpers/logger'
import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers'
import { database as db } from '../../initializers/database'
import { UserInstance } from '../../models/account/user-interface'
import { VideoInstance } from '../../models/video/video-interface'
import { authenticate } from '../oauth'
import { areValidationErrors, checkErrors } from './utils'
import { isVideoExistsPromise } from '../../helpers/index'
import { areValidationErrors } from './utils'
const videosAddValidator = [
body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
@ -42,68 +41,58 @@ const videosAddValidator = [
body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
checkErrors(req, res, () => {
const videoFile: Express.Multer.File = req.files['videofile'][0]
const user = res.locals.oauth.token.User
if (areValidationErrors(req, res)) return
return db.VideoChannel.loadByIdAndAccount(req.body.channelId, user.Account.id)
.then(videoChannel => {
if (!videoChannel) {
res.status(400)
.json({ error: 'Unknown video video channel for this account.' })
.end()
const videoFile: Express.Multer.File = req.files['videofile'][0]
const user = res.locals.oauth.token.User
return undefined
}
const videoChannel = await db.VideoChannel.loadByIdAndAccount(req.body.channelId, user.Account.id)
if (!videoChannel) {
res.status(400)
.json({ error: 'Unknown video video channel for this account.' })
.end()
res.locals.videoChannel = videoChannel
return
}
return user.isAbleToUploadVideo(videoFile)
})
.then(isAble => {
if (isAble === false) {
res.status(403)
.json({ error: 'The user video quota is exceeded with this video.' })
.end()
res.locals.videoChannel = videoChannel
return undefined
}
const isAble = await user.isAbleToUploadVideo(videoFile)
if (isAble === false) {
res.status(403)
.json({ error: 'The user video quota is exceeded with this video.' })
.end()
return getDurationFromVideoFile(videoFile.path)
.catch(err => {
logger.error('Invalid input file in videosAddValidator.', err)
res.status(400)
.json({ error: 'Invalid input file.' })
.end()
return
}
return undefined
})
})
.then(duration => {
// Previous test failed, abort
if (duration === undefined) return undefined
let duration: number
if (!isVideoDurationValid('' + duration)) {
return res.status(400)
.json({
error: 'Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).'
})
.end()
}
try {
duration = await getDurationFromVideoFile(videoFile.path)
} catch (err) {
logger.error('Invalid input file in videosAddValidator.', err)
res.status(400)
.json({ error: 'Invalid input file.' })
.end()
videoFile['duration'] = duration
next()
})
.catch(err => {
logger.error('Error in video add validator', err)
res.sendStatus(500)
return
}
return undefined
})
})
if (!isVideoDurationValid('' + duration)) {
return res.status(400)
.json({
error: 'Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).'
})
.end()
}
videoFile['duration'] = duration
return next()
}
]
@ -118,61 +107,59 @@ const videosUpdateValidator = [
body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosUpdate parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkVideoExists(req.params.id, res, () => {
const video = res.locals.video
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.id, res)) return
// We need to make additional checks
if (video.isOwned() === false) {
return res.status(403)
.json({ error: 'Cannot update video of another server' })
.end()
}
const video = res.locals.video
if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
return res.status(403)
.json({ error: 'Cannot update video of another user' })
.end()
}
// We need to make additional checks
if (video.isOwned() === false) {
return res.status(403)
.json({ error: 'Cannot update video of another server' })
.end()
}
if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
return res.status(409)
.json({ error: 'Cannot set "private" a video that was not private anymore.' })
.end()
}
if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
return res.status(403)
.json({ error: 'Cannot update video of another user' })
.end()
}
next()
})
})
if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
return res.status(409)
.json({ error: 'Cannot set "private" a video that was not private anymore.' })
.end()
}
return next()
}
]
const videosGetValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosGet parameters', { parameters: req.params })
checkErrors(req, res, () => {
checkVideoExists(req.params.id, res, () => {
const video = res.locals.video
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.id, res)) return
// Video is not private, anyone can access it
if (video.privacy !== VideoPrivacy.PRIVATE) return next()
const video = res.locals.video
authenticate(req, res, () => {
if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
return res.status(403)
.json({ error: 'Cannot get this private video of another user' })
.end()
}
// Video is not private, anyone can access it
if (video.privacy !== VideoPrivacy.PRIVATE) return next()
next()
})
})
authenticate(req, res, () => {
if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
return res.status(403)
.json({ error: 'Cannot get this private video of another user' })
.end()
}
return next()
})
}
]
@ -180,17 +167,16 @@ const videosGetValidator = [
const videosRemoveValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosRemove parameters', { parameters: req.params })
checkErrors(req, res, () => {
checkVideoExists(req.params.id, res, () => {
// Check if the user who did the request is able to delete the video
checkUserCanDeleteVideo(res.locals.oauth.token.User, res, () => {
next()
})
})
})
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.id, res)) return
// Check if the user who did the request is able to delete the video
if (!checkUserCanDeleteVideo(res.locals.oauth.token.User, res.locals.video, res)) return
return next()
}
]
@ -201,7 +187,9 @@ const videosSearchValidator = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosSearch parameters', { parameters: req.params })
checkErrors(req, res, next)
if (areValidationErrors(req, res)) return
return next()
}
]
@ -209,12 +197,13 @@ const videoAbuseReportValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkVideoExists(req.params.id, res, next)
})
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.id, res)) return
return next()
}
]
@ -222,12 +211,13 @@ const videoRateValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoRate parameters', { parameters: req.body })
checkErrors(req, res, () => {
checkVideoExists(req.params.id, res, next)
})
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.id, res)) return
return next()
}
]
@ -239,7 +229,7 @@ const videosShareValidator = [
logger.debug('Checking videoShare parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await isVideoExistsPromise(req.params.id, res)) return
if (!await isVideoExist(req.params.id, res)) return
const share = await db.VideoShare.load(req.params.accountId, res.locals.video.id)
if (!share) {
@ -248,7 +238,6 @@ const videosShareValidator = [
}
res.locals.videoShare = share
return next()
}
]
@ -270,24 +259,25 @@ export {
// ---------------------------------------------------------------------------
function checkUserCanDeleteVideo (user: UserInstance, res: express.Response, callback: () => void) {
function checkUserCanDeleteVideo (user: UserInstance, video: VideoInstance, res: express.Response) {
// Retrieve the user who did the request
if (res.locals.video.isOwned() === false) {
return res.status(403)
if (video.isOwned() === false) {
res.status(403)
.json({ error: 'Cannot remove video of another server, blacklist it' })
.end()
return false
}
// Check if the user can delete the video
// The user can delete it if s/he is an admin
// Or if s/he is the video's account
const account = res.locals.video.VideoChannel.Account
const account = video.VideoChannel.Account
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO) === false && account.userId !== user.id) {
return res.status(403)
res.status(403)
.json({ error: 'Cannot remove video of another user' })
.end()
return false
}
// If we reach this comment, we can delete the video
callback()
return true
}

View File

@ -1,37 +1,31 @@
import * as express from 'express'
import { query } from 'express-validator/check'
import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger'
import { database as db } from '../../initializers'
import { checkErrors } from './utils'
import { logger } from '../../helpers/logger'
import { database as db } from '../../initializers'
import { areValidationErrors } from './utils'
const webfingerValidator = [
query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking webfinger parameters', { parameters: req.query })
checkErrors(req, res, () => {
// Remove 'acct:' from the beginning of the string
const nameWithHost = req.query.resource.substr(5)
const [ name ] = nameWithHost.split('@')
if (areValidationErrors(req, res)) return
db.Account.loadLocalByName(name)
.then(account => {
if (!account) {
return res.status(404)
.send({ error: 'Account not found' })
.end()
}
// Remove 'acct:' from the beginning of the string
const nameWithHost = req.query.resource.substr(5)
const [ name ] = nameWithHost.split('@')
res.locals.account = account
return next()
})
.catch(err => {
logger.error('Error in webfinger validator.', err)
return res.sendStatus(500)
})
})
const account = await db.Account.loadLocalByName(name)
if (!account) {
return res.status(404)
.send({ error: 'Account not found' })
.end()
}
res.locals.account = account
return next()
}
]

View File

@ -2,17 +2,12 @@ import * as Sequelize from 'sequelize'
import {
activityPubContextify,
isAccountFollowersCountValid,
isAccountFollowersValid,
isAccountFollowingCountValid,
isAccountFollowingValid,
isAccountInboxValid,
isAccountOutboxValid,
isAccountPrivateKeyValid,
isAccountPublicKeyValid,
isAccountSharedInboxValid,
isAccountUrlValid,
isUserUsernameValid
} from '../../helpers'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
@ -61,7 +56,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
allowNull: false,
validate: {
urlValid: value => {
const res = isAccountUrlValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('URL is not valid.')
}
}
@ -111,7 +106,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
allowNull: false,
validate: {
inboxUrlValid: value => {
const res = isAccountInboxValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('Inbox URL is not valid.')
}
}
@ -121,7 +116,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
allowNull: false,
validate: {
outboxUrlValid: value => {
const res = isAccountOutboxValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('Outbox URL is not valid.')
}
}
@ -131,7 +126,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
allowNull: false,
validate: {
sharedInboxUrlValid: value => {
const res = isAccountSharedInboxValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('Shared inbox URL is not valid.')
}
}
@ -141,7 +136,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
allowNull: false,
validate: {
followersUrlValid: value => {
const res = isAccountFollowersValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('Followers URL is not valid.')
}
}
@ -151,7 +146,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
allowNull: false,
validate: {
followingUrlValid: value => {
const res = isAccountFollowingValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('Following URL is not valid.')
}
}

View File

@ -1,6 +1,5 @@
import * as Sequelize from 'sequelize'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers'
import { isVideoChannelUrlValid } from '../../helpers/custom-validators/video-channels'
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete'
@ -8,6 +7,7 @@ import { addMethodsToModel, getSort } from '../utils'
import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface'
import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
import { activityPubCollection } from '../../helpers/activitypub'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
@ -66,7 +66,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
allowNull: false,
validate: {
urlValid: value => {
const res = isVideoChannelUrlValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('Video channel URL is not valid.')
}
}

View File

@ -7,7 +7,18 @@ import * as Sequelize from 'sequelize'
import { VideoPrivacy, VideoResolution } from '../../../shared'
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
import { activityPubCollection } from '../../helpers/activitypub'
import { isVideoCategoryValid, isVideoLanguageValid, isVideoPrivacyValid, isVideoUrlValid } from '../../helpers/custom-validators/videos'
import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
import { isVideoCategoryValid, isVideoLanguageValid, isVideoPrivacyValid } from '../../helpers/custom-validators/videos'
import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils'
import {
isActivityPubUrlValid,
isVideoDescriptionValid,
isVideoDurationValid,
isVideoLicenceValid,
isVideoNameValid,
isVideoNSFWValid
} from '../../helpers/index'
import { logger } from '../../helpers/logger'
import {
API_VERSION,
CONFIG,
@ -21,18 +32,12 @@ import {
VIDEO_LICENCES,
VIDEO_PRIVACIES
} from '../../initializers/constants'
import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
import { sendDeleteVideo } from '../../lib/index'
import { addMethodsToModel, getSort } from '../utils'
import { TagInstance } from './tag-interface'
import { VideoFileInstance, VideoFileModel } from './video-file-interface'
import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
import { isVideoNameValid, isVideoLicenceValid, isVideoNSFWValid, isVideoDescriptionValid, isVideoDurationValid } from '../../helpers/index'
import { logger } from '../../helpers/logger'
import { generateImageFromVideoFile, transcode, getVideoFileHeight } from '../../helpers/ffmpeg-utils'
import { createTorrentPromise, writeFilePromise, unlinkPromise, renamePromise, statPromise } from '../../helpers/core-utils'
import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
let Video: Sequelize.Model<VideoInstance, VideoAttributes>
let getOriginalFile: VideoMethods.GetOriginalFile
@ -205,7 +210,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
allowNull: false,
validate: {
urlValid: value => {
const res = isVideoUrlValid(value)
const res = isActivityPubUrlValid(value)
if (res === false) throw new Error('Video URL is not valid.')
}
}

View File

@ -55,10 +55,10 @@ If you want to test the decentralization feature, you can easily run 3 instances
The server is composed by:
* a REST API (throught Express framework)
* a REST API (Express framework)
* a WebTorrent Tracker
A video is seeded by the server throught the [WebSeed](http://www.bittorrent.org/beps/bep_0019.html) protocol (HTTP).
A video is seeded by the server with the [WebSeed](http://www.bittorrent.org/beps/bep_0019.html) protocol (HTTP).
![Architecture scheme](https://github.com/Chocobozzz/PeerTube/blob/master/support/doc/server/upload-video.png)