]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/videos.ts
Add quota used in users list
[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 isVideoCategoryValid,
18 isVideoChannelOfAccountExist,
19 isVideoDescriptionValid,
20 isVideoExist,
21 isVideoFile,
22 isVideoImage,
23 isVideoLanguageValid,
24 isVideoLicenceValid,
25 isVideoNameValid,
26 isVideoPrivacyValid,
27 isVideoRatingTypeValid,
28 isVideoSupportValid,
29 isVideoTagsValid
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
41 const videosAddValidator = getCommonVideoAttributes().concat([
42 body('videofile')
43 .custom((value, { req }) => isVideoFile(req.files)).withMessage(
44 'This file is not supported or too large. Please, make sure it is of the following type: '
45 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
46 ),
47 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
48 body('channelId')
49 .toInt()
50 .custom(isIdValid).withMessage('Should have correct video channel id'),
51
52 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
53 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
54
55 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
56 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
57
58 const videoFile: Express.Multer.File = req.files['videofile'][0]
59 const user = res.locals.oauth.token.User
60
61 if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
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
69 return cleanUpReqFiles(req)
70 }
71
72 let duration: number
73
74 try {
75 duration = await getDurationFromVideoFile(videoFile.path)
76 } catch (err) {
77 logger.error('Invalid input file in videosAddValidator.', { err })
78 res.status(400)
79 .json({ error: 'Invalid input file.' })
80 .end()
81
82 return cleanUpReqFiles(req)
83 }
84
85 videoFile['duration'] = duration
86
87 return next()
88 }
89 ])
90
91 const videosUpdateValidator = getCommonVideoAttributes().concat([
92 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
93 body('name')
94 .optional()
95 .custom(isVideoNameValid).withMessage('Should have a valid name'),
96 body('channelId')
97 .optional()
98 .toInt()
99 .custom(isIdValid).withMessage('Should have correct video channel id'),
100
101 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
102 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
103
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)
107
108 const video = res.locals.video
109
110 // Check if the user who did the request is able to update the video
111 const user = res.locals.oauth.token.User
112 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
113
114 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
115 cleanUpReqFiles(req)
116 return res.status(409)
117 .json({ error: 'Cannot set "private" a video that was not private.' })
118 .end()
119 }
120
121 if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
122
123 return next()
124 }
125 ])
126
127 const videosGetValidator = [
128 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
129
130 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
131 logger.debug('Checking videosGet parameters', { parameters: req.params })
132
133 if (areValidationErrors(req, res)) return
134 if (!await isVideoExist(req.params.id, res)) return
135
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 }
155
156 // Video is public, anyone can access it
157 if (video.privacy === VideoPrivacy.PUBLIC) return next()
158
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 }
166 }
167 ]
168
169 const videosRemoveValidator = [
170 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
171
172 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
173 logger.debug('Checking videosRemove parameters', { parameters: req.params })
174
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
179 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
180
181 return next()
182 }
183 ]
184
185 const videoRateValidator = [
186 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
187 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
188
189 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
190 logger.debug('Checking videoRate parameters', { parameters: req.body })
191
192 if (areValidationErrors(req, res)) return
193 if (!await isVideoExist(req.params.id, res)) return
194
195 return next()
196 }
197 ]
198
199 const 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
207 if (!await isVideoExist(req.params.id, res)) return
208
209 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
210 if (!share) {
211 return res.status(404)
212 .end()
213 }
214
215 res.locals.videoShare = share
216 return next()
217 }
218 ]
219
220 function 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 }
286
287 // ---------------------------------------------------------------------------
288
289 export {
290 videosAddValidator,
291 videosUpdateValidator,
292 videosGetValidator,
293 videosRemoveValidator,
294 videosShareValidator,
295
296 videoRateValidator,
297
298 getCommonVideoAttributes
299 }
300
301 // ---------------------------------------------------------------------------
302
303 function 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 }