]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/videos.ts
Remove unnecessary image check in video upload
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos.ts
CommitLineData
69818c93 1import * as express from 'express'
3fd3ab2d 2import 'express-validator'
a920fef1 3import { body, param, query, ValidationChain } from 'express-validator/check'
8d468a16 4import { UserRight, VideoPrivacy } from '../../../shared'
b60e5f38 5import {
2baea0c7
C
6 isBooleanValid,
7 isDateValid,
8 isIdOrUUIDValid,
9 isIdValid,
10 isUUIDValid,
11 toIntOrNull,
12 toValueOrNull
13} from '../../helpers/custom-validators/misc'
14import {
40e87e9e 15 checkUserCanManageVideo,
2baea0c7 16 isScheduleVideoUpdatePrivacyValid,
ac81d1a0
C
17 isVideoAbuseReasonValid,
18 isVideoCategoryValid,
0f320037 19 isVideoChannelOfAccountExist,
ac81d1a0
C
20 isVideoDescriptionValid,
21 isVideoExist,
22 isVideoFile,
23 isVideoImage,
24 isVideoLanguageValid,
25 isVideoLicenceValid,
26 isVideoNameValid,
27 isVideoPrivacyValid,
360329cc
C
28 isVideoRatingTypeValid,
29 isVideoSupportValid,
ac81d1a0 30 isVideoTagsValid
8d468a16 31} from '../../helpers/custom-validators/videos'
da854ddd
C
32import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
33import { logger } from '../../helpers/logger'
f3aaa9a9 34import { CONSTRAINTS_FIELDS } from '../../initializers'
3fd3ab2d 35import { VideoShareModel } from '../../models/video/video-share'
11474c3c 36import { authenticate } from '../oauth'
a2431b7d 37import { areValidationErrors } from './utils'
34ca3b52 38
a920fef1 39const videosAddValidator = getCommonVideoAttributes().concat([
0c237b19
C
40 body('videofile')
41 .custom((value, { req }) => isVideoFile(req.files)).withMessage(
40e87e9e 42 'This file is not supported or too large. Please, make sure it is of the following type: '
0c237b19
C
43 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
44 ),
b60e5f38 45 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
0f320037
C
46 body('channelId')
47 .toInt()
2baea0c7 48 .custom(isIdValid).withMessage('Should have correct video channel id'),
b60e5f38 49
a2431b7d 50 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
51 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
52
a2431b7d 53 if (areValidationErrors(req, res)) return
2baea0c7 54 if (areErrorsInScheduleUpdate(req, res)) return
a2431b7d
C
55
56 const videoFile: Express.Multer.File = req.files['videofile'][0]
57 const user = res.locals.oauth.token.User
b60e5f38 58
6200d8d9 59 if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return
a2431b7d
C
60
61 const isAble = await user.isAbleToUploadVideo(videoFile)
62 if (isAble === false) {
63 res.status(403)
64 .json({ error: 'The user video quota is exceeded with this video.' })
65 .end()
66
67 return
68 }
69
70 let duration: number
71
72 try {
73 duration = await getDurationFromVideoFile(videoFile.path)
74 } catch (err) {
d5b7d911 75 logger.error('Invalid input file in videosAddValidator.', { err })
a2431b7d
C
76 res.status(400)
77 .json({ error: 'Invalid input file.' })
78 .end()
79
80 return
81 }
82
a2431b7d
C
83 videoFile['duration'] = duration
84
85 return next()
b60e5f38 86 }
a920fef1 87])
b60e5f38 88
a920fef1 89const videosUpdateValidator = getCommonVideoAttributes().concat([
72c7248b 90 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
360329cc
C
91 body('name')
92 .optional()
93 .custom(isVideoNameValid).withMessage('Should have a valid name'),
0f320037
C
94 body('channelId')
95 .optional()
96 .toInt()
97 .custom(isIdValid).withMessage('Should have correct video channel id'),
b60e5f38 98
a2431b7d 99 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
100 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
101
a2431b7d 102 if (areValidationErrors(req, res)) return
2baea0c7 103 if (areErrorsInScheduleUpdate(req, res)) return
a2431b7d
C
104 if (!await isVideoExist(req.params.id, res)) return
105
106 const video = res.locals.video
107
6221f311 108 // Check if the user who did the request is able to update the video
0f320037
C
109 const user = res.locals.oauth.token.User
110 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
a2431b7d
C
111
112 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
113 return res.status(409)
bbe0f064 114 .json({ error: 'Cannot set "private" a video that was not private.' })
a2431b7d
C
115 .end()
116 }
117
6200d8d9 118 if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return
0f320037 119
a2431b7d 120 return next()
b60e5f38 121 }
a920fef1 122])
c173e565 123
b60e5f38 124const videosGetValidator = [
72c7248b 125 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
34ca3b52 126
a2431b7d 127 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 128 logger.debug('Checking videosGet parameters', { parameters: req.params })
7b1f49de 129
a2431b7d
C
130 if (areValidationErrors(req, res)) return
131 if (!await isVideoExist(req.params.id, res)) return
11474c3c 132
a2431b7d 133 const video = res.locals.video
11474c3c 134
81ebea48
C
135 // Video is public, anyone can access it
136 if (video.privacy === VideoPrivacy.PUBLIC) return next()
11474c3c 137
81ebea48
C
138 // Video is unlisted, check we used the uuid to fetch it
139 if (video.privacy === VideoPrivacy.UNLISTED) {
140 if (isUUIDValid(req.params.id)) return next()
141
142 // Don't leak this unlisted video
143 return res.status(404).end()
144 }
145
146 // Video is private, check the user
a2431b7d
C
147 authenticate(req, res, () => {
148 if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
149 return res.status(403)
150 .json({ error: 'Cannot get this private video of another user' })
151 .end()
152 }
153
154 return next()
b60e5f38
C
155 })
156 }
157]
34ca3b52 158
b60e5f38 159const videosRemoveValidator = [
72c7248b 160 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
34ca3b52 161
a2431b7d 162 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 163 logger.debug('Checking videosRemove parameters', { parameters: req.params })
34ca3b52 164
a2431b7d
C
165 if (areValidationErrors(req, res)) return
166 if (!await isVideoExist(req.params.id, res)) return
167
168 // Check if the user who did the request is able to delete the video
6221f311 169 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
a2431b7d
C
170
171 return next()
b60e5f38
C
172 }
173]
34ca3b52 174
b60e5f38 175const videosSearchValidator = [
f3aaa9a9 176 query('search').not().isEmpty().withMessage('Should have a valid search'),
c45f7f84 177
b60e5f38
C
178 (req: express.Request, res: express.Response, next: express.NextFunction) => {
179 logger.debug('Checking videosSearch parameters', { parameters: req.params })
c45f7f84 180
a2431b7d
C
181 if (areValidationErrors(req, res)) return
182
183 return next()
b60e5f38
C
184 }
185]
c45f7f84 186
b60e5f38 187const videoAbuseReportValidator = [
72c7248b 188 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
b60e5f38 189 body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
55fa55a9 190
a2431b7d 191 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 192 logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
55fa55a9 193
a2431b7d
C
194 if (areValidationErrors(req, res)) return
195 if (!await isVideoExist(req.params.id, res)) return
196
197 return next()
b60e5f38
C
198 }
199]
55fa55a9 200
b60e5f38 201const videoRateValidator = [
72c7248b 202 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
b60e5f38 203 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
d38b8281 204
a2431b7d 205 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 206 logger.debug('Checking videoRate parameters', { parameters: req.body })
d38b8281 207
a2431b7d
C
208 if (areValidationErrors(req, res)) return
209 if (!await isVideoExist(req.params.id, res)) return
210
211 return next()
b60e5f38
C
212 }
213]
d38b8281 214
4e50b6a1
C
215const videosShareValidator = [
216 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
217 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
218
219 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
220 logger.debug('Checking videoShare parameters', { parameters: req.params })
221
222 if (areValidationErrors(req, res)) return
a2431b7d 223 if (!await isVideoExist(req.params.id, res)) return
4e50b6a1 224
3fd3ab2d 225 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
4e50b6a1
C
226 if (!share) {
227 return res.status(404)
228 .end()
229 }
230
231 res.locals.videoShare = share
4e50b6a1
C
232 return next()
233 }
234]
235
9f10b292 236// ---------------------------------------------------------------------------
c45f7f84 237
65fcc311
C
238export {
239 videosAddValidator,
240 videosUpdateValidator,
241 videosGetValidator,
242 videosRemoveValidator,
243 videosSearchValidator,
4e50b6a1 244 videosShareValidator,
65fcc311
C
245
246 videoAbuseReportValidator,
247
35bf0c83 248 videoRateValidator
65fcc311 249}
7b1f49de
C
250
251// ---------------------------------------------------------------------------
252
2baea0c7
C
253function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
254 if (req.body.scheduleUpdate) {
255 if (!req.body.scheduleUpdate.updateAt) {
256 res.status(400)
257 .json({ error: 'Schedule update at is mandatory.' })
258 .end()
259
260 return true
261 }
262 }
263
264 return false
265}
a920fef1
C
266
267function getCommonVideoAttributes () {
268 return [
269 body('thumbnailfile')
270 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
271 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: '
272 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
273 ),
274 body('previewfile')
275 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
276 'This preview file is not supported or too large. Please, make sure it is of the following type: '
277 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
278 ),
279
280 body('category')
281 .optional()
282 .customSanitizer(toIntOrNull)
283 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
284 body('licence')
285 .optional()
286 .customSanitizer(toIntOrNull)
287 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
288 body('language')
289 .optional()
290 .customSanitizer(toValueOrNull)
291 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
292 body('nsfw')
293 .optional()
294 .toBoolean()
295 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
296 body('waitTranscoding')
297 .optional()
298 .toBoolean()
299 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
300 body('privacy')
301 .optional()
302 .toInt()
303 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
304 body('description')
305 .optional()
306 .customSanitizer(toValueOrNull)
307 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
308 body('support')
309 .optional()
310 .customSanitizer(toValueOrNull)
311 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
312 body('tags')
313 .optional()
314 .customSanitizer(toValueOrNull)
315 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
316 body('commentsEnabled')
317 .optional()
318 .toBoolean()
319 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
320
321 body('scheduleUpdate')
322 .optional()
323 .customSanitizer(toValueOrNull),
324 body('scheduleUpdate.updateAt')
325 .optional()
326 .custom(isDateValid).withMessage('Should have a valid schedule update date'),
327 body('scheduleUpdate.privacy')
328 .optional()
329 .toInt()
330 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
331 ] as (ValidationChain | express.Handler)[]
332}