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