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