]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/users.ts
enable email verification by admin (#1348)
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / users.ts
CommitLineData
ecb4e35f 1import * as Bluebird from 'bluebird'
69818c93 2import * as express from 'express'
a2431b7d
C
3import 'express-validator'
4import { body, param } from 'express-validator/check'
ecb4e35f 5import { omit } from 'lodash'
3fd3ab2d 6import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
b60e5f38 7import {
eacb25c4 8 isUserAutoPlayVideoValid, isUserBlockedReasonValid,
4bbfc6c6
C
9 isUserDescriptionValid,
10 isUserDisplayNameValid,
0883b324 11 isUserNSFWPolicyValid,
ecb4e35f
C
12 isUserPasswordValid,
13 isUserRoleValid,
14 isUserUsernameValid,
bee0abff
FA
15 isUserVideoQuotaValid,
16 isUserVideoQuotaDailyValid
3fd3ab2d 17} from '../../helpers/custom-validators/users'
47564bbe 18import { isVideoExist } from '../../helpers/custom-validators/videos'
da854ddd 19import { logger } from '../../helpers/logger'
06215f15 20import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
ecb4e35f 21import { Redis } from '../../lib/redis'
3fd3ab2d 22import { UserModel } from '../../models/account/user'
a2431b7d 23import { areValidationErrors } from './utils'
2ef6a063 24import { ActorModel } from '../../models/activitypub/actor'
a890d1e0 25import { comparePassword } from '../../helpers/peertube-crypto'
9bd26629 26
b60e5f38 27const usersAddValidator = [
563d032e 28 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
b60e5f38
C
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'),
bee0abff 32 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
954605a8 33 body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
9bd26629 34
a2431b7d 35 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 36 logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
9bd26629 37
a2431b7d
C
38 if (areValidationErrors(req, res)) return
39 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
40
41 return next()
b60e5f38
C
42 }
43]
6fcd19ba 44
b60e5f38
C
45const 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'),
77a5501f 49
a2431b7d 50 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 51 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
77a5501f 52
a2431b7d
C
53 if (areValidationErrors(req, res)) return
54 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
55
56 return next()
b60e5f38
C
57 }
58]
9bd26629 59
b60e5f38
C
60const usersRemoveValidator = [
61 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
9bd26629 62
a2431b7d 63 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 64 logger.debug('Checking usersRemove parameters', { parameters: req.params })
9bd26629 65
a2431b7d
C
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()
b60e5f38
C
77 }
78]
8094a898 79
e6921918
C
80const usersBlockingValidator = [
81 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
eacb25c4 82 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
e6921918
C
83
84 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
eacb25c4 85 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
e6921918
C
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
92b9d60c
C
101const 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
b60e5f38
C
114const 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'),
fc2ec87a 117 body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
b60e5f38 118 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
bee0abff 119 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
954605a8 120 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
8094a898 121
a2431b7d 122 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 123 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
9bd26629 124
a2431b7d
C
125 if (areValidationErrors(req, res)) return
126 if (!await checkUserIdExist(req.params.id, res)) return
127
f8b8c36b
C
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
a2431b7d 135 return next()
b60e5f38
C
136 }
137]
9bd26629 138
b60e5f38 139const usersUpdateMeValidator = [
ed56ad11 140 body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
2422c46b 141 body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
a890d1e0 142 body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
b60e5f38
C
143 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
144 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
0883b324 145 body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
7efe153b 146 body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
9bd26629 147
a890d1e0 148 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 149 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
8094a898 150
a890d1e0
C
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
a2431b7d
C
167 if (areValidationErrors(req, res)) return
168
169 return next()
b60e5f38
C
170 }
171]
8094a898 172
b60e5f38
C
173const usersGetValidator = [
174 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
d38b8281 175
a2431b7d 176 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 177 logger.debug('Checking usersGet parameters', { parameters: req.params })
a2431b7d
C
178
179 if (areValidationErrors(req, res)) return
180 if (!await checkUserIdExist(req.params.id, res)) return
181
182 return next()
b60e5f38
C
183 }
184]
d38b8281 185
b60e5f38 186const usersVideoRatingValidator = [
72c7248b 187 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
0a6658fd 188
a2431b7d 189 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 190 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
0a6658fd 191
a2431b7d 192 if (areValidationErrors(req, res)) return
627621c1 193 if (!await isVideoExist(req.params.videoId, res, 'id')) return
a2431b7d
C
194
195 return next()
b60e5f38
C
196 }
197]
198
199const ensureUserRegistrationAllowed = [
a2431b7d
C
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()
b60e5f38
C
209 }
210]
291e8d3e 211
ff2c1fe8
RK
212const 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
ecb4e35f
C
226const 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
244const 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.' })
f076daa7 262 .end()
ecb4e35f
C
263 }
264
265 return next()
266 }
267]
268
d9eaee39
JM
269const 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
287const 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
74d63469
GR
311const userAutocompleteValidator = [
312 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
313]
314
9bd26629
C
315// ---------------------------------------------------------------------------
316
65fcc311
C
317export {
318 usersAddValidator,
92b9d60c 319 deleteMeValidator,
77a5501f 320 usersRegisterValidator,
e6921918 321 usersBlockingValidator,
65fcc311
C
322 usersRemoveValidator,
323 usersUpdateValidator,
8094a898 324 usersUpdateMeValidator,
291e8d3e 325 usersVideoRatingValidator,
8094a898 326 ensureUserRegistrationAllowed,
ff2c1fe8 327 ensureUserRegistrationAllowedForIP,
c5911fd3 328 usersGetValidator,
ecb4e35f 329 usersAskResetPasswordValidator,
d9eaee39
JM
330 usersResetPasswordValidator,
331 usersAskSendVerifyEmailValidator,
74d63469
GR
332 usersVerifyEmailValidator,
333 userAutocompleteValidator
8094a898
C
334}
335
336// ---------------------------------------------------------------------------
337
ecb4e35f
C
338function checkUserIdExist (id: number, res: express.Response) {
339 return checkUserExist(() => UserModel.loadById(id), res)
340}
a2431b7d 341
ecb4e35f
C
342function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
343 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
65fcc311 344}
77a5501f 345
a2431b7d 346async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
3fd3ab2d 347 const user = await UserModel.loadByUsernameOrEmail(username, email)
a2431b7d
C
348
349 if (user) {
350 res.status(409)
edf7f40a 351 .send({ error: 'User with this username or email already exists.' })
a2431b7d
C
352 .end()
353 return false
354 }
355
2ef6a063
C
356 const actor = await ActorModel.loadLocalByName(username)
357 if (actor) {
358 res.status(409)
c907c2fa 359 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
2ef6a063
C
360 .end()
361 return false
362 }
363
a2431b7d 364 return true
77a5501f 365}
ecb4e35f
C
366
367async 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}