]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/users.ts
Users can change ownership of their video [#510] (#888)
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / users.ts
1 import * as Bluebird from 'bluebird'
2 import * as express from 'express'
3 import 'express-validator'
4 import { body, param } from 'express-validator/check'
5 import { omit } from 'lodash'
6 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
7 import {
8 isUserAutoPlayVideoValid, isUserBlockedReasonValid,
9 isUserDescriptionValid,
10 isUserDisplayNameValid,
11 isUserNSFWPolicyValid,
12 isUserPasswordValid,
13 isUserRoleValid,
14 isUserUsernameValid,
15 isUserVideoQuotaValid,
16 isUserVideoQuotaDailyValid
17 } from '../../helpers/custom-validators/users'
18 import { isVideoExist } from '../../helpers/custom-validators/videos'
19 import { logger } from '../../helpers/logger'
20 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
21 import { Redis } from '../../lib/redis'
22 import { UserModel } from '../../models/account/user'
23 import { areValidationErrors } from './utils'
24 import { ActorModel } from '../../models/activitypub/actor'
25
26 const usersAddValidator = [
27 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
28 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
29 body('email').isEmail().withMessage('Should have a valid email'),
30 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
31 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
32 body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
33
34 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
35 logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
36
37 if (areValidationErrors(req, res)) return
38 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
39
40 return next()
41 }
42 ]
43
44 const usersRegisterValidator = [
45 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
46 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
47 body('email').isEmail().withMessage('Should have a valid email'),
48
49 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
50 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
51
52 if (areValidationErrors(req, res)) return
53 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
54
55 return next()
56 }
57 ]
58
59 const usersRemoveValidator = [
60 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
61
62 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
63 logger.debug('Checking usersRemove parameters', { parameters: req.params })
64
65 if (areValidationErrors(req, res)) return
66 if (!await checkUserIdExist(req.params.id, res)) return
67
68 const user = res.locals.user
69 if (user.username === 'root') {
70 return res.status(400)
71 .send({ error: 'Cannot remove the root user' })
72 .end()
73 }
74
75 return next()
76 }
77 ]
78
79 const usersBlockingValidator = [
80 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
81 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
82
83 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
84 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
85
86 if (areValidationErrors(req, res)) return
87 if (!await checkUserIdExist(req.params.id, res)) return
88
89 const user = res.locals.user
90 if (user.username === 'root') {
91 return res.status(400)
92 .send({ error: 'Cannot block the root user' })
93 .end()
94 }
95
96 return next()
97 }
98 ]
99
100 const deleteMeValidator = [
101 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
102 const user: UserModel = res.locals.oauth.token.User
103 if (user.username === 'root') {
104 return res.status(400)
105 .send({ error: 'You cannot delete your root account.' })
106 .end()
107 }
108
109 return next()
110 }
111 ]
112
113 const usersUpdateValidator = [
114 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
115 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
116 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
117 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
118 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
119
120 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
121 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
122
123 if (areValidationErrors(req, res)) return
124 if (!await checkUserIdExist(req.params.id, res)) return
125
126 const user = res.locals.user
127 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
128 return res.status(400)
129 .send({ error: 'Cannot change root role.' })
130 .end()
131 }
132
133 return next()
134 }
135 ]
136
137 const usersUpdateMeValidator = [
138 body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
139 body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
140 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
141 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
142 body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
143 body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
144
145 (req: express.Request, res: express.Response, next: express.NextFunction) => {
146 // TODO: Add old password verification
147 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
148
149 if (areValidationErrors(req, res)) return
150
151 return next()
152 }
153 ]
154
155 const usersGetValidator = [
156 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
157
158 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
159 logger.debug('Checking usersGet parameters', { parameters: req.params })
160
161 if (areValidationErrors(req, res)) return
162 if (!await checkUserIdExist(req.params.id, res)) return
163
164 return next()
165 }
166 ]
167
168 const usersVideoRatingValidator = [
169 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
170
171 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
172 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
173
174 if (areValidationErrors(req, res)) return
175 if (!await isVideoExist(req.params.videoId, res)) return
176
177 return next()
178 }
179 ]
180
181 const ensureUserRegistrationAllowed = [
182 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
183 const allowed = await isSignupAllowed()
184 if (allowed === false) {
185 return res.status(403)
186 .send({ error: 'User registration is not enabled or user limit is reached.' })
187 .end()
188 }
189
190 return next()
191 }
192 ]
193
194 const ensureUserRegistrationAllowedForIP = [
195 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
196 const allowed = isSignupAllowedForCurrentIP(req.ip)
197
198 if (allowed === false) {
199 return res.status(403)
200 .send({ error: 'You are not on a network authorized for registration.' })
201 .end()
202 }
203
204 return next()
205 }
206 ]
207
208 const usersAskResetPasswordValidator = [
209 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
210
211 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
212 logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
213
214 if (areValidationErrors(req, res)) return
215 const exists = await checkUserEmailExist(req.body.email, res, false)
216 if (!exists) {
217 logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
218 // Do not leak our emails
219 return res.status(204).end()
220 }
221
222 return next()
223 }
224 ]
225
226 const usersResetPasswordValidator = [
227 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
228 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
229 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
230
231 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
232 logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
233
234 if (areValidationErrors(req, res)) return
235 if (!await checkUserIdExist(req.params.id, res)) return
236
237 const user = res.locals.user as UserModel
238 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
239
240 if (redisVerificationString !== req.body.verificationString) {
241 return res
242 .status(403)
243 .send({ error: 'Invalid verification string.' })
244 .end()
245 }
246
247 return next()
248 }
249 ]
250
251 const usersAskSendVerifyEmailValidator = [
252 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
253
254 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
255 logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
256
257 if (areValidationErrors(req, res)) return
258 const exists = await checkUserEmailExist(req.body.email, res, false)
259 if (!exists) {
260 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
261 // Do not leak our emails
262 return res.status(204).end()
263 }
264
265 return next()
266 }
267 ]
268
269 const usersVerifyEmailValidator = [
270 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
271 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
272
273 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
274 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
275
276 if (areValidationErrors(req, res)) return
277 if (!await checkUserIdExist(req.params.id, res)) return
278
279 const user = res.locals.user as UserModel
280 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
281
282 if (redisVerificationString !== req.body.verificationString) {
283 return res
284 .status(403)
285 .send({ error: 'Invalid verification string.' })
286 .end()
287 }
288
289 return next()
290 }
291 ]
292
293 const userAutocompleteValidator = [
294 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
295 ]
296
297 // ---------------------------------------------------------------------------
298
299 export {
300 usersAddValidator,
301 deleteMeValidator,
302 usersRegisterValidator,
303 usersBlockingValidator,
304 usersRemoveValidator,
305 usersUpdateValidator,
306 usersUpdateMeValidator,
307 usersVideoRatingValidator,
308 ensureUserRegistrationAllowed,
309 ensureUserRegistrationAllowedForIP,
310 usersGetValidator,
311 usersAskResetPasswordValidator,
312 usersResetPasswordValidator,
313 usersAskSendVerifyEmailValidator,
314 usersVerifyEmailValidator,
315 userAutocompleteValidator
316 }
317
318 // ---------------------------------------------------------------------------
319
320 function checkUserIdExist (id: number, res: express.Response) {
321 return checkUserExist(() => UserModel.loadById(id), res)
322 }
323
324 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
325 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
326 }
327
328 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
329 const user = await UserModel.loadByUsernameOrEmail(username, email)
330
331 if (user) {
332 res.status(409)
333 .send({ error: 'User with this username or email already exists.' })
334 .end()
335 return false
336 }
337
338 const actor = await ActorModel.loadLocalByName(username)
339 if (actor) {
340 res.status(409)
341 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
342 .end()
343 return false
344 }
345
346 return true
347 }
348
349 async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
350 const user = await finder()
351
352 if (!user) {
353 if (abortResponse === true) {
354 res.status(404)
355 .send({ error: 'User not found' })
356 .end()
357 }
358
359 return false
360 }
361
362 res.locals.user = user
363
364 return true
365 }