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