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