]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/users.ts
Merge branch 'release/v1.0.0' into develop
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / users.ts
1 import * as Bluebird from 'bluebird'
2 import * as express from 'express'
3 import 'express-validator'
4 import { body, param } from 'express-validator/check'
5 import { omit } from 'lodash'
6 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
7 import {
8 isUserAutoPlayVideoValid, isUserBlockedReasonValid,
9 isUserDescriptionValid,
10 isUserDisplayNameValid,
11 isUserNSFWPolicyValid,
12 isUserPasswordValid,
13 isUserRoleValid,
14 isUserUsernameValid,
15 isUserVideoQuotaValid,
16 isUserVideoQuotaDailyValid
17 } from '../../helpers/custom-validators/users'
18 import { isVideoExist } from '../../helpers/custom-validators/videos'
19 import { logger } from '../../helpers/logger'
20 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
21 import { Redis } from '../../lib/redis'
22 import { UserModel } from '../../models/account/user'
23 import { areValidationErrors } from './utils'
24 import { ActorModel } from '../../models/activitypub/actor'
25 import { comparePassword } from '../../helpers/peertube-crypto'
26
27 const usersAddValidator = [
28 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
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'),
32 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
33 body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
34
35 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
36 logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
37
38 if (areValidationErrors(req, res)) return
39 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
40
41 return next()
42 }
43 ]
44
45 const 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'),
49
50 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
51 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
52
53 if (areValidationErrors(req, res)) return
54 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
55
56 return next()
57 }
58 ]
59
60 const usersRemoveValidator = [
61 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
62
63 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
64 logger.debug('Checking usersRemove parameters', { parameters: req.params })
65
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()
77 }
78 ]
79
80 const usersBlockingValidator = [
81 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
82 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
83
84 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
85 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
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
101 const 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
114 const 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'),
118 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
119 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
120
121 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
122 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
123
124 if (areValidationErrors(req, res)) return
125 if (!await checkUserIdExist(req.params.id, res)) return
126
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
134 return next()
135 }
136 ]
137
138 const usersUpdateMeValidator = [
139 body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
140 body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
141 body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
142 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
143 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
144 body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
145 body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
146
147 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
148 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
149
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
166 if (areValidationErrors(req, res)) return
167
168 return next()
169 }
170 ]
171
172 const usersGetValidator = [
173 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
174
175 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
176 logger.debug('Checking usersGet parameters', { parameters: req.params })
177
178 if (areValidationErrors(req, res)) return
179 if (!await checkUserIdExist(req.params.id, res)) return
180
181 return next()
182 }
183 ]
184
185 const usersVideoRatingValidator = [
186 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
187
188 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
189 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
190
191 if (areValidationErrors(req, res)) return
192 if (!await isVideoExist(req.params.videoId, res, 'id')) return
193
194 return next()
195 }
196 ]
197
198 const ensureUserRegistrationAllowed = [
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()
208 }
209 ]
210
211 const 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
225 const 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
243 const 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.' })
261 .end()
262 }
263
264 return next()
265 }
266 ]
267
268 const 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
286 const 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
310 const userAutocompleteValidator = [
311 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
312 ]
313
314 // ---------------------------------------------------------------------------
315
316 export {
317 usersAddValidator,
318 deleteMeValidator,
319 usersRegisterValidator,
320 usersBlockingValidator,
321 usersRemoveValidator,
322 usersUpdateValidator,
323 usersUpdateMeValidator,
324 usersVideoRatingValidator,
325 ensureUserRegistrationAllowed,
326 ensureUserRegistrationAllowedForIP,
327 usersGetValidator,
328 usersAskResetPasswordValidator,
329 usersResetPasswordValidator,
330 usersAskSendVerifyEmailValidator,
331 usersVerifyEmailValidator,
332 userAutocompleteValidator
333 }
334
335 // ---------------------------------------------------------------------------
336
337 function checkUserIdExist (id: number, res: express.Response) {
338 return checkUserExist(() => UserModel.loadById(id), res)
339 }
340
341 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
342 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
343 }
344
345 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
346 const user = await UserModel.loadByUsernameOrEmail(username, email)
347
348 if (user) {
349 res.status(409)
350 .send({ error: 'User with this username or email already exists.' })
351 .end()
352 return false
353 }
354
355 const actor = await ActorModel.loadLocalByName(username)
356 if (actor) {
357 res.status(409)
358 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
359 .end()
360 return false
361 }
362
363 return true
364 }
365
366 async 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 }