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