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