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