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