]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/users.ts
add blocked filter in users list to filter banned users
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / users.ts
1 import * as Bluebird from 'bluebird'
2 import * as express from 'express'
3 import { body, param, query } from 'express-validator'
4 import { omit } from 'lodash'
5 import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
6 import {
7 isNoInstanceConfigWarningModal,
8 isNoWelcomeModal,
9 isUserAdminFlagsValid,
10 isUserAutoPlayNextVideoValid,
11 isUserAutoPlayVideoValid,
12 isUserBlockedReasonValid,
13 isUserDescriptionValid,
14 isUserDisplayNameValid,
15 isUserNSFWPolicyValid,
16 isUserPasswordValid,
17 isUserPasswordValidOrEmpty,
18 isUserRoleValid,
19 isUserUsernameValid,
20 isUserVideoLanguages,
21 isUserVideoQuotaDailyValid,
22 isUserVideoQuotaValid,
23 isUserVideosHistoryEnabledValid
24 } from '../../helpers/custom-validators/users'
25 import { logger } from '../../helpers/logger'
26 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
27 import { Redis } from '../../lib/redis'
28 import { UserModel } from '../../models/account/user'
29 import { areValidationErrors } from './utils'
30 import { ActorModel } from '../../models/activitypub/actor'
31 import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
32 import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
33 import { UserRegister } from '../../../shared/models/users/user-register.model'
34 import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
35 import { isThemeRegistered } from '../../lib/plugins/theme-utils'
36 import { doesVideoExist } from '../../helpers/middlewares'
37 import { UserRole } from '../../../shared/models/users'
38 import { MUserDefault } from '@server/types/models'
39 import { Hooks } from '@server/lib/plugins/hooks'
40
41 const usersListValidator = [
42 query('blocked')
43 .optional()
44 .customSanitizer(toBooleanOrNull)
45 .isBoolean().withMessage('Should be a valid boolean banned state'),
46
47 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
48 logger.debug('Checking usersList parameters', { parameters: req.query })
49
50 if (areValidationErrors(req, res)) return
51
52 return next()
53 }
54 ]
55
56 const usersAddValidator = [
57 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
58 body('password').custom(isUserPasswordValidOrEmpty).withMessage('Should have a valid password'),
59 body('email').isEmail().withMessage('Should have a valid email'),
60 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
61 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
62 body('role')
63 .customSanitizer(toIntOrNull)
64 .custom(isUserRoleValid).withMessage('Should have a valid role'),
65 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
66
67 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
68 logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
69
70 if (areValidationErrors(req, res)) return
71 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
72
73 const authUser = res.locals.oauth.token.User
74 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) {
75 return res.status(403)
76 .json({ error: 'You can only create users (and not administrators or moderators)' })
77 }
78
79 return next()
80 }
81 ]
82
83 const usersRegisterValidator = [
84 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
85 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
86 body('email').isEmail().withMessage('Should have a valid email'),
87 body('displayName')
88 .optional()
89 .custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
90
91 body('channel.name')
92 .optional()
93 .custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
94 body('channel.displayName')
95 .optional()
96 .custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
97
98 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
99 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
100
101 if (areValidationErrors(req, res)) return
102 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
103
104 const body: UserRegister = req.body
105 if (body.channel) {
106 if (!body.channel.name || !body.channel.displayName) {
107 return res.status(400)
108 .json({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
109 }
110
111 if (body.channel.name === body.username) {
112 return res.status(400)
113 .json({ error: 'Channel name cannot be the same as user username.' })
114 }
115
116 const existing = await ActorModel.loadLocalByName(body.channel.name)
117 if (existing) {
118 return res.status(409)
119 .json({ error: `Channel with name ${body.channel.name} already exists.` })
120 }
121 }
122
123 return next()
124 }
125 ]
126
127 const usersRemoveValidator = [
128 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
129
130 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
131 logger.debug('Checking usersRemove parameters', { parameters: req.params })
132
133 if (areValidationErrors(req, res)) return
134 if (!await checkUserIdExist(req.params.id, res)) return
135
136 const user = res.locals.user
137 if (user.username === 'root') {
138 return res.status(400)
139 .json({ error: 'Cannot remove the root user' })
140 }
141
142 return next()
143 }
144 ]
145
146 const usersBlockingValidator = [
147 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
148 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
149
150 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
151 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
152
153 if (areValidationErrors(req, res)) return
154 if (!await checkUserIdExist(req.params.id, res)) return
155
156 const user = res.locals.user
157 if (user.username === 'root') {
158 return res.status(400)
159 .json({ error: 'Cannot block the root user' })
160 }
161
162 return next()
163 }
164 ]
165
166 const deleteMeValidator = [
167 (req: express.Request, res: express.Response, next: express.NextFunction) => {
168 const user = res.locals.oauth.token.User
169 if (user.username === 'root') {
170 return res.status(400)
171 .json({ error: 'You cannot delete your root account.' })
172 .end()
173 }
174
175 return next()
176 }
177 ]
178
179 const usersUpdateValidator = [
180 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
181 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
182 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
183 body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
184 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
185 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
186 body('role')
187 .optional()
188 .customSanitizer(toIntOrNull)
189 .custom(isUserRoleValid).withMessage('Should have a valid role'),
190 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
191
192 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
193 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
194
195 if (areValidationErrors(req, res)) return
196 if (!await checkUserIdExist(req.params.id, res)) return
197
198 const user = res.locals.user
199 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
200 return res.status(400)
201 .json({ error: 'Cannot change root role.' })
202 }
203
204 return next()
205 }
206 ]
207
208 const usersUpdateMeValidator = [
209 body('displayName')
210 .optional()
211 .custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
212 body('description')
213 .optional()
214 .custom(isUserDescriptionValid).withMessage('Should have a valid description'),
215 body('currentPassword')
216 .optional()
217 .custom(isUserPasswordValid).withMessage('Should have a valid current password'),
218 body('password')
219 .optional()
220 .custom(isUserPasswordValid).withMessage('Should have a valid password'),
221 body('email')
222 .optional()
223 .isEmail().withMessage('Should have a valid email attribute'),
224 body('nsfwPolicy')
225 .optional()
226 .custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
227 body('autoPlayVideo')
228 .optional()
229 .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
230 body('videoLanguages')
231 .optional()
232 .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'),
233 body('videosHistoryEnabled')
234 .optional()
235 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
236 body('theme')
237 .optional()
238 .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'),
239 body('noInstanceConfigWarningModal')
240 .optional()
241 .custom(v => isNoInstanceConfigWarningModal(v)).withMessage('Should have a valid noInstanceConfigWarningModal boolean'),
242 body('noWelcomeModal')
243 .optional()
244 .custom(v => isNoWelcomeModal(v)).withMessage('Should have a valid noWelcomeModal boolean'),
245 body('autoPlayNextVideo')
246 .optional()
247 .custom(v => isUserAutoPlayNextVideoValid(v)).withMessage('Should have a valid autoPlayNextVideo boolean'),
248
249 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
250 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
251
252 const user = res.locals.oauth.token.User
253
254 if (req.body.password || req.body.email) {
255 if (user.pluginAuth !== null) {
256 return res.status(400)
257 .json({ error: 'You cannot update your email or password that is associated with an external auth system.' })
258 }
259
260 if (!req.body.currentPassword) {
261 return res.status(400)
262 .json({ error: 'currentPassword parameter is missing.' })
263 }
264
265 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
266 return res.status(401)
267 .json({ error: 'currentPassword is invalid.' })
268 }
269 }
270
271 if (areValidationErrors(req, res)) return
272
273 return next()
274 }
275 ]
276
277 const usersGetValidator = [
278 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
279 query('withStats').optional().isBoolean().withMessage('Should have a valid stats flag'),
280
281 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
282 logger.debug('Checking usersGet parameters', { parameters: req.params })
283
284 if (areValidationErrors(req, res)) return
285 if (!await checkUserIdExist(req.params.id, res, req.query.withStats)) return
286
287 return next()
288 }
289 ]
290
291 const usersVideoRatingValidator = [
292 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
293
294 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
295 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
296
297 if (areValidationErrors(req, res)) return
298 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
299
300 return next()
301 }
302 ]
303
304 const ensureUserRegistrationAllowed = [
305 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
306 const allowedParams = {
307 body: req.body,
308 ip: req.ip
309 }
310
311 const allowedResult = await Hooks.wrapPromiseFun(
312 isSignupAllowed,
313 allowedParams,
314 'filter:api.user.signup.allowed.result'
315 )
316
317 if (allowedResult.allowed === false) {
318 return res.status(403)
319 .json({ error: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' })
320 }
321
322 return next()
323 }
324 ]
325
326 const ensureUserRegistrationAllowedForIP = [
327 (req: express.Request, res: express.Response, next: express.NextFunction) => {
328 const allowed = isSignupAllowedForCurrentIP(req.ip)
329
330 if (allowed === false) {
331 return res.status(403)
332 .json({ error: 'You are not on a network authorized for registration.' })
333 }
334
335 return next()
336 }
337 ]
338
339 const usersAskResetPasswordValidator = [
340 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
341
342 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
343 logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
344
345 if (areValidationErrors(req, res)) return
346
347 const exists = await checkUserEmailExist(req.body.email, res, false)
348 if (!exists) {
349 logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
350 // Do not leak our emails
351 return res.status(204).end()
352 }
353
354 return next()
355 }
356 ]
357
358 const usersResetPasswordValidator = [
359 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
360 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
361 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
362
363 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
364 logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
365
366 if (areValidationErrors(req, res)) return
367 if (!await checkUserIdExist(req.params.id, res)) return
368
369 const user = res.locals.user
370 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
371
372 if (redisVerificationString !== req.body.verificationString) {
373 return res
374 .status(403)
375 .json({ error: 'Invalid verification string.' })
376 }
377
378 return next()
379 }
380 ]
381
382 const usersAskSendVerifyEmailValidator = [
383 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
384
385 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
386 logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
387
388 if (areValidationErrors(req, res)) return
389 const exists = await checkUserEmailExist(req.body.email, res, false)
390 if (!exists) {
391 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
392 // Do not leak our emails
393 return res.status(204).end()
394 }
395
396 return next()
397 }
398 ]
399
400 const usersVerifyEmailValidator = [
401 param('id')
402 .isInt().not().isEmpty().withMessage('Should have a valid id'),
403
404 body('verificationString')
405 .not().isEmpty().withMessage('Should have a valid verification string'),
406 body('isPendingEmail')
407 .optional()
408 .customSanitizer(toBooleanOrNull),
409
410 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
411 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
412
413 if (areValidationErrors(req, res)) return
414 if (!await checkUserIdExist(req.params.id, res)) return
415
416 const user = res.locals.user
417 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
418
419 if (redisVerificationString !== req.body.verificationString) {
420 return res
421 .status(403)
422 .json({ error: 'Invalid verification string.' })
423 }
424
425 return next()
426 }
427 ]
428
429 const userAutocompleteValidator = [
430 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
431 ]
432
433 const ensureAuthUserOwnsAccountValidator = [
434 (req: express.Request, res: express.Response, next: express.NextFunction) => {
435 const user = res.locals.oauth.token.User
436
437 if (res.locals.account.id !== user.Account.id) {
438 return res.status(403)
439 .json({ error: 'Only owner can access ratings list.' })
440 }
441
442 return next()
443 }
444 ]
445
446 const ensureCanManageUser = [
447 (req: express.Request, res: express.Response, next: express.NextFunction) => {
448 const authUser = res.locals.oauth.token.User
449 const onUser = res.locals.user
450
451 if (authUser.role === UserRole.ADMINISTRATOR) return next()
452 if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next()
453
454 return res.status(403)
455 .json({ error: 'A moderator can only manager users.' })
456 }
457 ]
458
459 // ---------------------------------------------------------------------------
460
461 export {
462 usersListValidator,
463 usersAddValidator,
464 deleteMeValidator,
465 usersRegisterValidator,
466 usersBlockingValidator,
467 usersRemoveValidator,
468 usersUpdateValidator,
469 usersUpdateMeValidator,
470 usersVideoRatingValidator,
471 ensureUserRegistrationAllowed,
472 ensureUserRegistrationAllowedForIP,
473 usersGetValidator,
474 usersAskResetPasswordValidator,
475 usersResetPasswordValidator,
476 usersAskSendVerifyEmailValidator,
477 usersVerifyEmailValidator,
478 userAutocompleteValidator,
479 ensureAuthUserOwnsAccountValidator,
480 ensureCanManageUser
481 }
482
483 // ---------------------------------------------------------------------------
484
485 function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
486 const id = parseInt(idArg + '', 10)
487 return checkUserExist(() => UserModel.loadById(id, withStats), res)
488 }
489
490 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
491 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
492 }
493
494 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
495 const user = await UserModel.loadByUsernameOrEmail(username, email)
496
497 if (user) {
498 res.status(409)
499 .json({ error: 'User with this username or email already exists.' })
500 return false
501 }
502
503 const actor = await ActorModel.loadLocalByName(username)
504 if (actor) {
505 res.status(409)
506 .json({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
507 return false
508 }
509
510 return true
511 }
512
513 async function checkUserExist (finder: () => Bluebird<MUserDefault>, res: express.Response, abortResponse = true) {
514 const user = await finder()
515
516 if (!user) {
517 if (abortResponse === true) {
518 res.status(404)
519 .json({ error: 'User not found' })
520 }
521
522 return false
523 }
524
525 res.locals.user = user
526
527 return true
528 }