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