1 import * as express from 'express'
2 import 'express-validator'
3 import { body, param, query } from 'express-validator/check'
4 import { UserRight, VideoPrivacy } from '../../../shared'
5 import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntOrNull, toValueOrNull } from '../../helpers/custom-validators/misc'
7 isVideoAbuseReasonValid,
9 isVideoChannelOfAccountExist,
10 isVideoDescriptionValid,
18 isVideoRatingTypeValid,
21 } from '../../helpers/custom-validators/videos'
22 import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
23 import { logger } from '../../helpers/logger'
24 import { CONSTRAINTS_FIELDS } from '../../initializers'
25 import { UserModel } from '../../models/account/user'
26 import { VideoModel } from '../../models/video/video'
27 import { VideoShareModel } from '../../models/video/video-share'
28 import { authenticate } from '../oauth'
29 import { areValidationErrors } from './utils'
31 const videosAddValidator = [
32 body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
33 'This file is not supported. Please, make sure it is of the following type : '
34 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
36 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
37 'This thumbnail file is not supported. Please, make sure it is of the following type : '
38 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
40 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
41 'This preview file is not supported. Please, make sure it is of the following type : '
42 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
44 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
47 .customSanitizer(toIntOrNull)
48 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
51 .customSanitizer(toIntOrNull)
52 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
55 .customSanitizer(toValueOrNull)
56 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
60 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
61 body('waitTranscoding')
64 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
67 .customSanitizer(toValueOrNull)
68 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
71 .customSanitizer(toValueOrNull)
72 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
75 .customSanitizer(toValueOrNull)
76 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
77 body('commentsEnabled')
80 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
84 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
88 .withMessage('Should have correct video channel id'),
90 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
91 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
93 if (areValidationErrors(req, res)) return
94 if (areErrorsInVideoImageFiles(req, res)) return
96 const videoFile: Express.Multer.File = req.files['videofile'][0]
97 const user = res.locals.oauth.token.User
99 if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return
101 const isAble = await user.isAbleToUploadVideo(videoFile)
102 if (isAble === false) {
104 .json({ error: 'The user video quota is exceeded with this video.' })
113 duration = await getDurationFromVideoFile(videoFile.path)
115 logger.error('Invalid input file in videosAddValidator.', { err })
117 .json({ error: 'Invalid input file.' })
123 videoFile['duration'] = duration
129 const videosUpdateValidator = [
130 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
131 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
132 'This thumbnail file is not supported. Please, make sure it is of the following type : '
133 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
135 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
136 'This preview file is not supported. Please, make sure it is of the following type : '
137 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
141 .custom(isVideoNameValid).withMessage('Should have a valid name'),
144 .customSanitizer(toIntOrNull)
145 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
148 .customSanitizer(toIntOrNull)
149 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
152 .customSanitizer(toValueOrNull)
153 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
157 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
158 body('waitTranscoding')
161 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
165 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
168 .customSanitizer(toValueOrNull)
169 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
172 .customSanitizer(toValueOrNull)
173 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
176 .customSanitizer(toValueOrNull)
177 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
178 body('commentsEnabled')
181 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
185 .custom(isIdValid).withMessage('Should have correct video channel id'),
187 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
188 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
190 if (areValidationErrors(req, res)) return
191 if (areErrorsInVideoImageFiles(req, res)) return
192 if (!await isVideoExist(req.params.id, res)) return
194 const video = res.locals.video
196 // Check if the user who did the request is able to update the video
197 const user = res.locals.oauth.token.User
198 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
200 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
201 return res.status(409)
202 .json({ error: 'Cannot set "private" a video that was not private anymore.' })
206 if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return
212 const videosGetValidator = [
213 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
215 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
216 logger.debug('Checking videosGet parameters', { parameters: req.params })
218 if (areValidationErrors(req, res)) return
219 if (!await isVideoExist(req.params.id, res)) return
221 const video = res.locals.video
223 // Video is public, anyone can access it
224 if (video.privacy === VideoPrivacy.PUBLIC) return next()
226 // Video is unlisted, check we used the uuid to fetch it
227 if (video.privacy === VideoPrivacy.UNLISTED) {
228 if (isUUIDValid(req.params.id)) return next()
230 // Don't leak this unlisted video
231 return res.status(404).end()
234 // Video is private, check the user
235 authenticate(req, res, () => {
236 if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
237 return res.status(403)
238 .json({ error: 'Cannot get this private video of another user' })
247 const videosRemoveValidator = [
248 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
250 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
251 logger.debug('Checking videosRemove parameters', { parameters: req.params })
253 if (areValidationErrors(req, res)) return
254 if (!await isVideoExist(req.params.id, res)) return
256 // Check if the user who did the request is able to delete the video
257 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
263 const videosSearchValidator = [
264 query('search').not().isEmpty().withMessage('Should have a valid search'),
266 (req: express.Request, res: express.Response, next: express.NextFunction) => {
267 logger.debug('Checking videosSearch parameters', { parameters: req.params })
269 if (areValidationErrors(req, res)) return
275 const videoAbuseReportValidator = [
276 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
277 body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
279 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
280 logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
282 if (areValidationErrors(req, res)) return
283 if (!await isVideoExist(req.params.id, res)) return
289 const videoRateValidator = [
290 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
291 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
293 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
294 logger.debug('Checking videoRate parameters', { parameters: req.body })
296 if (areValidationErrors(req, res)) return
297 if (!await isVideoExist(req.params.id, res)) return
303 const videosShareValidator = [
304 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
305 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
307 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
308 logger.debug('Checking videoShare parameters', { parameters: req.params })
310 if (areValidationErrors(req, res)) return
311 if (!await isVideoExist(req.params.id, res)) return
313 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
315 return res.status(404)
319 res.locals.videoShare = share
324 // ---------------------------------------------------------------------------
328 videosUpdateValidator,
330 videosRemoveValidator,
331 videosSearchValidator,
332 videosShareValidator,
334 videoAbuseReportValidator,
339 // ---------------------------------------------------------------------------
341 function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: express.Response) {
342 // Retrieve the user who did the request
343 if (video.isOwned() === false) {
345 .json({ error: 'Cannot manage a video of another server.' })
350 // Check if the user can delete the video
351 // The user can delete it if he has the right
352 // Or if s/he is the video's account
353 const account = video.VideoChannel.Account
354 if (user.hasRight(right) === false && account.userId !== user.id) {
356 .json({ error: 'Cannot manage a video of another user.' })
364 function areErrorsInVideoImageFiles (req: express.Request, res: express.Response) {
365 // Files are optional
366 if (!req.files) return false
368 for (const imageField of [ 'thumbnail', 'preview' ]) {
369 if (!req.files[ imageField ]) continue
371 const imageFile = req.files[ imageField ][ 0 ] as Express.Multer.File
372 if (imageFile.size > CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max) {
374 .send({ error: `The size of the ${imageField} is too big (>${CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max}).` })