Cleanup reset user password by admin
[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('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
117   body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
118   body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
119   body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
120   body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
121   body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
122
123   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
124     logger.debug('Checking usersUpdate parameters', { parameters: req.body })
125
126     if (areValidationErrors(req, res)) return
127     if (!await checkUserIdExist(req.params.id, res)) return
128
129     const user = res.locals.user
130     if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
131       return res.status(400)
132         .send({ error: 'Cannot change root role.' })
133         .end()
134     }
135
136     return next()
137   }
138 ]
139
140 const usersUpdateMeValidator = [
141   body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
142   body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
143   body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
144   body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
145   body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
146   body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
147   body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
148   body('videosHistoryEnabled')
149     .optional()
150     .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
151
152   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
153     logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
154
155     if (req.body.password) {
156       if (!req.body.currentPassword) {
157         return res.status(400)
158                   .send({ error: 'currentPassword parameter is missing.' })
159                   .end()
160       }
161
162       const user: UserModel = res.locals.oauth.token.User
163
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 isVideoExist(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 as UserModel
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 as UserModel
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 }