]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/middlewares/validators/users.ts
Don't inject untrusted input
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / users.ts
index 2de5265fb50978ecdec32bc58ece815597b45c1a..50327b6aebf4eb575b0721131c8c56dd2917e653 100644 (file)
@@ -1,9 +1,9 @@
 import express from 'express'
 import { body, param, query } from 'express-validator'
 import { Hooks } from '@server/lib/plugins/hooks'
-import { MUserDefault } from '@server/types/models'
+import { forceNumber } from '@shared/core-utils'
 import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models'
-import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
+import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
 import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
 import {
   isUserAdminFlagsValid,
@@ -30,8 +30,15 @@ import { isThemeRegistered } from '../../lib/plugins/theme-utils'
 import { Redis } from '../../lib/redis'
 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup'
 import { ActorModel } from '../../models/actor/actor'
-import { UserModel } from '../../models/user/user'
-import { areValidationErrors, doesVideoChannelIdExist, doesVideoExist, isValidVideoIdParam } from './shared'
+import {
+  areValidationErrors,
+  checkUserEmailExist,
+  checkUserIdExist,
+  checkUserNameOrEmailDoesNotAlreadyExist,
+  doesVideoChannelIdExist,
+  doesVideoExist,
+  isValidVideoIdParam
+} from './shared'
 
 const usersListValidator = [
   query('blocked')
@@ -411,6 +418,13 @@ const usersAskResetPasswordValidator = [
       return res.status(HttpStatusCode.NO_CONTENT_204).end()
     }
 
+    if (res.locals.user.pluginAuth) {
+      return res.fail({
+        status: HttpStatusCode.CONFLICT_409,
+        message: 'Cannot recover password of a user that uses a plugin authentication.'
+      })
+    }
+
     return next()
   }
 ]
@@ -428,7 +442,7 @@ const usersResetPasswordValidator = [
     if (!await checkUserIdExist(req.params.id, res)) return
 
     const user = res.locals.user
-    const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
+    const redisVerificationString = await Redis.Instance.getResetPasswordVerificationString(user.id)
 
     if (redisVerificationString !== req.body.verificationString) {
       return res.fail({
@@ -454,6 +468,13 @@ const usersAskSendVerifyEmailValidator = [
       return res.status(HttpStatusCode.NO_CONTENT_204).end()
     }
 
+    if (res.locals.user.pluginAuth) {
+      return res.fail({
+        status: HttpStatusCode.CONFLICT_409,
+        message: 'Cannot ask verification email of a user that uses a plugin authentication.'
+      })
+    }
+
     return next()
   }
 ]
@@ -486,6 +507,41 @@ const usersVerifyEmailValidator = [
   }
 ]
 
+const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => {
+  return [
+    body('currentPassword').optional().custom(exists),
+
+    async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+      if (areValidationErrors(req, res)) return
+
+      const user = res.locals.oauth.token.User
+      const isAdminOrModerator = user.role === UserRole.ADMINISTRATOR || user.role === UserRole.MODERATOR
+      const targetUserId = forceNumber(targetUserIdGetter(req))
+
+      // Admin/moderator action on another user, skip the password check
+      if (isAdminOrModerator && targetUserId !== user.id) {
+        return next()
+      }
+
+      if (!req.body.currentPassword) {
+        return res.fail({
+          status: HttpStatusCode.BAD_REQUEST_400,
+          message: 'currentPassword is missing'
+        })
+      }
+
+      if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
+        return res.fail({
+          status: HttpStatusCode.FORBIDDEN_403,
+          message: 'currentPassword is invalid.'
+        })
+      }
+
+      return next()
+    }
+  ]
+}
+
 const userAutocompleteValidator = [
   param('search')
     .isString()
@@ -553,6 +609,7 @@ export {
   usersUpdateValidator,
   usersUpdateMeValidator,
   usersVideoRatingValidator,
+  usersCheckCurrentPasswordFactory,
   ensureUserRegistrationAllowed,
   ensureUserRegistrationAllowedForIP,
   usersGetValidator,
@@ -566,55 +623,3 @@ export {
   ensureCanModerateUser,
   ensureCanManageChannelOrAccount
 }
-
-// ---------------------------------------------------------------------------
-
-function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
-  const id = parseInt(idArg + '', 10)
-  return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res)
-}
-
-function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
-  return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
-}
-
-async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
-  const user = await UserModel.loadByUsernameOrEmail(username, email)
-
-  if (user) {
-    res.fail({
-      status: HttpStatusCode.CONFLICT_409,
-      message: 'User with this username or email already exists.'
-    })
-    return false
-  }
-
-  const actor = await ActorModel.loadLocalByName(username)
-  if (actor) {
-    res.fail({
-      status: HttpStatusCode.CONFLICT_409,
-      message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.'
-    })
-    return false
-  }
-
-  return true
-}
-
-async function checkUserExist (finder: () => Promise<MUserDefault>, res: express.Response, abortResponse = true) {
-  const user = await finder()
-
-  if (!user) {
-    if (abortResponse === true) {
-      res.fail({
-        status: HttpStatusCode.NOT_FOUND_404,
-        message: 'User not found'
-      })
-    }
-
-    return false
-  }
-
-  res.locals.user = user
-  return true
-}