]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/users.ts
Merge branch 'release/v1.0.0' into develop
[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'),
117 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
bee0abff 118 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
954605a8 119 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
8094a898 120
a2431b7d 121 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 122 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
9bd26629 123
a2431b7d
C
124 if (areValidationErrors(req, res)) return
125 if (!await checkUserIdExist(req.params.id, res)) return
126
f8b8c36b
C
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
a2431b7d 134 return next()
b60e5f38
C
135 }
136]
9bd26629 137
b60e5f38 138const usersUpdateMeValidator = [
ed56ad11 139 body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
2422c46b 140 body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
a890d1e0 141 body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
b60e5f38
C
142 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
143 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
0883b324 144 body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
7efe153b 145 body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
9bd26629 146
a890d1e0 147 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 148 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
8094a898 149
a890d1e0
C
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
a2431b7d
C
166 if (areValidationErrors(req, res)) return
167
168 return next()
b60e5f38
C
169 }
170]
8094a898 171
b60e5f38
C
172const usersGetValidator = [
173 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
d38b8281 174
a2431b7d 175 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 176 logger.debug('Checking usersGet parameters', { parameters: req.params })
a2431b7d
C
177
178 if (areValidationErrors(req, res)) return
179 if (!await checkUserIdExist(req.params.id, res)) return
180
181 return next()
b60e5f38
C
182 }
183]
d38b8281 184
b60e5f38 185const usersVideoRatingValidator = [
72c7248b 186 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
0a6658fd 187
a2431b7d 188 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 189 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
0a6658fd 190
a2431b7d 191 if (areValidationErrors(req, res)) return
627621c1 192 if (!await isVideoExist(req.params.videoId, res, 'id')) return
a2431b7d
C
193
194 return next()
b60e5f38
C
195 }
196]
197
198const ensureUserRegistrationAllowed = [
a2431b7d
C
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()
b60e5f38
C
208 }
209]
291e8d3e 210
ff2c1fe8
RK
211const 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
ecb4e35f
C
225const 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
243const 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.' })
f076daa7 261 .end()
ecb4e35f
C
262 }
263
264 return next()
265 }
266]
267
d9eaee39
JM
268const 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
286const 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
74d63469
GR
310const userAutocompleteValidator = [
311 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
312]
313
9bd26629
C
314// ---------------------------------------------------------------------------
315
65fcc311
C
316export {
317 usersAddValidator,
92b9d60c 318 deleteMeValidator,
77a5501f 319 usersRegisterValidator,
e6921918 320 usersBlockingValidator,
65fcc311
C
321 usersRemoveValidator,
322 usersUpdateValidator,
8094a898 323 usersUpdateMeValidator,
291e8d3e 324 usersVideoRatingValidator,
8094a898 325 ensureUserRegistrationAllowed,
ff2c1fe8 326 ensureUserRegistrationAllowedForIP,
c5911fd3 327 usersGetValidator,
ecb4e35f 328 usersAskResetPasswordValidator,
d9eaee39
JM
329 usersResetPasswordValidator,
330 usersAskSendVerifyEmailValidator,
74d63469
GR
331 usersVerifyEmailValidator,
332 userAutocompleteValidator
8094a898
C
333}
334
335// ---------------------------------------------------------------------------
336
ecb4e35f
C
337function checkUserIdExist (id: number, res: express.Response) {
338 return checkUserExist(() => UserModel.loadById(id), res)
339}
a2431b7d 340
ecb4e35f
C
341function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
342 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
65fcc311 343}
77a5501f 344
a2431b7d 345async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
3fd3ab2d 346 const user = await UserModel.loadByUsernameOrEmail(username, email)
a2431b7d
C
347
348 if (user) {
349 res.status(409)
edf7f40a 350 .send({ error: 'User with this username or email already exists.' })
a2431b7d
C
351 .end()
352 return false
353 }
354
2ef6a063
C
355 const actor = await ActorModel.loadLocalByName(username)
356 if (actor) {
357 res.status(409)
c907c2fa 358 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
2ef6a063
C
359 .end()
360 return false
361 }
362
a2431b7d 363 return true
77a5501f 364}
ecb4e35f
C
365
366async 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}