6604ae6a880bb0bef9e627826560eb3337f5a600
[oweals/peertube.git] / server / middlewares / validators / videos / video-channels.ts
1 import * as express from 'express'
2 import { body, param, query } from 'express-validator'
3 import { VIDEO_CHANNELS } from '@server/initializers/constants'
4 import { MChannelAccountDefault, MUser } from '@server/typings/models'
5 import { UserRight } from '../../../../shared'
6 import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
7 import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
8 import {
9   isVideoChannelDescriptionValid,
10   isVideoChannelNameValid,
11   isVideoChannelSupportValid
12 } from '../../../helpers/custom-validators/video-channels'
13 import { logger } from '../../../helpers/logger'
14 import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares'
15 import { ActorModel } from '../../../models/activitypub/actor'
16 import { VideoChannelModel } from '../../../models/video/video-channel'
17 import { areValidationErrors } from '../utils'
18
19 const videoChannelsAddValidator = [
20   body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
21   body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
22   body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
23   body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
24
25   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
26     logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body })
27
28     if (areValidationErrors(req, res)) return
29
30     const actor = await ActorModel.loadLocalByName(req.body.name)
31     if (actor) {
32       res.status(409)
33          .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
34          .end()
35       return false
36     }
37
38     const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
39     if (count >= VIDEO_CHANNELS.MAX_PER_USER) {
40       res.status(400)
41         .send({ error: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` })
42         .end()
43       return false
44     }
45
46     return next()
47   }
48 ]
49
50 const videoChannelsUpdateValidator = [
51   param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
52   body('displayName')
53     .optional()
54     .custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
55   body('description')
56     .optional()
57     .custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
58   body('support')
59     .optional()
60     .custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
61   body('bulkVideosSupportUpdate')
62     .optional()
63     .custom(isBooleanValid).withMessage('Should have a valid bulkVideosSupportUpdate boolean field'),
64
65   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
66     logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
67
68     if (areValidationErrors(req, res)) return
69     if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
70
71     // We need to make additional checks
72     if (res.locals.videoChannel.Actor.isOwned() === false) {
73       return res.status(403)
74         .json({ error: 'Cannot update video channel of another server' })
75         .end()
76     }
77
78     if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) {
79       return res.status(403)
80         .json({ error: 'Cannot update video channel of another user' })
81         .end()
82     }
83
84     return next()
85   }
86 ]
87
88 const videoChannelsRemoveValidator = [
89   param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
90
91   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
92     logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
93
94     if (areValidationErrors(req, res)) return
95     if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
96
97     if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return
98     if (!await checkVideoChannelIsNotTheLastOne(res)) return
99
100     return next()
101   }
102 ]
103
104 const videoChannelsNameWithHostValidator = [
105   param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
106
107   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
108     logger.debug('Checking videoChannelsNameWithHostValidator parameters', { parameters: req.params })
109
110     if (areValidationErrors(req, res)) return
111
112     if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
113
114     return next()
115   }
116 ]
117
118 const localVideoChannelValidator = [
119   param('name').custom(isVideoChannelNameValid).withMessage('Should have a valid video channel name'),
120
121   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
122     logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params })
123
124     if (areValidationErrors(req, res)) return
125     if (!await doesLocalVideoChannelNameExist(req.params.name, res)) return
126
127     return next()
128   }
129 ]
130
131 const videoChannelStatsValidator = [
132   query('withStats')
133     .optional()
134     .customSanitizer(toBooleanOrNull)
135     .custom(isBooleanValid).withMessage('Should have a valid stats flag'),
136
137   (req: express.Request, res: express.Response, next: express.NextFunction) => {
138     if (areValidationErrors(req, res)) return
139     return next()
140   }
141 ]
142
143 // ---------------------------------------------------------------------------
144
145 export {
146   videoChannelsAddValidator,
147   videoChannelsUpdateValidator,
148   videoChannelsRemoveValidator,
149   videoChannelsNameWithHostValidator,
150   localVideoChannelValidator,
151   videoChannelStatsValidator
152 }
153
154 // ---------------------------------------------------------------------------
155
156 function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) {
157   if (videoChannel.Actor.isOwned() === false) {
158     res.status(403)
159               .json({ error: 'Cannot remove video channel of another server.' })
160               .end()
161
162     return false
163   }
164
165   // Check if the user can delete the video channel
166   // The user can delete it if s/he is an admin
167   // Or if s/he is the video channel's account
168   if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) {
169     res.status(403)
170               .json({ error: 'Cannot remove video channel of another user' })
171               .end()
172
173     return false
174   }
175
176   return true
177 }
178
179 async function checkVideoChannelIsNotTheLastOne (res: express.Response) {
180   const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
181
182   if (count <= 1) {
183     res.status(409)
184       .json({ error: 'Cannot remove the last channel of this user' })
185       .end()
186
187     return false
188   }
189
190   return true
191 }