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