Change how we handle resolution
[oweals/peertube.git] / server / middlewares / validators / videos.ts
1 import { body, param, query } from 'express-validator/check'
2 import * as express from 'express'
3 import * as Promise from 'bluebird'
4 import * as validator from 'validator'
5
6 import { database as db } from '../../initializers/database'
7 import { checkErrors } from './utils'
8 import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers'
9 import {
10   logger,
11   isVideoDurationValid,
12   isVideoFile,
13   isVideoNameValid,
14   isVideoCategoryValid,
15   isVideoLicenceValid,
16   isVideoDescriptionValid,
17   isVideoLanguageValid,
18   isVideoTagsValid,
19   isVideoNSFWValid,
20   isVideoIdOrUUIDValid,
21   isVideoAbuseReasonValid,
22   isVideoRatingTypeValid,
23   getDurationFromVideoFile
24 } from '../../helpers'
25 import { VideoInstance } from '../../models'
26
27 const videosAddValidator = [
28   body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage('Should have a valid file'),
29   body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
30   body('category').custom(isVideoCategoryValid).withMessage('Should have a valid category'),
31   body('licence').custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
32   body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
33   body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'),
34   body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
35   body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
36
37   (req: express.Request, res: express.Response, next: express.NextFunction) => {
38     logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
39
40     checkErrors(req, res, () => {
41       const videoFile: Express.Multer.File = req.files['videofile'][0]
42       const user = res.locals.oauth.token.User
43
44       user.isAbleToUploadVideo(videoFile)
45         .then(isAble => {
46           if (isAble === false) {
47             res.status(403)
48                .json({ error: 'The user video quota is exceeded with this video.' })
49                .end()
50
51             return undefined
52           }
53
54           return getDurationFromVideoFile(videoFile.path)
55             .catch(err => {
56               logger.error('Invalid input file in videosAddValidator.', err)
57               res.status(400)
58                  .json({ error: 'Invalid input file.' })
59                  .end()
60
61               return undefined
62             })
63         })
64         .then(duration => {
65           // Previous test failed, abort
66           if (duration === undefined) return
67
68           if (!isVideoDurationValid('' + duration)) {
69             return res.status(400)
70                       .json({
71                         error: 'Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).'
72                       })
73                       .end()
74           }
75
76           videoFile['duration'] = duration
77           next()
78         })
79         .catch(err => {
80           logger.error('Error in video add validator', err)
81           res.sendStatus(500)
82
83           return undefined
84         })
85     })
86   }
87 ]
88
89 const videosUpdateValidator = [
90   param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
91   body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'),
92   body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
93   body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
94   body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
95   body('nsfw').optional().custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'),
96   body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
97   body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
98
99   (req: express.Request, res: express.Response, next: express.NextFunction) => {
100     logger.debug('Checking videosUpdate parameters', { parameters: req.body })
101
102     checkErrors(req, res, () => {
103       checkVideoExists(req.params.id, res, () => {
104         // We need to make additional checks
105         if (res.locals.video.isOwned() === false) {
106           return res.status(403)
107                     .json({ error: 'Cannot update video of another pod' })
108                     .end()
109         }
110
111         if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
112           return res.status(403)
113                     .json({ error: 'Cannot update video of another user' })
114                     .end()
115         }
116
117         next()
118       })
119     })
120   }
121 ]
122
123 const videosGetValidator = [
124   param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
125
126   (req: express.Request, res: express.Response, next: express.NextFunction) => {
127     logger.debug('Checking videosGet parameters', { parameters: req.params })
128
129     checkErrors(req, res, () => {
130       checkVideoExists(req.params.id, res, next)
131     })
132   }
133 ]
134
135 const videosRemoveValidator = [
136   param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
137
138   (req: express.Request, res: express.Response, next: express.NextFunction) => {
139     logger.debug('Checking videosRemove parameters', { parameters: req.params })
140
141     checkErrors(req, res, () => {
142       checkVideoExists(req.params.id, res, () => {
143         // Check if the user who did the request is able to delete the video
144         checkUserCanDeleteVideo(res.locals.oauth.token.User.id, res, () => {
145           next()
146         })
147       })
148     })
149   }
150 ]
151
152 const videosSearchValidator = [
153   param('value').not().isEmpty().withMessage('Should have a valid search'),
154   query('field').optional().isIn(SEARCHABLE_COLUMNS.VIDEOS).withMessage('Should have correct searchable column'),
155
156   (req: express.Request, res: express.Response, next: express.NextFunction) => {
157     logger.debug('Checking videosSearch parameters', { parameters: req.params })
158
159     checkErrors(req, res, next)
160   }
161 ]
162
163 const videoAbuseReportValidator = [
164   param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
165   body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
166
167   (req: express.Request, res: express.Response, next: express.NextFunction) => {
168     logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
169
170     checkErrors(req, res, () => {
171       checkVideoExists(req.params.id, res, next)
172     })
173   }
174 ]
175
176 const videoRateValidator = [
177   param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
178   body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
179
180   (req: express.Request, res: express.Response, next: express.NextFunction) => {
181     logger.debug('Checking videoRate parameters', { parameters: req.body })
182
183     checkErrors(req, res, () => {
184       checkVideoExists(req.params.id, res, next)
185     })
186   }
187 ]
188
189 const videosBlacklistValidator = [
190   param('id').custom(isVideoIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
191
192   (req: express.Request, res: express.Response, next: express.NextFunction) => {
193     logger.debug('Checking videosBlacklist parameters', { parameters: req.params })
194
195     checkErrors(req, res, () => {
196       checkVideoExists(req.params.id, res, () => {
197         checkVideoIsBlacklistable(req, res, next)
198       })
199     })
200   }
201 ]
202
203 // ---------------------------------------------------------------------------
204
205 export {
206   videosAddValidator,
207   videosUpdateValidator,
208   videosGetValidator,
209   videosRemoveValidator,
210   videosSearchValidator,
211
212   videoAbuseReportValidator,
213
214   videoRateValidator,
215
216   videosBlacklistValidator
217 }
218
219 // ---------------------------------------------------------------------------
220
221 function checkVideoExists (id: string, res: express.Response, callback: () => void) {
222   let promise: Promise<VideoInstance>
223   if (validator.isInt(id)) {
224     promise = db.Video.loadAndPopulateAuthorAndPodAndTags(+id)
225   } else { // UUID
226     promise = db.Video.loadByUUIDAndPopulateAuthorAndPodAndTags(id)
227   }
228
229   promise.then(video => {
230     if (!video) {
231       return res.status(404)
232                 .json({ error: 'Video not found' })
233                 .end()
234     }
235
236     res.locals.video = video
237     callback()
238   })
239   .catch(err => {
240     logger.error('Error in video request validator.', err)
241     return res.sendStatus(500)
242   })
243 }
244
245 function checkUserCanDeleteVideo (userId: number, res: express.Response, callback: () => void) {
246   // Retrieve the user who did the request
247   db.User.loadById(userId)
248     .then(user => {
249       if (res.locals.video.isOwned() === false) {
250         return res.status(403)
251                   .json({ error: 'Cannot remove video of another pod, blacklist it' })
252                   .end()
253       }
254
255       // Check if the user can delete the video
256       // The user can delete it if s/he is an admin
257       // Or if s/he is the video's author
258       if (user.isAdmin() === false && res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
259         return res.status(403)
260                   .json({ error: 'Cannot remove video of another user' })
261                   .end()
262       }
263
264       // If we reach this comment, we can delete the video
265       callback()
266     })
267     .catch(err => {
268       logger.error('Error in video request validator.', err)
269       return res.sendStatus(500)
270     })
271 }
272
273 function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) {
274   if (res.locals.video.isOwned() === true) {
275     return res.status(403)
276               .json({ error: 'Cannot blacklist a local video' })
277               .end()
278   }
279
280   callback()
281 }