]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/users.ts
Better AP route checker
[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'
0f6acda1 19import { doesVideoExist } 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'),
b426edd4 116 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
b60e5f38 117 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
fc2ec87a 118 body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
b60e5f38 119 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
bee0abff 120 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
954605a8 121 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
8094a898 122
a2431b7d 123 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 124 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
9bd26629 125
a2431b7d
C
126 if (areValidationErrors(req, res)) return
127 if (!await checkUserIdExist(req.params.id, res)) return
128
f8b8c36b
C
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
a2431b7d 136 return next()
b60e5f38
C
137 }
138]
9bd26629 139
b60e5f38 140const usersUpdateMeValidator = [
ed56ad11 141 body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
2422c46b 142 body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
a890d1e0 143 body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
b60e5f38
C
144 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
145 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
0883b324 146 body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
7efe153b 147 body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
1a12adcd
C
148 body('videosHistoryEnabled')
149 .optional()
150 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
9bd26629 151
a890d1e0 152 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 153 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
8094a898 154
a890d1e0
C
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
a2431b7d
C
171 if (areValidationErrors(req, res)) return
172
173 return next()
b60e5f38
C
174 }
175]
8094a898 176
b60e5f38
C
177const usersGetValidator = [
178 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
d38b8281 179
a2431b7d 180 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 181 logger.debug('Checking usersGet parameters', { parameters: req.params })
a2431b7d
C
182
183 if (areValidationErrors(req, res)) return
184 if (!await checkUserIdExist(req.params.id, res)) return
185
186 return next()
b60e5f38
C
187 }
188]
d38b8281 189
b60e5f38 190const usersVideoRatingValidator = [
72c7248b 191 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
0a6658fd 192
a2431b7d 193 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 194 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
0a6658fd 195
a2431b7d 196 if (areValidationErrors(req, res)) return
0f6acda1 197 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
a2431b7d
C
198
199 return next()
b60e5f38
C
200 }
201]
202
203const ensureUserRegistrationAllowed = [
a2431b7d
C
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()
b60e5f38
C
213 }
214]
291e8d3e 215
ff2c1fe8
RK
216const 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
ecb4e35f
C
230const 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
b426edd4 237
ecb4e35f
C
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
249const 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.' })
f076daa7 267 .end()
ecb4e35f
C
268 }
269
270 return next()
271 }
272]
273
d9eaee39
JM
274const 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
292const 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
74d63469
GR
316const userAutocompleteValidator = [
317 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
318]
319
9bd26629
C
320// ---------------------------------------------------------------------------
321
65fcc311
C
322export {
323 usersAddValidator,
92b9d60c 324 deleteMeValidator,
77a5501f 325 usersRegisterValidator,
e6921918 326 usersBlockingValidator,
65fcc311
C
327 usersRemoveValidator,
328 usersUpdateValidator,
8094a898 329 usersUpdateMeValidator,
291e8d3e 330 usersVideoRatingValidator,
8094a898 331 ensureUserRegistrationAllowed,
ff2c1fe8 332 ensureUserRegistrationAllowedForIP,
c5911fd3 333 usersGetValidator,
ecb4e35f 334 usersAskResetPasswordValidator,
d9eaee39
JM
335 usersResetPasswordValidator,
336 usersAskSendVerifyEmailValidator,
74d63469
GR
337 usersVerifyEmailValidator,
338 userAutocompleteValidator
8094a898
C
339}
340
341// ---------------------------------------------------------------------------
342
ecb4e35f
C
343function checkUserIdExist (id: number, res: express.Response) {
344 return checkUserExist(() => UserModel.loadById(id), res)
345}
a2431b7d 346
ecb4e35f
C
347function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
348 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
65fcc311 349}
77a5501f 350
a2431b7d 351async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
3fd3ab2d 352 const user = await UserModel.loadByUsernameOrEmail(username, email)
a2431b7d
C
353
354 if (user) {
355 res.status(409)
edf7f40a 356 .send({ error: 'User with this username or email already exists.' })
a2431b7d
C
357 .end()
358 return false
359 }
360
2ef6a063
C
361 const actor = await ActorModel.loadLocalByName(username)
362 if (actor) {
363 res.status(409)
c907c2fa 364 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
2ef6a063
C
365 .end()
366 return false
367 }
368
a2431b7d 369 return true
77a5501f 370}
ecb4e35f
C
371
372async 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}