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