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