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