diff options
author | Chocobozzz <me@florianbigard.com> | 2018-10-05 11:15:06 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-10-05 11:22:38 +0200 |
commit | 6e46de095d7169355dd83030f6ce4a582304153a (patch) | |
tree | dfa78e2008d3d135a00b798b05350b4975145acc /server/middlewares/validators/videos.ts | |
parent | a585824160d016db7c9bff0e1cb1ffa3aaf73d74 (diff) | |
download | PeerTube-6e46de095d7169355dd83030f6ce4a582304153a.tar.gz PeerTube-6e46de095d7169355dd83030f6ce4a582304153a.tar.zst PeerTube-6e46de095d7169355dd83030f6ce4a582304153a.zip |
Add user history and resume videos
Diffstat (limited to 'server/middlewares/validators/videos.ts')
-rw-r--r-- | server/middlewares/validators/videos.ts | 399 |
1 files changed, 0 insertions, 399 deletions
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts deleted file mode 100644 index 67eabe468..000000000 --- a/server/middlewares/validators/videos.ts +++ /dev/null | |||
@@ -1,399 +0,0 @@ | |||
1 | import * as express from 'express' | ||
2 | import 'express-validator' | ||
3 | import { body, param, ValidationChain } from 'express-validator/check' | ||
4 | import { UserRight, VideoChangeOwnershipStatus, 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 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../helpers/custom-validators/video-ownership' | ||
41 | import { VideoChangeOwnershipAccept } from '../../../shared/models/videos/video-change-ownership-accept.model' | ||
42 | import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' | ||
43 | import { AccountModel } from '../../models/account/account' | ||
44 | import { VideoFetchType } from '../../helpers/video' | ||
45 | |||
46 | const videosAddValidator = getCommonVideoAttributes().concat([ | ||
47 | body('videofile') | ||
48 | .custom((value, { req }) => isVideoFile(req.files)).withMessage( | ||
49 | 'This file is not supported or too large. Please, make sure it is of the following type: ' | ||
50 | + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') | ||
51 | ), | ||
52 | body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), | ||
53 | body('channelId') | ||
54 | .toInt() | ||
55 | .custom(isIdValid).withMessage('Should have correct video channel id'), | ||
56 | |||
57 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
58 | logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) | ||
59 | |||
60 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | ||
61 | if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) | ||
62 | |||
63 | const videoFile: Express.Multer.File = req.files['videofile'][0] | ||
64 | const user = res.locals.oauth.token.User | ||
65 | |||
66 | if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | ||
67 | |||
68 | const isAble = await user.isAbleToUploadVideo(videoFile) | ||
69 | if (isAble === false) { | ||
70 | res.status(403) | ||
71 | .json({ error: 'The user video quota is exceeded with this video.' }) | ||
72 | .end() | ||
73 | |||
74 | return cleanUpReqFiles(req) | ||
75 | } | ||
76 | |||
77 | let duration: number | ||
78 | |||
79 | try { | ||
80 | duration = await getDurationFromVideoFile(videoFile.path) | ||
81 | } catch (err) { | ||
82 | logger.error('Invalid input file in videosAddValidator.', { err }) | ||
83 | res.status(400) | ||
84 | .json({ error: 'Invalid input file.' }) | ||
85 | .end() | ||
86 | |||
87 | return cleanUpReqFiles(req) | ||
88 | } | ||
89 | |||
90 | videoFile['duration'] = duration | ||
91 | |||
92 | return next() | ||
93 | } | ||
94 | ]) | ||
95 | |||
96 | const videosUpdateValidator = getCommonVideoAttributes().concat([ | ||
97 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
98 | body('name') | ||
99 | .optional() | ||
100 | .custom(isVideoNameValid).withMessage('Should have a valid name'), | ||
101 | body('channelId') | ||
102 | .optional() | ||
103 | .toInt() | ||
104 | .custom(isIdValid).withMessage('Should have correct video channel id'), | ||
105 | |||
106 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
107 | logger.debug('Checking videosUpdate parameters', { parameters: req.body }) | ||
108 | |||
109 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | ||
110 | if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) | ||
111 | if (!await isVideoExist(req.params.id, res)) return cleanUpReqFiles(req) | ||
112 | |||
113 | const video = res.locals.video | ||
114 | |||
115 | // Check if the user who did the request is able to update the video | ||
116 | const user = res.locals.oauth.token.User | ||
117 | if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) | ||
118 | |||
119 | if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { | ||
120 | cleanUpReqFiles(req) | ||
121 | return res.status(409) | ||
122 | .json({ error: 'Cannot set "private" a video that was not private.' }) | ||
123 | .end() | ||
124 | } | ||
125 | |||
126 | if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | ||
127 | |||
128 | return next() | ||
129 | } | ||
130 | ]) | ||
131 | |||
132 | const videosCustomGetValidator = (fetchType: VideoFetchType) => { | ||
133 | return [ | ||
134 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
135 | |||
136 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
137 | logger.debug('Checking videosGet parameters', { parameters: req.params }) | ||
138 | |||
139 | if (areValidationErrors(req, res)) return | ||
140 | if (!await isVideoExist(req.params.id, res, fetchType)) return | ||
141 | |||
142 | const video: VideoModel = res.locals.video | ||
143 | |||
144 | // Video private or blacklisted | ||
145 | if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { | ||
146 | return authenticate(req, res, () => { | ||
147 | const user: UserModel = res.locals.oauth.token.User | ||
148 | |||
149 | // Only the owner or a user that have blacklist rights can see the video | ||
150 | if (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) { | ||
151 | return res.status(403) | ||
152 | .json({ error: 'Cannot get this private or blacklisted video.' }) | ||
153 | .end() | ||
154 | } | ||
155 | |||
156 | return next() | ||
157 | }) | ||
158 | } | ||
159 | |||
160 | // Video is public, anyone can access it | ||
161 | if (video.privacy === VideoPrivacy.PUBLIC) return next() | ||
162 | |||
163 | // Video is unlisted, check we used the uuid to fetch it | ||
164 | if (video.privacy === VideoPrivacy.UNLISTED) { | ||
165 | if (isUUIDValid(req.params.id)) return next() | ||
166 | |||
167 | // Don't leak this unlisted video | ||
168 | return res.status(404).end() | ||
169 | } | ||
170 | } | ||
171 | ] | ||
172 | } | ||
173 | |||
174 | const videosGetValidator = videosCustomGetValidator('all') | ||
175 | |||
176 | const videosRemoveValidator = [ | ||
177 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
178 | |||
179 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
180 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) | ||
181 | |||
182 | if (areValidationErrors(req, res)) return | ||
183 | if (!await isVideoExist(req.params.id, res)) return | ||
184 | |||
185 | // Check if the user who did the request is able to delete the video | ||
186 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return | ||
187 | |||
188 | return next() | ||
189 | } | ||
190 | ] | ||
191 | |||
192 | const videoRateValidator = [ | ||
193 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
194 | body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), | ||
195 | |||
196 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
197 | logger.debug('Checking videoRate parameters', { parameters: req.body }) | ||
198 | |||
199 | if (areValidationErrors(req, res)) return | ||
200 | if (!await isVideoExist(req.params.id, res)) return | ||
201 | |||
202 | return next() | ||
203 | } | ||
204 | ] | ||
205 | |||
206 | const videosShareValidator = [ | ||
207 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
208 | param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'), | ||
209 | |||
210 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
211 | logger.debug('Checking videoShare parameters', { parameters: req.params }) | ||
212 | |||
213 | if (areValidationErrors(req, res)) return | ||
214 | if (!await isVideoExist(req.params.id, res)) return | ||
215 | |||
216 | const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined) | ||
217 | if (!share) { | ||
218 | return res.status(404) | ||
219 | .end() | ||
220 | } | ||
221 | |||
222 | res.locals.videoShare = share | ||
223 | return next() | ||
224 | } | ||
225 | ] | ||
226 | |||
227 | const videosChangeOwnershipValidator = [ | ||
228 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
229 | |||
230 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
231 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) | ||
232 | |||
233 | if (areValidationErrors(req, res)) return | ||
234 | if (!await isVideoExist(req.params.videoId, res)) return | ||
235 | |||
236 | // Check if the user who did the request is able to change the ownership of the video | ||
237 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return | ||
238 | |||
239 | const nextOwner = await AccountModel.loadLocalByName(req.body.username) | ||
240 | if (!nextOwner) { | ||
241 | res.status(400) | ||
242 | .type('json') | ||
243 | .end() | ||
244 | return | ||
245 | } | ||
246 | res.locals.nextOwner = nextOwner | ||
247 | |||
248 | return next() | ||
249 | } | ||
250 | ] | ||
251 | |||
252 | const videosTerminateChangeOwnershipValidator = [ | ||
253 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
254 | |||
255 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
256 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) | ||
257 | |||
258 | if (areValidationErrors(req, res)) return | ||
259 | if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return | ||
260 | |||
261 | // Check if the user who did the request is able to change the ownership of the video | ||
262 | if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return | ||
263 | |||
264 | return next() | ||
265 | }, | ||
266 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
267 | const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel | ||
268 | |||
269 | if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { | ||
270 | return next() | ||
271 | } else { | ||
272 | res.status(403) | ||
273 | .json({ error: 'Ownership already accepted or refused' }) | ||
274 | .end() | ||
275 | return | ||
276 | } | ||
277 | } | ||
278 | ] | ||
279 | |||
280 | const videosAcceptChangeOwnershipValidator = [ | ||
281 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
282 | const body = req.body as VideoChangeOwnershipAccept | ||
283 | if (!await isVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return | ||
284 | |||
285 | const user = res.locals.oauth.token.User | ||
286 | const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel | ||
287 | const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile()) | ||
288 | if (isAble === false) { | ||
289 | res.status(403) | ||
290 | .json({ error: 'The user video quota is exceeded with this video.' }) | ||
291 | .end() | ||
292 | return | ||
293 | } | ||
294 | |||
295 | return next() | ||
296 | } | ||
297 | ] | ||
298 | |||
299 | function getCommonVideoAttributes () { | ||
300 | return [ | ||
301 | body('thumbnailfile') | ||
302 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( | ||
303 | 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' | ||
304 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | ||
305 | ), | ||
306 | body('previewfile') | ||
307 | .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( | ||
308 | 'This preview file is not supported or too large. Please, make sure it is of the following type: ' | ||
309 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | ||
310 | ), | ||
311 | |||
312 | body('category') | ||
313 | .optional() | ||
314 | .customSanitizer(toIntOrNull) | ||
315 | .custom(isVideoCategoryValid).withMessage('Should have a valid category'), | ||
316 | body('licence') | ||
317 | .optional() | ||
318 | .customSanitizer(toIntOrNull) | ||
319 | .custom(isVideoLicenceValid).withMessage('Should have a valid licence'), | ||
320 | body('language') | ||
321 | .optional() | ||
322 | .customSanitizer(toValueOrNull) | ||
323 | .custom(isVideoLanguageValid).withMessage('Should have a valid language'), | ||
324 | body('nsfw') | ||
325 | .optional() | ||
326 | .toBoolean() | ||
327 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), | ||
328 | body('waitTranscoding') | ||
329 | .optional() | ||
330 | .toBoolean() | ||
331 | .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'), | ||
332 | body('privacy') | ||
333 | .optional() | ||
334 | .toInt() | ||
335 | .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), | ||
336 | body('description') | ||
337 | .optional() | ||
338 | .customSanitizer(toValueOrNull) | ||
339 | .custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | ||
340 | body('support') | ||
341 | .optional() | ||
342 | .customSanitizer(toValueOrNull) | ||
343 | .custom(isVideoSupportValid).withMessage('Should have a valid support text'), | ||
344 | body('tags') | ||
345 | .optional() | ||
346 | .customSanitizer(toValueOrNull) | ||
347 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), | ||
348 | body('commentsEnabled') | ||
349 | .optional() | ||
350 | .toBoolean() | ||
351 | .custom(isBooleanValid).withMessage('Should have comments enabled boolean'), | ||
352 | |||
353 | body('scheduleUpdate') | ||
354 | .optional() | ||
355 | .customSanitizer(toValueOrNull), | ||
356 | body('scheduleUpdate.updateAt') | ||
357 | .optional() | ||
358 | .custom(isDateValid).withMessage('Should have a valid schedule update date'), | ||
359 | body('scheduleUpdate.privacy') | ||
360 | .optional() | ||
361 | .toInt() | ||
362 | .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy') | ||
363 | ] as (ValidationChain | express.Handler)[] | ||
364 | } | ||
365 | |||
366 | // --------------------------------------------------------------------------- | ||
367 | |||
368 | export { | ||
369 | videosAddValidator, | ||
370 | videosUpdateValidator, | ||
371 | videosGetValidator, | ||
372 | videosCustomGetValidator, | ||
373 | videosRemoveValidator, | ||
374 | videosShareValidator, | ||
375 | |||
376 | videoRateValidator, | ||
377 | |||
378 | videosChangeOwnershipValidator, | ||
379 | videosTerminateChangeOwnershipValidator, | ||
380 | videosAcceptChangeOwnershipValidator, | ||
381 | |||
382 | getCommonVideoAttributes | ||
383 | } | ||
384 | |||
385 | // --------------------------------------------------------------------------- | ||
386 | |||
387 | function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) { | ||
388 | if (req.body.scheduleUpdate) { | ||
389 | if (!req.body.scheduleUpdate.updateAt) { | ||
390 | res.status(400) | ||
391 | .json({ error: 'Schedule update at is mandatory.' }) | ||
392 | .end() | ||
393 | |||
394 | return true | ||
395 | } | ||
396 | } | ||
397 | |||
398 | return false | ||
399 | } | ||