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