]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/users.ts
Handle email update on server
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / users.ts
1 import * as Bluebird from 'bluebird'
2 import * as express from 'express'
3 import 'express-validator'
4 import { body, param } from 'express-validator/check'
5 import { omit } from 'lodash'
6 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
7 import {
8 isUserAdminFlagsValid,
9 isUserAutoPlayVideoValid,
10 isUserBlockedReasonValid,
11 isUserDescriptionValid,
12 isUserDisplayNameValid,
13 isUserNSFWPolicyValid,
14 isUserPasswordValid,
15 isUserRoleValid,
16 isUserUsernameValid,
17 isUserVideoQuotaDailyValid,
18 isUserVideoQuotaValid,
19 isUserVideosHistoryEnabledValid
20 } from '../../helpers/custom-validators/users'
21 import { doesVideoExist } from '../../helpers/custom-validators/videos'
22 import { logger } from '../../helpers/logger'
23 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
24 import { Redis } from '../../lib/redis'
25 import { UserModel } from '../../models/account/user'
26 import { areValidationErrors } from './utils'
27 import { ActorModel } from '../../models/activitypub/actor'
28 import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
29 import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
30 import { UserRegister } from '../../../shared/models/users/user-register.model'
31
32 const usersAddValidator = [
33 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
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'),
37 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
38 body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
39 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
40
41 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
42 logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
43
44 if (areValidationErrors(req, res)) return
45 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
46
47 return next()
48 }
49 ]
50
51 const 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'),
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'),
65
66 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
67 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
68
69 if (areValidationErrors(req, res)) return
70 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
71
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
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
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
94 return next()
95 }
96 ]
97
98 const usersRemoveValidator = [
99 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
100
101 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
102 logger.debug('Checking usersRemove parameters', { parameters: req.params })
103
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()
115 }
116 ]
117
118 const usersBlockingValidator = [
119 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
120 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
121
122 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
123 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
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
139 const deleteMeValidator = [
140 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
141 const user = res.locals.oauth.token.User
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
152 const usersUpdateValidator = [
153 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
154 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
155 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
156 body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
157 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
158 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
159 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
160 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
161
162 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
163 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
164
165 if (areValidationErrors(req, res)) return
166 if (!await checkUserIdExist(req.params.id, res)) return
167
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
175 return next()
176 }
177 ]
178
179 const usersUpdateMeValidator = [
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'),
201 body('videosHistoryEnabled')
202 .optional()
203 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
204
205 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
206 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
207
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
215 const user = res.locals.oauth.token.User
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
223 if (areValidationErrors(req, res)) return
224
225 return next()
226 }
227 ]
228
229 const usersGetValidator = [
230 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
231
232 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
233 logger.debug('Checking usersGet parameters', { parameters: req.params })
234
235 if (areValidationErrors(req, res)) return
236 if (!await checkUserIdExist(req.params.id, res)) return
237
238 return next()
239 }
240 ]
241
242 const usersVideoRatingValidator = [
243 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
244
245 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
246 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
247
248 if (areValidationErrors(req, res)) return
249 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
250
251 return next()
252 }
253 ]
254
255 const ensureUserRegistrationAllowed = [
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()
265 }
266 ]
267
268 const 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
282 const 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
289
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
301 const 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
312 const user = res.locals.user
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.' })
319 .end()
320 }
321
322 return next()
323 }
324 ]
325
326 const 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
344 const usersVerifyEmailValidator = [
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(),
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
360 const user = res.locals.user
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
374 const userAutocompleteValidator = [
375 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
376 ]
377
378 const 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
392 // ---------------------------------------------------------------------------
393
394 export {
395 usersAddValidator,
396 deleteMeValidator,
397 usersRegisterValidator,
398 usersBlockingValidator,
399 usersRemoveValidator,
400 usersUpdateValidator,
401 usersUpdateMeValidator,
402 usersVideoRatingValidator,
403 ensureUserRegistrationAllowed,
404 ensureUserRegistrationAllowedForIP,
405 usersGetValidator,
406 usersAskResetPasswordValidator,
407 usersResetPasswordValidator,
408 usersAskSendVerifyEmailValidator,
409 usersVerifyEmailValidator,
410 userAutocompleteValidator,
411 ensureAuthUserOwnsAccountValidator
412 }
413
414 // ---------------------------------------------------------------------------
415
416 function checkUserIdExist (id: number, res: express.Response) {
417 return checkUserExist(() => UserModel.loadById(id), res)
418 }
419
420 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
421 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
422 }
423
424 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
425 const user = await UserModel.loadByUsernameOrEmail(username, email)
426
427 if (user) {
428 res.status(409)
429 .send({ error: 'User with this username or email already exists.' })
430 .end()
431 return false
432 }
433
434 const actor = await ActorModel.loadLocalByName(username)
435 if (actor) {
436 res.status(409)
437 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
438 .end()
439 return false
440 }
441
442 return true
443 }
444
445 async 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 }