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