1 import * as Bluebird from 'bluebird'
2 import * as express from 'express'
3 import { body, param } from 'express-validator'
4 import { omit } from 'lodash'
5 import { isIdOrUUIDValid, toBooleanOrNull } from '../../helpers/custom-validators/misc'
8 isUserAutoPlayVideoValid,
9 isUserBlockedReasonValid,
10 isUserDescriptionValid,
11 isUserDisplayNameValid,
12 isUserNSFWPolicyValid,
17 isUserVideoQuotaDailyValid,
18 isUserVideoQuotaValid,
19 isUserVideosHistoryEnabledValid
20 } from '../../helpers/custom-validators/users'
21 import { logger } from '../../helpers/logger'
22 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
23 import { Redis } from '../../lib/redis'
24 import { UserModel } from '../../models/account/user'
25 import { areValidationErrors } from './utils'
26 import { ActorModel } from '../../models/activitypub/actor'
27 import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
28 import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
29 import { UserRegister } from '../../../shared/models/users/user-register.model'
30 import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
31 import { isThemeRegistered } from '../../lib/plugins/theme-utils'
32 import { doesVideoExist } from '../../helpers/middlewares'
33 import { UserRole } from '../../../shared/models/users'
34 import { MUserDefault } from '@server/typings/models'
36 const usersAddValidator = [
37 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
38 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
39 body('email').isEmail().withMessage('Should have a valid email'),
40 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
41 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
43 .customSanitizer(toIntOrNull)
44 .custom(isUserRoleValid).withMessage('Should have a valid role'),
45 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
47 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
48 logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
50 if (areValidationErrors(req, res)) return
51 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
53 const authUser = res.locals.oauth.token.User
54 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) {
55 return res.status(403)
56 .json({ error: 'You can only create users (and not administrators or moderators)' })
63 const usersRegisterValidator = [
64 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
65 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
66 body('email').isEmail().withMessage('Should have a valid email'),
69 .custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
73 .custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
74 body('channel.displayName')
76 .custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
78 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
79 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
81 if (areValidationErrors(req, res)) return
82 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
84 const body: UserRegister = req.body
86 if (!body.channel.name || !body.channel.displayName) {
87 return res.status(400)
88 .json({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
91 if (body.channel.name === body.username) {
92 return res.status(400)
93 .json({ error: 'Channel name cannot be the same than user username.' })
96 const existing = await ActorModel.loadLocalByName(body.channel.name)
98 return res.status(409)
99 .json({ error: `Channel with name ${body.channel.name} already exists.` })
107 const usersRemoveValidator = [
108 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
110 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
111 logger.debug('Checking usersRemove parameters', { parameters: req.params })
113 if (areValidationErrors(req, res)) return
114 if (!await checkUserIdExist(req.params.id, res)) return
116 const user = res.locals.user
117 if (user.username === 'root') {
118 return res.status(400)
119 .json({ error: 'Cannot remove the root user' })
126 const usersBlockingValidator = [
127 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
128 body('reason').optional().custom(isUserBlockedReasonValid).withMessage('Should have a valid blocking reason'),
130 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
131 logger.debug('Checking usersBlocking parameters', { parameters: req.params })
133 if (areValidationErrors(req, res)) return
134 if (!await checkUserIdExist(req.params.id, res)) return
136 const user = res.locals.user
137 if (user.username === 'root') {
138 return res.status(400)
139 .json({ error: 'Cannot block the root user' })
146 const deleteMeValidator = [
147 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
148 const user = res.locals.oauth.token.User
149 if (user.username === 'root') {
150 return res.status(400)
151 .json({ error: 'You cannot delete your root account.' })
159 const usersUpdateValidator = [
160 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
161 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
162 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
163 body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'),
164 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
165 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
168 .customSanitizer(toIntOrNull)
169 .custom(isUserRoleValid).withMessage('Should have a valid role'),
170 body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
172 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
173 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
175 if (areValidationErrors(req, res)) return
176 if (!await checkUserIdExist(req.params.id, res)) return
178 const user = res.locals.user
179 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
180 return res.status(400)
181 .json({ error: 'Cannot change root role.' })
188 const usersUpdateMeValidator = [
191 .custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
194 .custom(isUserDescriptionValid).withMessage('Should have a valid description'),
195 body('currentPassword')
197 .custom(isUserPasswordValid).withMessage('Should have a valid current password'),
200 .custom(isUserPasswordValid).withMessage('Should have a valid password'),
203 .isEmail().withMessage('Should have a valid email attribute'),
206 .custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
207 body('autoPlayVideo')
209 .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
210 body('videoLanguages')
212 .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'),
213 body('videosHistoryEnabled')
215 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
218 .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'),
220 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
221 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
223 if (req.body.password || req.body.email) {
224 if (!req.body.currentPassword) {
225 return res.status(400)
226 .json({ error: 'currentPassword parameter is missing.' })
230 const user = res.locals.oauth.token.User
231 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
232 return res.status(401)
233 .json({ error: 'currentPassword is invalid.' })
237 if (areValidationErrors(req, res)) return
243 const usersGetValidator = [
244 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
246 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
247 logger.debug('Checking usersGet parameters', { parameters: req.params })
249 if (areValidationErrors(req, res)) return
250 if (!await checkUserIdExist(req.params.id, res)) return
256 const usersVideoRatingValidator = [
257 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
259 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
260 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
262 if (areValidationErrors(req, res)) return
263 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
269 const ensureUserRegistrationAllowed = [
270 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
271 const allowed = await isSignupAllowed()
272 if (allowed === false) {
273 return res.status(403)
274 .json({ error: 'User registration is not enabled or user limit is reached.' })
281 const ensureUserRegistrationAllowedForIP = [
282 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
283 const allowed = isSignupAllowedForCurrentIP(req.ip)
285 if (allowed === false) {
286 return res.status(403)
287 .json({ error: 'You are not on a network authorized for registration.' })
294 const usersAskResetPasswordValidator = [
295 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
297 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
298 logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body })
300 if (areValidationErrors(req, res)) return
302 const exists = await checkUserEmailExist(req.body.email, res, false)
304 logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
305 // Do not leak our emails
306 return res.status(204).end()
313 const usersResetPasswordValidator = [
314 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
315 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'),
316 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
318 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
319 logger.debug('Checking usersResetPassword parameters', { parameters: req.params })
321 if (areValidationErrors(req, res)) return
322 if (!await checkUserIdExist(req.params.id, res)) return
324 const user = res.locals.user
325 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
327 if (redisVerificationString !== req.body.verificationString) {
330 .json({ error: 'Invalid verification string.' })
337 const usersAskSendVerifyEmailValidator = [
338 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
340 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
341 logger.debug('Checking askUsersSendVerifyEmail parameters', { parameters: req.body })
343 if (areValidationErrors(req, res)) return
344 const exists = await checkUserEmailExist(req.body.email, res, false)
346 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
347 // Do not leak our emails
348 return res.status(204).end()
355 const usersVerifyEmailValidator = [
357 .isInt().not().isEmpty().withMessage('Should have a valid id'),
359 body('verificationString')
360 .not().isEmpty().withMessage('Should have a valid verification string'),
361 body('isPendingEmail')
363 .customSanitizer(toBooleanOrNull),
365 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
366 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
368 if (areValidationErrors(req, res)) return
369 if (!await checkUserIdExist(req.params.id, res)) return
371 const user = res.locals.user
372 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
374 if (redisVerificationString !== req.body.verificationString) {
377 .json({ error: 'Invalid verification string.' })
384 const userAutocompleteValidator = [
385 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
388 const ensureAuthUserOwnsAccountValidator = [
389 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
390 const user = res.locals.oauth.token.User
392 if (res.locals.account.id !== user.Account.id) {
393 return res.status(403)
394 .json({ error: 'Only owner can access ratings list.' })
401 const ensureCanManageUser = [
402 (req: express.Request, res: express.Response, next: express.NextFunction) => {
403 const authUser = res.locals.oauth.token.User
404 const onUser = res.locals.user
406 if (authUser.role === UserRole.ADMINISTRATOR) return next()
407 if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next()
409 return res.status(403)
410 .json({ error: 'A moderator can only manager users.' })
414 // ---------------------------------------------------------------------------
419 usersRegisterValidator,
420 usersBlockingValidator,
421 usersRemoveValidator,
422 usersUpdateValidator,
423 usersUpdateMeValidator,
424 usersVideoRatingValidator,
425 ensureUserRegistrationAllowed,
426 ensureUserRegistrationAllowedForIP,
428 usersAskResetPasswordValidator,
429 usersResetPasswordValidator,
430 usersAskSendVerifyEmailValidator,
431 usersVerifyEmailValidator,
432 userAutocompleteValidator,
433 ensureAuthUserOwnsAccountValidator,
437 // ---------------------------------------------------------------------------
439 function checkUserIdExist (id: number, res: express.Response) {
440 return checkUserExist(() => UserModel.loadById(id), res)
443 function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
444 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
447 async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
448 const user = await UserModel.loadByUsernameOrEmail(username, email)
452 .json({ error: 'User with this username or email already exists.' })
456 const actor = await ActorModel.loadLocalByName(username)
459 .json({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
466 async function checkUserExist (finder: () => Bluebird<MUserDefault>, res: express.Response, abortResponse = true) {
467 const user = await finder()
470 if (abortResponse === true) {
472 .json({ error: 'User not found' })
478 res.locals.user = user