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