]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/users.ts
Merge branch 'release/v1.2.0'
[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 {
1a12adcd
C
8 isUserAutoPlayVideoValid,
9 isUserBlockedReasonValid,
4bbfc6c6
C
10 isUserDescriptionValid,
11 isUserDisplayNameValid,
0883b324 12 isUserNSFWPolicyValid,
ecb4e35f
C
13 isUserPasswordValid,
14 isUserRoleValid,
15 isUserUsernameValid,
1a12adcd
C
16 isUserVideoQuotaDailyValid,
17 isUserVideoQuotaValid, isUserVideosHistoryEnabledValid
3fd3ab2d 18} from '../../helpers/custom-validators/users'
47564bbe 19import { isVideoExist } from '../../helpers/custom-validators/videos'
da854ddd 20import { logger } from '../../helpers/logger'
06215f15 21import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
ecb4e35f 22import { Redis } from '../../lib/redis'
3fd3ab2d 23import { UserModel } from '../../models/account/user'
a2431b7d 24import { areValidationErrors } from './utils'
2ef6a063 25import { ActorModel } from '../../models/activitypub/actor'
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'),
1a12adcd
C
147 body('videosHistoryEnabled')
148 .optional()
149 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
9bd26629 150
a890d1e0 151 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 152 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
8094a898 153
a890d1e0
C
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
a2431b7d
C
170 if (areValidationErrors(req, res)) return
171
172 return next()
b60e5f38
C
173 }
174]
8094a898 175
b60e5f38
C
176const usersGetValidator = [
177 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
d38b8281 178
a2431b7d 179 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 180 logger.debug('Checking usersGet parameters', { parameters: req.params })
a2431b7d
C
181
182 if (areValidationErrors(req, res)) return
183 if (!await checkUserIdExist(req.params.id, res)) return
184
185 return next()
b60e5f38
C
186 }
187]
d38b8281 188
b60e5f38 189const usersVideoRatingValidator = [
72c7248b 190 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
0a6658fd 191
a2431b7d 192 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 193 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
0a6658fd 194
a2431b7d 195 if (areValidationErrors(req, res)) return
627621c1 196 if (!await isVideoExist(req.params.videoId, res, 'id')) return
a2431b7d
C
197
198 return next()
b60e5f38
C
199 }
200]
201
202const ensureUserRegistrationAllowed = [
a2431b7d
C
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()
b60e5f38
C
212 }
213]
291e8d3e 214
ff2c1fe8
RK
215const 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
ecb4e35f
C
229const 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
247const 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.' })
f076daa7 265 .end()
ecb4e35f
C
266 }
267
268 return next()
269 }
270]
271
d9eaee39
JM
272const 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
290const 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
74d63469
GR
314const userAutocompleteValidator = [
315 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
316]
317
9bd26629
C
318// ---------------------------------------------------------------------------
319
65fcc311
C
320export {
321 usersAddValidator,
92b9d60c 322 deleteMeValidator,
77a5501f 323 usersRegisterValidator,
e6921918 324 usersBlockingValidator,
65fcc311
C
325 usersRemoveValidator,
326 usersUpdateValidator,
8094a898 327 usersUpdateMeValidator,
291e8d3e 328 usersVideoRatingValidator,
8094a898 329 ensureUserRegistrationAllowed,
ff2c1fe8 330 ensureUserRegistrationAllowedForIP,
c5911fd3 331 usersGetValidator,
ecb4e35f 332 usersAskResetPasswordValidator,
d9eaee39
JM
333 usersResetPasswordValidator,
334 usersAskSendVerifyEmailValidator,
74d63469
GR
335 usersVerifyEmailValidator,
336 userAutocompleteValidator
8094a898
C
337}
338
339// ---------------------------------------------------------------------------
340
ecb4e35f
C
341function checkUserIdExist (id: number, res: express.Response) {
342 return checkUserExist(() => UserModel.loadById(id), res)
343}
a2431b7d 344
ecb4e35f
C
345function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
346 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
65fcc311 347}
77a5501f 348
a2431b7d 349async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
3fd3ab2d 350 const user = await UserModel.loadByUsernameOrEmail(username, email)
a2431b7d
C
351
352 if (user) {
353 res.status(409)
edf7f40a 354 .send({ error: 'User with this username or email already exists.' })
a2431b7d
C
355 .end()
356 return false
357 }
358
2ef6a063
C
359 const actor = await ActorModel.loadLocalByName(username)
360 if (actor) {
361 res.status(409)
c907c2fa 362 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
2ef6a063
C
363 .end()
364 return false
365 }
366
a2431b7d 367 return true
77a5501f 368}
ecb4e35f
C
369
370async 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}