1 import * as express from 'express'
2 import 'express-validator'
3 import { body, param, ValidationChain } from 'express-validator/check'
4 import { UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
13 } from '../../../helpers/custom-validators/misc'
15 checkUserCanManageVideo,
16 isScheduleVideoUpdatePrivacyValid,
18 isVideoChannelOfAccountExist,
19 isVideoDescriptionValid,
27 isVideoRatingTypeValid,
30 } from '../../../helpers/custom-validators/videos'
31 import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils'
32 import { logger } from '../../../helpers/logger'
33 import { CONSTRAINTS_FIELDS } from '../../../initializers'
34 import { VideoShareModel } from '../../../models/video/video-share'
35 import { authenticate } from '../../oauth'
36 import { areValidationErrors } from '../utils'
37 import { cleanUpReqFiles } from '../../../helpers/express-utils'
38 import { VideoModel } from '../../../models/video/video'
39 import { UserModel } from '../../../models/account/user'
40 import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
41 import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
42 import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership'
43 import { AccountModel } from '../../../models/account/account'
44 import { VideoFetchType } from '../../../helpers/video'
46 const videosAddValidator = getCommonVideoAttributes().concat([
48 .custom((value, { req }) => isVideoFile(req.files)).withMessage(
49 'This file is not supported or too large. Please, make sure it is of the following type: '
50 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
52 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
55 .custom(isIdValid).withMessage('Should have correct video channel id'),
57 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
58 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
60 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
61 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
63 const videoFile: Express.Multer.File = req.files['videofile'][0]
64 const user = res.locals.oauth.token.User
66 if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
68 const isAble = await user.isAbleToUploadVideo(videoFile)
69 if (isAble === false) {
71 .json({ error: 'The user video quota is exceeded with this video.' })
74 return cleanUpReqFiles(req)
80 duration = await getDurationFromVideoFile(videoFile.path)
82 logger.error('Invalid input file in videosAddValidator.', { err })
84 .json({ error: 'Invalid input file.' })
87 return cleanUpReqFiles(req)
90 videoFile['duration'] = duration
96 const videosUpdateValidator = getCommonVideoAttributes().concat([
97 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
100 .custom(isVideoNameValid).withMessage('Should have a valid name'),
104 .custom(isIdValid).withMessage('Should have correct video channel id'),
106 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
107 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
109 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
110 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
111 if (!await isVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
113 const video = res.locals.video
115 // Check if the user who did the request is able to update the video
116 const user = res.locals.oauth.token.User
117 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
119 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
121 return res.status(409)
122 .json({ error: 'Cannot set "private" a video that was not private.' })
126 if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
132 const videosCustomGetValidator = (fetchType: VideoFetchType) => {
134 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
136 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
137 logger.debug('Checking videosGet parameters', { parameters: req.params })
139 if (areValidationErrors(req, res)) return
140 if (!await isVideoExist(req.params.id, res, fetchType)) return
142 const video: VideoModel = res.locals.video
144 // Video private or blacklisted
145 if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) {
146 return authenticate(req, res, () => {
147 const user: UserModel = res.locals.oauth.token.User
149 // Only the owner or a user that have blacklist rights can see the video
150 if (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) {
151 return res.status(403)
152 .json({ error: 'Cannot get this private or blacklisted video.' })
160 // Video is public, anyone can access it
161 if (video.privacy === VideoPrivacy.PUBLIC) return next()
163 // Video is unlisted, check we used the uuid to fetch it
164 if (video.privacy === VideoPrivacy.UNLISTED) {
165 if (isUUIDValid(req.params.id)) return next()
167 // Don't leak this unlisted video
168 return res.status(404).end()
174 const videosGetValidator = videosCustomGetValidator('all')
176 const videosRemoveValidator = [
177 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
179 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
180 logger.debug('Checking videosRemove parameters', { parameters: req.params })
182 if (areValidationErrors(req, res)) return
183 if (!await isVideoExist(req.params.id, res)) return
185 // Check if the user who did the request is able to delete the video
186 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
192 const videoRateValidator = [
193 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
194 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
196 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
197 logger.debug('Checking videoRate parameters', { parameters: req.body })
199 if (areValidationErrors(req, res)) return
200 if (!await isVideoExist(req.params.id, res)) return
206 const videosShareValidator = [
207 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
208 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
210 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
211 logger.debug('Checking videoShare parameters', { parameters: req.params })
213 if (areValidationErrors(req, res)) return
214 if (!await isVideoExist(req.params.id, res)) return
216 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
218 return res.status(404)
222 res.locals.videoShare = share
227 const videosChangeOwnershipValidator = [
228 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
230 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
231 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
233 if (areValidationErrors(req, res)) return
234 if (!await isVideoExist(req.params.videoId, res)) return
236 // Check if the user who did the request is able to change the ownership of the video
237 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
239 const nextOwner = await AccountModel.loadLocalByName(req.body.username)
246 res.locals.nextOwner = nextOwner
252 const videosTerminateChangeOwnershipValidator = [
253 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
255 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
256 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
258 if (areValidationErrors(req, res)) return
259 if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return
261 // Check if the user who did the request is able to change the ownership of the video
262 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
266 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
267 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
269 if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) {
273 .json({ error: 'Ownership already accepted or refused' })
280 const videosAcceptChangeOwnershipValidator = [
281 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
282 const body = req.body as VideoChangeOwnershipAccept
283 if (!await isVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
285 const user = res.locals.oauth.token.User
286 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
287 const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile())
288 if (isAble === false) {
290 .json({ error: 'The user video quota is exceeded with this video.' })
299 function getCommonVideoAttributes () {
301 body('thumbnailfile')
302 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
303 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: '
304 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
307 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
308 'This preview file is not supported or too large. Please, make sure it is of the following type: '
309 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
314 .customSanitizer(toIntOrNull)
315 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
318 .customSanitizer(toIntOrNull)
319 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
322 .customSanitizer(toValueOrNull)
323 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
327 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
328 body('waitTranscoding')
331 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
335 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
338 .customSanitizer(toValueOrNull)
339 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
342 .customSanitizer(toValueOrNull)
343 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
346 .customSanitizer(toValueOrNull)
347 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
348 body('commentsEnabled')
351 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
352 body('downloadingEnabled')
355 .custom(isBooleanValid).withMessage('Should have downloading enabled boolean'),
357 body('scheduleUpdate')
359 .customSanitizer(toValueOrNull),
360 body('scheduleUpdate.updateAt')
362 .custom(isDateValid).withMessage('Should have a valid schedule update date'),
363 body('scheduleUpdate.privacy')
366 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
367 ] as (ValidationChain | express.Handler)[]
370 // ---------------------------------------------------------------------------
374 videosUpdateValidator,
376 videosCustomGetValidator,
377 videosRemoveValidator,
378 videosShareValidator,
382 videosChangeOwnershipValidator,
383 videosTerminateChangeOwnershipValidator,
384 videosAcceptChangeOwnershipValidator,
386 getCommonVideoAttributes
389 // ---------------------------------------------------------------------------
391 function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
392 if (req.body.scheduleUpdate) {
393 if (!req.body.scheduleUpdate.updateAt) {
395 .json({ error: 'Schedule update at is mandatory.' })