]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/users.ts
Add ability to specify channel on registration
[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 {
1eddc9a7 8 isUserAdminFlagsValid,
1a12adcd
C
9 isUserAutoPlayVideoValid,
10 isUserBlockedReasonValid,
4bbfc6c6
C
11 isUserDescriptionValid,
12 isUserDisplayNameValid,
0883b324 13 isUserNSFWPolicyValid,
ecb4e35f
C
14 isUserPasswordValid,
15 isUserRoleValid,
16 isUserUsernameValid,
1a12adcd 17 isUserVideoQuotaDailyValid,
dae86118
C
18 isUserVideoQuotaValid,
19 isUserVideosHistoryEnabledValid
3fd3ab2d 20} from '../../helpers/custom-validators/users'
0f6acda1 21import { doesVideoExist } from '../../helpers/custom-validators/videos'
da854ddd 22import { logger } from '../../helpers/logger'
06215f15 23import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
ecb4e35f 24import { Redis } from '../../lib/redis'
3fd3ab2d 25import { UserModel } from '../../models/account/user'
a2431b7d 26import { areValidationErrors } from './utils'
2ef6a063 27import { ActorModel } from '../../models/activitypub/actor'
e590b4a5
C
28import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
29import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
30import { UserCreate } from '../../../shared/models/users'
31import { UserRegister } from '../../../shared/models/users/user-register.model'
9bd26629 32
b60e5f38 33const usersAddValidator = [
563d032e 34 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
b60e5f38
C
35 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
36 body('email').isEmail().withMessage('Should have a valid email'),
37 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
bee0abff 38 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
954605a8 39 body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
1eddc9a7 40 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
9bd26629 41
a2431b7d 42 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 43 logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
9bd26629 44
a2431b7d
C
45 if (areValidationErrors(req, res)) return
46 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
47
48 return next()
b60e5f38
C
49 }
50]
6fcd19ba 51
b60e5f38
C
52const usersRegisterValidator = [
53 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
54 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
55 body('email').isEmail().withMessage('Should have a valid email'),
e590b4a5
C
56 body('channel.name').optional().custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
57 body('channel.displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
77a5501f 58
a2431b7d 59 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 60 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
77a5501f 61
a2431b7d
C
62 if (areValidationErrors(req, res)) return
63 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
64
e590b4a5
C
65 const body: UserRegister = req.body
66 if (body.channel) {
67 if (!body.channel.name || !body.channel.displayName) {
68 return res.status(400)
69 .send({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
70 .end()
71 }
72
73 const existing = await ActorModel.loadLocalByName(body.channel.name)
74 if (existing) {
75 return res.status(409)
76 .send({ error: `Channel with name ${body.channel.name} already exists.` })
77 .end()
78 }
79 }
80
a2431b7d 81 return next()
b60e5f38
C
82 }
83]
9bd26629 84
b60e5f38
C
85const usersRemoveValidator = [
86 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
9bd26629 87
a2431b7d 88 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 89 logger.debug('Checking usersRemove parameters', { parameters: req.params })
9bd26629 90
a2431b7d
C
91 if (areValidationErrors(req, res)) return
92 if (!await checkUserIdExist(req.params.id, res)) return
93
94 const user = res.locals.user
95 if (user.username === 'root') {
96 return res.status(400)
97 .send({ error: 'Cannot remove the root user' })
98 .end()
99 }
100
101 return next()
b60e5f38
C
102 }
103]
8094a898 104
e6921918
C
105const usersBlockingValidator = [
106 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
eacb25c4 107 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
e6921918
C
108
109 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
eacb25c4 110 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
e6921918
C
111
112 if (areValidationErrors(req, res)) return
113 if (!await checkUserIdExist(req.params.id, res)) return
114
115 const user = res.locals.user
116 if (user.username === 'root') {
117 return res.status(400)
118 .send({ error: 'Cannot block the root user' })
119 .end()
120 }
121
122 return next()
123 }
124]
125
92b9d60c
C
126const deleteMeValidator = [
127 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
dae86118 128 const user = res.locals.oauth.token.User
92b9d60c
C
129 if (user.username === 'root') {
130 return res.status(400)
131 .send({ error: 'You cannot delete your root account.' })
132 .end()
133 }
134
135 return next()
136 }
137]
138
b60e5f38
C
139const usersUpdateValidator = [
140 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
b426edd4 141 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
b60e5f38 142 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
fc2ec87a 143 body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
b60e5f38 144 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
bee0abff 145 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
954605a8 146 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
1eddc9a7 147 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
8094a898 148
a2431b7d 149 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 150 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
9bd26629 151
a2431b7d
C
152 if (areValidationErrors(req, res)) return
153 if (!await checkUserIdExist(req.params.id, res)) return
154
f8b8c36b
C
155 const user = res.locals.user
156 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
157 return res.status(400)
158 .send({ error: 'Cannot change root role.' })
159 .end()
160 }
161
a2431b7d 162 return next()
b60e5f38
C
163 }
164]
9bd26629 165
b60e5f38 166const usersUpdateMeValidator = [
ed56ad11 167 body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
2422c46b 168 body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
a890d1e0 169 body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
b60e5f38
C
170 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
171 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
0883b324 172 body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
7efe153b 173 body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
1a12adcd
C
174 body('videosHistoryEnabled')
175 .optional()
176 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
9bd26629 177
a890d1e0 178 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 179 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
8094a898 180
a890d1e0
C
181 if (req.body.password) {
182 if (!req.body.currentPassword) {
183 return res.status(400)
184 .send({ error: 'currentPassword parameter is missing.' })
185 .end()
186 }
187
2ba92871 188 const user = res.locals.oauth.token.User
a890d1e0
C
189 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
190 return res.status(401)
191 .send({ error: 'currentPassword is invalid.' })
192 .end()
193 }
194 }
195
a2431b7d
C
196 if (areValidationErrors(req, res)) return
197
198 return next()
b60e5f38
C
199 }
200]
8094a898 201
b60e5f38
C
202const usersGetValidator = [
203 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
d38b8281 204
a2431b7d 205 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
ce97fe36 206 logger.debug('Checking usersGet parameters', { parameters: req.params })
a2431b7d
C
207
208 if (areValidationErrors(req, res)) return
209 if (!await checkUserIdExist(req.params.id, res)) return
210
211 return next()
b60e5f38
C
212 }
213]
d38b8281 214
b60e5f38 215const usersVideoRatingValidator = [
72c7248b 216 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
0a6658fd 217
a2431b7d 218 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 219 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
0a6658fd 220
a2431b7d 221 if (areValidationErrors(req, res)) return
0f6acda1 222 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
a2431b7d
C
223
224 return next()
b60e5f38
C
225 }
226]
227
228const ensureUserRegistrationAllowed = [
a2431b7d
C
229 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
230 const allowed = await isSignupAllowed()
231 if (allowed === false) {
232 return res.status(403)
233 .send({ error: 'User registration is not enabled or user limit is reached.' })
234 .end()
235 }
236
237 return next()
b60e5f38
C
238 }
239]
291e8d3e 240
ff2c1fe8
RK
241const ensureUserRegistrationAllowedForIP = [
242 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
243 const allowed = isSignupAllowedForCurrentIP(req.ip)
244
245 if (allowed === false) {
246 return res.status(403)
247 .send({ error: 'You are not on a network authorized for registration.' })
248 .end()
249 }
250
251 return next()
252 }
253]
254
ecb4e35f
C
255const usersAskResetPasswordValidator = [
256 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
257
258 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
259 logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
260
261 if (areValidationErrors(req, res)) return
b426edd4 262
ecb4e35f
C
263 const exists = await checkUserEmailExist(req.body.email, res, false)
264 if (!exists) {
265 logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
266 // Do not leak our emails
267 return res.status(204).end()
268 }
269
270 return next()
271 }
272]
273
274const usersResetPasswordValidator = [
275 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
276 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
277 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
278
279 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
280 logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
281
282 if (areValidationErrors(req, res)) return
283 if (!await checkUserIdExist(req.params.id, res)) return
284
dae86118 285 const user = res.locals.user
ecb4e35f
C
286 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
287
288 if (redisVerificationString !== req.body.verificationString) {
289 return res
290 .status(403)
291 .send({ error: 'Invalid verification string.' })
f076daa7 292 .end()
ecb4e35f
C
293 }
294
295 return next()
296 }
297]
298
d9eaee39
JM
299const usersAskSendVerifyEmailValidator = [
300 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
301
302 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
303 logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
304
305 if (areValidationErrors(req, res)) return
306 const exists = await checkUserEmailExist(req.body.email, res, false)
307 if (!exists) {
308 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
309 // Do not leak our emails
310 return res.status(204).end()
311 }
312
313 return next()
314 }
315]
316
317const usersVerifyEmailValidator = [
318 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
319 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
320
321 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
322 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
323
324 if (areValidationErrors(req, res)) return
325 if (!await checkUserIdExist(req.params.id, res)) return
326
dae86118 327 const user = res.locals.user
d9eaee39
JM
328 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
329
330 if (redisVerificationString !== req.body.verificationString) {
331 return res
332 .status(403)
333 .send({ error: 'Invalid verification string.' })
334 .end()
335 }
336
337 return next()
338 }
339]
340
74d63469
GR
341const userAutocompleteValidator = [
342 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
343]
344
c100a614
YB
345const ensureAuthUserOwnsAccountValidator = [
346 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
347 const user = res.locals.oauth.token.User
348
349 if (res.locals.account.id !== user.Account.id) {
350 return res.status(403)
351 .send({ error: 'Only owner can access ratings list.' })
352 .end()
353 }
354
355 return next()
356 }
357]
358
9bd26629
C
359// ---------------------------------------------------------------------------
360
65fcc311
C
361export {
362 usersAddValidator,
92b9d60c 363 deleteMeValidator,
77a5501f 364 usersRegisterValidator,
e6921918 365 usersBlockingValidator,
65fcc311
C
366 usersRemoveValidator,
367 usersUpdateValidator,
8094a898 368 usersUpdateMeValidator,
291e8d3e 369 usersVideoRatingValidator,
8094a898 370 ensureUserRegistrationAllowed,
ff2c1fe8 371 ensureUserRegistrationAllowedForIP,
c5911fd3 372 usersGetValidator,
ecb4e35f 373 usersAskResetPasswordValidator,
d9eaee39
JM
374 usersResetPasswordValidator,
375 usersAskSendVerifyEmailValidator,
74d63469 376 usersVerifyEmailValidator,
c100a614
YB
377 userAutocompleteValidator,
378 ensureAuthUserOwnsAccountValidator
8094a898
C
379}
380
381// ---------------------------------------------------------------------------
382
ecb4e35f
C
383function checkUserIdExist (id: number, res: express.Response) {
384 return checkUserExist(() => UserModel.loadById(id), res)
385}
a2431b7d 386
ecb4e35f
C
387function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
388 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
65fcc311 389}
77a5501f 390
a2431b7d 391async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
3fd3ab2d 392 const user = await UserModel.loadByUsernameOrEmail(username, email)
a2431b7d
C
393
394 if (user) {
395 res.status(409)
edf7f40a 396 .send({ error: 'User with this username or email already exists.' })
a2431b7d
C
397 .end()
398 return false
399 }
400
2ef6a063
C
401 const actor = await ActorModel.loadLocalByName(username)
402 if (actor) {
403 res.status(409)
c907c2fa 404 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
2ef6a063
C
405 .end()
406 return false
407 }
408
a2431b7d 409 return true
77a5501f 410}
ecb4e35f
C
411
412async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
413 const user = await finder()
414
415 if (!user) {
416 if (abortResponse === true) {
417 res.status(404)
418 .send({ error: 'User not found' })
419 .end()
420 }
421
422 return false
423 }
424
425 res.locals.user = user
426
427 return true
428}