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