Check activities host
[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, isUserBlockedReasonValid,
9   isUserDescriptionValid,
10   isUserDisplayNameValid,
11   isUserNSFWPolicyValid,
12   isUserPasswordValid,
13   isUserRoleValid,
14   isUserUsernameValid,
15   isUserVideoQuotaValid,
16   isUserVideoQuotaDailyValid
17 } from '../../helpers/custom-validators/users'
18 import { isVideoExist } from '../../helpers/custom-validators/videos'
19 import { logger } from '../../helpers/logger'
20 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
21 import { Redis } from '../../lib/redis'
22 import { UserModel } from '../../models/account/user'
23 import { areValidationErrors } from './utils'
24 import { ActorModel } from '../../models/activitypub/actor'
25 import { comparePassword } from '../../helpers/peertube-crypto'
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('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
118   body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
119   body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
120
121   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
122     logger.debug('Checking usersUpdate parameters', { parameters: req.body })
123
124     if (areValidationErrors(req, res)) return
125     if (!await checkUserIdExist(req.params.id, res)) return
126
127     const user = res.locals.user
128     if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
129       return res.status(400)
130         .send({ error: 'Cannot change root role.' })
131         .end()
132     }
133
134     return next()
135   }
136 ]
137
138 const usersUpdateMeValidator = [
139   body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
140   body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
141   body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
142   body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
143   body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
144   body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
145   body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
146
147   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
148     logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
149
150     if (req.body.password) {
151       if (!req.body.currentPassword) {
152         return res.status(400)
153                   .send({ error: 'currentPassword parameter is missing.' })
154                   .end()
155       }
156
157       const user: UserModel = res.locals.oauth.token.User
158
159       if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
160         return res.status(401)
161                   .send({ error: 'currentPassword is invalid.' })
162                   .end()
163       }
164     }
165
166     if (areValidationErrors(req, res)) return
167
168     return next()
169   }
170 ]
171
172 const usersGetValidator = [
173   param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
174
175   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
176     logger.debug('Checking usersGet parameters', { parameters: req.params })
177
178     if (areValidationErrors(req, res)) return
179     if (!await checkUserIdExist(req.params.id, res)) return
180
181     return next()
182   }
183 ]
184
185 const usersVideoRatingValidator = [
186   param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
187
188   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
189     logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
190
191     if (areValidationErrors(req, res)) return
192     if (!await isVideoExist(req.params.videoId, res, 'id')) return
193
194     return next()
195   }
196 ]
197
198 const ensureUserRegistrationAllowed = [
199   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
200     const allowed = await isSignupAllowed()
201     if (allowed === false) {
202       return res.status(403)
203                 .send({ error: 'User registration is not enabled or user limit is reached.' })
204                 .end()
205     }
206
207     return next()
208   }
209 ]
210
211 const ensureUserRegistrationAllowedForIP = [
212   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
213     const allowed = isSignupAllowedForCurrentIP(req.ip)
214
215     if (allowed === false) {
216       return res.status(403)
217                 .send({ error: 'You are not on a network authorized for registration.' })
218                 .end()
219     }
220
221     return next()
222   }
223 ]
224
225 const usersAskResetPasswordValidator = [
226   body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
227
228   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
229     logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
230
231     if (areValidationErrors(req, res)) return
232     const exists = await checkUserEmailExist(req.body.email, res, false)
233     if (!exists) {
234       logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
235       // Do not leak our emails
236       return res.status(204).end()
237     }
238
239     return next()
240   }
241 ]
242
243 const usersResetPasswordValidator = [
244   param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
245   body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
246   body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
247
248   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
249     logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
250
251     if (areValidationErrors(req, res)) return
252     if (!await checkUserIdExist(req.params.id, res)) return
253
254     const user = res.locals.user as UserModel
255     const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
256
257     if (redisVerificationString !== req.body.verificationString) {
258       return res
259         .status(403)
260         .send({ error: 'Invalid verification string.' })
261         .end()
262     }
263
264     return next()
265   }
266 ]
267
268 const usersAskSendVerifyEmailValidator = [
269   body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
270
271   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
272     logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
273
274     if (areValidationErrors(req, res)) return
275     const exists = await checkUserEmailExist(req.body.email, res, false)
276     if (!exists) {
277       logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
278       // Do not leak our emails
279       return res.status(204).end()
280     }
281
282     return next()
283   }
284 ]
285
286 const usersVerifyEmailValidator = [
287   param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
288   body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
289
290   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
291     logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
292
293     if (areValidationErrors(req, res)) return
294     if (!await checkUserIdExist(req.params.id, res)) return
295
296     const user = res.locals.user as UserModel
297     const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
298
299     if (redisVerificationString !== req.body.verificationString) {
300       return res
301         .status(403)
302         .send({ error: 'Invalid verification string.' })
303         .end()
304     }
305
306     return next()
307   }
308 ]
309
310 const userAutocompleteValidator = [
311   param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
312 ]
313
314 // ---------------------------------------------------------------------------
315
316 export {
317   usersAddValidator,
318   deleteMeValidator,
319   usersRegisterValidator,
320   usersBlockingValidator,
321   usersRemoveValidator,
322   usersUpdateValidator,
323   usersUpdateMeValidator,
324   usersVideoRatingValidator,
325   ensureUserRegistrationAllowed,
326   ensureUserRegistrationAllowedForIP,
327   usersGetValidator,
328   usersAskResetPasswordValidator,
329   usersResetPasswordValidator,
330   usersAskSendVerifyEmailValidator,
331   usersVerifyEmailValidator,
332   userAutocompleteValidator
333 }
334
335 // ---------------------------------------------------------------------------
336
337 function checkUserIdExist (id: number, res: express.Response) {
338   return checkUserExist(() => UserModel.loadById(id), res)
339 }
340
341 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
342   return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
343 }
344
345 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
346   const user = await UserModel.loadByUsernameOrEmail(username, email)
347
348   if (user) {
349     res.status(409)
350               .send({ error: 'User with this username or email already exists.' })
351               .end()
352     return false
353   }
354
355   const actor = await ActorModel.loadLocalByName(username)
356   if (actor) {
357     res.status(409)
358        .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
359        .end()
360     return false
361   }
362
363   return true
364 }
365
366 async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
367   const user = await finder()
368
369   if (!user) {
370     if (abortResponse === true) {
371       res.status(404)
372         .send({ error: 'User not found' })
373         .end()
374     }
375
376     return false
377   }
378
379   res.locals.user = user
380
381   return true
382 }