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