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