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