]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/users.ts
Fix socket notification with multiple user tabs
[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'),
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
a95a4cc8
C
50 const authUser = res.locals.oauth.token.User
51 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) {
52 return res.status(403)
53 .json({ error: 'You can only create users (and not administrators or moderators' })
54 }
55
a2431b7d 56 return next()
b60e5f38
C
57 }
58]
6fcd19ba 59
b60e5f38
C
60const usersRegisterValidator = [
61 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
62 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
63 body('email').isEmail().withMessage('Should have a valid email'),
1f20622f
C
64 body('displayName')
65 .optional()
66 .custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
67
68 body('channel.name')
69 .optional()
70 .custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
71 body('channel.displayName')
72 .optional()
73 .custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
77a5501f 74
a2431b7d 75 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 76 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
77a5501f 77
a2431b7d
C
78 if (areValidationErrors(req, res)) return
79 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
80
e590b4a5
C
81 const body: UserRegister = req.body
82 if (body.channel) {
83 if (!body.channel.name || !body.channel.displayName) {
84 return res.status(400)
a95a4cc8 85 .json({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
e590b4a5
C
86 }
87
1d5342ab
C
88 if (body.channel.name === body.username) {
89 return res.status(400)
a95a4cc8 90 .json({ error: 'Channel name cannot be the same than user username.' })
1d5342ab
C
91 }
92
e590b4a5
C
93 const existing = await ActorModel.loadLocalByName(body.channel.name)
94 if (existing) {
95 return res.status(409)
a95a4cc8 96 .json({ error: `Channel with name ${body.channel.name} already exists.` })
e590b4a5
C
97 }
98 }
99
a2431b7d 100 return next()
b60e5f38
C
101 }
102]
9bd26629 103
b60e5f38
C
104const usersRemoveValidator = [
105 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
9bd26629 106
a2431b7d 107 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 108 logger.debug('Checking usersRemove parameters', { parameters: req.params })
9bd26629 109
a2431b7d
C
110 if (areValidationErrors(req, res)) return
111 if (!await checkUserIdExist(req.params.id, res)) return
112
113 const user = res.locals.user
114 if (user.username === 'root') {
115 return res.status(400)
a95a4cc8 116 .json({ error: 'Cannot remove the root user' })
a2431b7d
C
117 }
118
119 return next()
b60e5f38
C
120 }
121]
8094a898 122
e6921918
C
123const usersBlockingValidator = [
124 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
eacb25c4 125 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
e6921918
C
126
127 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
eacb25c4 128 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
e6921918
C
129
130 if (areValidationErrors(req, res)) return
131 if (!await checkUserIdExist(req.params.id, res)) return
132
133 const user = res.locals.user
134 if (user.username === 'root') {
135 return res.status(400)
a95a4cc8 136 .json({ error: 'Cannot block the root user' })
e6921918
C
137 }
138
139 return next()
140 }
141]
142
92b9d60c
C
143const deleteMeValidator = [
144 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
dae86118 145 const user = res.locals.oauth.token.User
92b9d60c
C
146 if (user.username === 'root') {
147 return res.status(400)
a95a4cc8 148 .json({ error: 'You cannot delete your root account.' })
92b9d60c
C
149 .end()
150 }
151
152 return next()
153 }
154]
155
b60e5f38
C
156const usersUpdateValidator = [
157 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
b426edd4 158 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
b60e5f38 159 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
fc2ec87a 160 body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
b60e5f38 161 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
bee0abff 162 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
954605a8 163 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
1eddc9a7 164 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
8094a898 165
a2431b7d 166 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 167 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
9bd26629 168
a2431b7d
C
169 if (areValidationErrors(req, res)) return
170 if (!await checkUserIdExist(req.params.id, res)) return
171
f8b8c36b
C
172 const user = res.locals.user
173 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
174 return res.status(400)
a95a4cc8 175 .json({ error: 'Cannot change root role.' })
f8b8c36b
C
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)
a95a4cc8 220 .json({ error: 'currentPassword parameter is missing.' })
a890d1e0
C
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)
a95a4cc8 227 .json({ error: 'currentPassword is invalid.' })
a890d1e0
C
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)
a95a4cc8 268 .json({ error: 'User registration is not enabled or user limit is reached.' })
a2431b7d
C
269 }
270
271 return next()
b60e5f38
C
272 }
273]
291e8d3e 274
ff2c1fe8
RK
275const ensureUserRegistrationAllowedForIP = [
276 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
277 const allowed = isSignupAllowedForCurrentIP(req.ip)
278
279 if (allowed === false) {
280 return res.status(403)
a95a4cc8 281 .json({ error: 'You are not on a network authorized for registration.' })
ff2c1fe8
RK
282 }
283
284 return next()
285 }
286]
287
ecb4e35f
C
288const usersAskResetPasswordValidator = [
289 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
290
291 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
292 logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
293
294 if (areValidationErrors(req, res)) return
b426edd4 295
ecb4e35f
C
296 const exists = await checkUserEmailExist(req.body.email, res, false)
297 if (!exists) {
298 logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
299 // Do not leak our emails
300 return res.status(204).end()
301 }
302
303 return next()
304 }
305]
306
307const usersResetPasswordValidator = [
308 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
309 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
310 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
311
312 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
313 logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
314
315 if (areValidationErrors(req, res)) return
316 if (!await checkUserIdExist(req.params.id, res)) return
317
dae86118 318 const user = res.locals.user
ecb4e35f
C
319 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
320
321 if (redisVerificationString !== req.body.verificationString) {
322 return res
323 .status(403)
a95a4cc8 324 .json({ error: 'Invalid verification string.' })
ecb4e35f
C
325 }
326
327 return next()
328 }
329]
330
d9eaee39
JM
331const usersAskSendVerifyEmailValidator = [
332 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
333
334 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
335 logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
336
337 if (areValidationErrors(req, res)) return
338 const exists = await checkUserEmailExist(req.body.email, res, false)
339 if (!exists) {
340 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
341 // Do not leak our emails
342 return res.status(204).end()
343 }
344
345 return next()
346 }
347]
348
349const usersVerifyEmailValidator = [
d1ab89de
C
350 param('id')
351 .isInt().not().isEmpty().withMessage('Should have a valid id'),
352
353 body('verificationString')
354 .not().isEmpty().withMessage('Should have a valid verification string'),
355 body('isPendingEmail')
356 .optional()
2b65c4e5 357 .customSanitizer(toBooleanOrNull),
d9eaee39
JM
358
359 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
360 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
361
362 if (areValidationErrors(req, res)) return
363 if (!await checkUserIdExist(req.params.id, res)) return
364
dae86118 365 const user = res.locals.user
d9eaee39
JM
366 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
367
368 if (redisVerificationString !== req.body.verificationString) {
369 return res
370 .status(403)
a95a4cc8 371 .json({ error: 'Invalid verification string.' })
d9eaee39
JM
372 }
373
374 return next()
375 }
376]
377
74d63469
GR
378const userAutocompleteValidator = [
379 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
380]
381
c100a614
YB
382const ensureAuthUserOwnsAccountValidator = [
383 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
384 const user = res.locals.oauth.token.User
385
386 if (res.locals.account.id !== user.Account.id) {
387 return res.status(403)
a95a4cc8 388 .json({ error: 'Only owner can access ratings list.' })
c100a614
YB
389 }
390
391 return next()
392 }
393]
394
a95a4cc8
C
395const ensureCanManageUser = [
396 (req: express.Request, res: express.Response, next: express.NextFunction) => {
397 const authUser = res.locals.oauth.token.User
398 const onUser = res.locals.user
399
400 if (authUser.role === UserRole.ADMINISTRATOR) return next()
401 if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next()
402
403 return res.status(403)
404 .json({ error: 'A moderator can only manager users.' })
405 }
406]
407
9bd26629
C
408// ---------------------------------------------------------------------------
409
65fcc311
C
410export {
411 usersAddValidator,
92b9d60c 412 deleteMeValidator,
77a5501f 413 usersRegisterValidator,
e6921918 414 usersBlockingValidator,
65fcc311
C
415 usersRemoveValidator,
416 usersUpdateValidator,
8094a898 417 usersUpdateMeValidator,
291e8d3e 418 usersVideoRatingValidator,
8094a898 419 ensureUserRegistrationAllowed,
ff2c1fe8 420 ensureUserRegistrationAllowedForIP,
c5911fd3 421 usersGetValidator,
ecb4e35f 422 usersAskResetPasswordValidator,
d9eaee39
JM
423 usersResetPasswordValidator,
424 usersAskSendVerifyEmailValidator,
74d63469 425 usersVerifyEmailValidator,
c100a614 426 userAutocompleteValidator,
a95a4cc8
C
427 ensureAuthUserOwnsAccountValidator,
428 ensureCanManageUser
8094a898
C
429}
430
431// ---------------------------------------------------------------------------
432
ecb4e35f
C
433function checkUserIdExist (id: number, res: express.Response) {
434 return checkUserExist(() => UserModel.loadById(id), res)
435}
a2431b7d 436
ecb4e35f
C
437function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
438 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
65fcc311 439}
77a5501f 440
a2431b7d 441async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
3fd3ab2d 442 const user = await UserModel.loadByUsernameOrEmail(username, email)
a2431b7d
C
443
444 if (user) {
445 res.status(409)
a95a4cc8 446 .json({ error: 'User with this username or email already exists.' })
a2431b7d
C
447 return false
448 }
449
2ef6a063
C
450 const actor = await ActorModel.loadLocalByName(username)
451 if (actor) {
452 res.status(409)
a95a4cc8 453 .json({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
2ef6a063
C
454 return false
455 }
456
a2431b7d 457 return true
77a5501f 458}
ecb4e35f
C
459
460async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
461 const user = await finder()
462
463 if (!user) {
464 if (abortResponse === true) {
465 res.status(404)
a95a4cc8 466 .json({ error: 'User not found' })
ecb4e35f
C
467 }
468
469 return false
470 }
471
472 res.locals.user = user
473
474 return true
475}