]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/users.ts
Add language filters in user preferences
[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, isUserVideoLanguages,
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('videoLanguages')
202 .optional()
203 .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'),
204 body('videosHistoryEnabled')
205 .optional()
206 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
207
208 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
209 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
210
211 if (req.body.password || req.body.email) {
212 if (!req.body.currentPassword) {
213 return res.status(400)
214 .send({ error: 'currentPassword parameter is missing.' })
215 .end()
216 }
217
218 const user = res.locals.oauth.token.User
219 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
220 return res.status(401)
221 .send({ error: 'currentPassword is invalid.' })
222 .end()
223 }
224 }
225
226 if (areValidationErrors(req, res)) return
227
228 return next()
229 }
230 ]
231
232 const usersGetValidator = [
233 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
234
235 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
236 logger.debug('Checking usersGet parameters', { parameters: req.params })
237
238 if (areValidationErrors(req, res)) return
239 if (!await checkUserIdExist(req.params.id, res)) return
240
241 return next()
242 }
243 ]
244
245 const usersVideoRatingValidator = [
246 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
247
248 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
249 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
250
251 if (areValidationErrors(req, res)) return
252 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
253
254 return next()
255 }
256 ]
257
258 const ensureUserRegistrationAllowed = [
259 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
260 const allowed = await isSignupAllowed()
261 if (allowed === false) {
262 return res.status(403)
263 .send({ error: 'User registration is not enabled or user limit is reached.' })
264 .end()
265 }
266
267 return next()
268 }
269 ]
270
271 const ensureUserRegistrationAllowedForIP = [
272 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
273 const allowed = isSignupAllowedForCurrentIP(req.ip)
274
275 if (allowed === false) {
276 return res.status(403)
277 .send({ error: 'You are not on a network authorized for registration.' })
278 .end()
279 }
280
281 return next()
282 }
283 ]
284
285 const usersAskResetPasswordValidator = [
286 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
287
288 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
289 logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
290
291 if (areValidationErrors(req, res)) return
292
293 const exists = await checkUserEmailExist(req.body.email, res, false)
294 if (!exists) {
295 logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
296 // Do not leak our emails
297 return res.status(204).end()
298 }
299
300 return next()
301 }
302 ]
303
304 const usersResetPasswordValidator = [
305 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
306 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
307 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
308
309 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
310 logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
311
312 if (areValidationErrors(req, res)) return
313 if (!await checkUserIdExist(req.params.id, res)) return
314
315 const user = res.locals.user
316 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
317
318 if (redisVerificationString !== req.body.verificationString) {
319 return res
320 .status(403)
321 .send({ error: 'Invalid verification string.' })
322 .end()
323 }
324
325 return next()
326 }
327 ]
328
329 const usersAskSendVerifyEmailValidator = [
330 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
331
332 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
333 logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
334
335 if (areValidationErrors(req, res)) return
336 const exists = await checkUserEmailExist(req.body.email, res, false)
337 if (!exists) {
338 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
339 // Do not leak our emails
340 return res.status(204).end()
341 }
342
343 return next()
344 }
345 ]
346
347 const usersVerifyEmailValidator = [
348 param('id')
349 .isInt().not().isEmpty().withMessage('Should have a valid id'),
350
351 body('verificationString')
352 .not().isEmpty().withMessage('Should have a valid verification string'),
353 body('isPendingEmail')
354 .optional()
355 .toBoolean(),
356
357 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
358 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
359
360 if (areValidationErrors(req, res)) return
361 if (!await checkUserIdExist(req.params.id, res)) return
362
363 const user = res.locals.user
364 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
365
366 if (redisVerificationString !== req.body.verificationString) {
367 return res
368 .status(403)
369 .send({ error: 'Invalid verification string.' })
370 .end()
371 }
372
373 return next()
374 }
375 ]
376
377 const userAutocompleteValidator = [
378 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
379 ]
380
381 const ensureAuthUserOwnsAccountValidator = [
382 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
383 const user = res.locals.oauth.token.User
384
385 if (res.locals.account.id !== user.Account.id) {
386 return res.status(403)
387 .send({ error: 'Only owner can access ratings list.' })
388 .end()
389 }
390
391 return next()
392 }
393 ]
394
395 // ---------------------------------------------------------------------------
396
397 export {
398 usersAddValidator,
399 deleteMeValidator,
400 usersRegisterValidator,
401 usersBlockingValidator,
402 usersRemoveValidator,
403 usersUpdateValidator,
404 usersUpdateMeValidator,
405 usersVideoRatingValidator,
406 ensureUserRegistrationAllowed,
407 ensureUserRegistrationAllowedForIP,
408 usersGetValidator,
409 usersAskResetPasswordValidator,
410 usersResetPasswordValidator,
411 usersAskSendVerifyEmailValidator,
412 usersVerifyEmailValidator,
413 userAutocompleteValidator,
414 ensureAuthUserOwnsAccountValidator
415 }
416
417 // ---------------------------------------------------------------------------
418
419 function checkUserIdExist (id: number, res: express.Response) {
420 return checkUserExist(() => UserModel.loadById(id), res)
421 }
422
423 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
424 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
425 }
426
427 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
428 const user = await UserModel.loadByUsernameOrEmail(username, email)
429
430 if (user) {
431 res.status(409)
432 .send({ error: 'User with this username or email already exists.' })
433 .end()
434 return false
435 }
436
437 const actor = await ActorModel.loadLocalByName(username)
438 if (actor) {
439 res.status(409)
440 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
441 .end()
442 return false
443 }
444
445 return true
446 }
447
448 async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
449 const user = await finder()
450
451 if (!user) {
452 if (abortResponse === true) {
453 res.status(404)
454 .send({ error: 'User not found' })
455 .end()
456 }
457
458 return false
459 }
460
461 res.locals.user = user
462
463 return true
464 }