]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/users.ts
enable email verification by admin (#1348)
[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, 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('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
148 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
149 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
150
151 if (req.body.password) {
152 if (!req.body.currentPassword) {
153 return res.status(400)
154 .send({ error: 'currentPassword parameter is missing.' })
155 .end()
156 }
157
158 const user: UserModel = res.locals.oauth.token.User
159
160 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
161 return res.status(401)
162 .send({ error: 'currentPassword is invalid.' })
163 .end()
164 }
165 }
166
167 if (areValidationErrors(req, res)) return
168
169 return next()
170 }
171 ]
172
173 const usersGetValidator = [
174 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
175
176 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
177 logger.debug('Checking usersGet parameters', { parameters: req.params })
178
179 if (areValidationErrors(req, res)) return
180 if (!await checkUserIdExist(req.params.id, res)) return
181
182 return next()
183 }
184 ]
185
186 const usersVideoRatingValidator = [
187 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
188
189 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
190 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
191
192 if (areValidationErrors(req, res)) return
193 if (!await isVideoExist(req.params.videoId, res, 'id')) return
194
195 return next()
196 }
197 ]
198
199 const ensureUserRegistrationAllowed = [
200 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
201 const allowed = await isSignupAllowed()
202 if (allowed === false) {
203 return res.status(403)
204 .send({ error: 'User registration is not enabled or user limit is reached.' })
205 .end()
206 }
207
208 return next()
209 }
210 ]
211
212 const ensureUserRegistrationAllowedForIP = [
213 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
214 const allowed = isSignupAllowedForCurrentIP(req.ip)
215
216 if (allowed === false) {
217 return res.status(403)
218 .send({ error: 'You are not on a network authorized for registration.' })
219 .end()
220 }
221
222 return next()
223 }
224 ]
225
226 const usersAskResetPasswordValidator = [
227 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
228
229 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
230 logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
231
232 if (areValidationErrors(req, res)) return
233 const exists = await checkUserEmailExist(req.body.email, res, false)
234 if (!exists) {
235 logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
236 // Do not leak our emails
237 return res.status(204).end()
238 }
239
240 return next()
241 }
242 ]
243
244 const usersResetPasswordValidator = [
245 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
246 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
247 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
248
249 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
250 logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
251
252 if (areValidationErrors(req, res)) return
253 if (!await checkUserIdExist(req.params.id, res)) return
254
255 const user = res.locals.user as UserModel
256 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
257
258 if (redisVerificationString !== req.body.verificationString) {
259 return res
260 .status(403)
261 .send({ error: 'Invalid verification string.' })
262 .end()
263 }
264
265 return next()
266 }
267 ]
268
269 const usersAskSendVerifyEmailValidator = [
270 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
271
272 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
273 logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
274
275 if (areValidationErrors(req, res)) return
276 const exists = await checkUserEmailExist(req.body.email, res, false)
277 if (!exists) {
278 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
279 // Do not leak our emails
280 return res.status(204).end()
281 }
282
283 return next()
284 }
285 ]
286
287 const usersVerifyEmailValidator = [
288 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
289 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
290
291 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
292 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
293
294 if (areValidationErrors(req, res)) return
295 if (!await checkUserIdExist(req.params.id, res)) return
296
297 const user = res.locals.user as UserModel
298 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
299
300 if (redisVerificationString !== req.body.verificationString) {
301 return res
302 .status(403)
303 .send({ error: 'Invalid verification string.' })
304 .end()
305 }
306
307 return next()
308 }
309 ]
310
311 const userAutocompleteValidator = [
312 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
313 ]
314
315 // ---------------------------------------------------------------------------
316
317 export {
318 usersAddValidator,
319 deleteMeValidator,
320 usersRegisterValidator,
321 usersBlockingValidator,
322 usersRemoveValidator,
323 usersUpdateValidator,
324 usersUpdateMeValidator,
325 usersVideoRatingValidator,
326 ensureUserRegistrationAllowed,
327 ensureUserRegistrationAllowedForIP,
328 usersGetValidator,
329 usersAskResetPasswordValidator,
330 usersResetPasswordValidator,
331 usersAskSendVerifyEmailValidator,
332 usersVerifyEmailValidator,
333 userAutocompleteValidator
334 }
335
336 // ---------------------------------------------------------------------------
337
338 function checkUserIdExist (id: number, res: express.Response) {
339 return checkUserExist(() => UserModel.loadById(id), res)
340 }
341
342 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
343 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
344 }
345
346 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
347 const user = await UserModel.loadByUsernameOrEmail(username, email)
348
349 if (user) {
350 res.status(409)
351 .send({ error: 'User with this username or email already exists.' })
352 .end()
353 return false
354 }
355
356 const actor = await ActorModel.loadLocalByName(username)
357 if (actor) {
358 res.status(409)
359 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
360 .end()
361 return false
362 }
363
364 return true
365 }
366
367 async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
368 const user = await finder()
369
370 if (!user) {
371 if (abortResponse === true) {
372 res.status(404)
373 .send({ error: 'User not found' })
374 .end()
375 }
376
377 return false
378 }
379
380 res.locals.user = user
381
382 return true
383 }