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