]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Check current password on server side
authorChocobozzz <me@florianbigard.com>
Wed, 26 Sep 2018 14:28:15 +0000 (16:28 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 26 Sep 2018 14:28:27 +0000 (16:28 +0200)
client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html
client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
client/src/app/shared/users/user.service.ts
server/controllers/api/users/me.ts
server/middlewares/validators/users.ts
server/tests/api/check-params/users.ts
server/tests/api/users/users.ts
server/tests/utils/users/users.ts

index 00b6d20fa221d397f93636a534b25fb72bb3cc88..ae797d1bc9276e033e1ef60a3b2e92ce90bb4f3a 100644 (file)
@@ -1,14 +1,14 @@
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-<form role="form" (ngSubmit)="checkPassword()" [formGroup]="form">
+<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
 
   <label i18n for="new-password">Change password</label>
   <input
-    type="password" id="old-password" i18n-placeholder placeholder="Old password"
-    formControlName="old-password" [ngClass]="{ 'input-error': formErrors['old-password'] }"
+    type="password" id="current-password" i18n-placeholder placeholder="Current password"
+    formControlName="current-password" [ngClass]="{ 'input-error': formErrors['current-password'] }"
   >
-  <div *ngIf="formErrors['old-password']" class="form-error">
-    {{ formErrors['old-password'] }}
+  <div *ngIf="formErrors['current-password']" class="form-error">
+    {{ formErrors['current-password'] }}
   </div>
 
   <input
index da0021df99149f8e8c96dc1f8dbb34c12cd2cdea..e5343b33d6798bce06fdc9ff9e9c9ca121227451 100644 (file)
@@ -30,7 +30,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
 
   ngOnInit () {
     this.buildForm({
-      'old-password': this.userValidatorsService.USER_PASSWORD,
+      'current-password': this.userValidatorsService.USER_PASSWORD,
       'new-password': this.userValidatorsService.USER_PASSWORD,
       'new-confirmed-password': this.userValidatorsService.USER_CONFIRM_PASSWORD
     })
@@ -44,31 +44,26 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
                           .subscribe(() => confirmPasswordControl.setErrors({ matchPassword: true }))
   }
 
-  checkPassword () {
-    this.error = null
-    const oldPassword = this.form.value[ 'old-password' ]
+  changePassword () {
+    const currentPassword = this.form.value[ 'current-password' ]
+    const newPassword = this.form.value[ 'new-password' ]
 
-    // compare old password
-    this.authService.login(this.user.account.name, oldPassword)
-      .subscribe(
-        () => this.changePassword(),
-        err => {
-          if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect old password.')
-          else this.error = err.message
-        }
-      )
-
-  }
-
-  private changePassword () {
-    this.userService.changePassword(this.form.value[ 'new-password' ]).subscribe(
+    this.userService.changePassword(currentPassword, newPassword).subscribe(
       () => {
         this.notificationsService.success(this.i18n('Success'), this.i18n('Password updated.'))
 
         this.form.reset()
+        this.error = null
       },
 
-      err => this.error = err.message
+      err => {
+        if (err.status === 401) {
+          this.error = this.i18n('You current password is invalid.')
+          return
+        }
+
+        this.error = err.message
+      }
     )
   }
 }
index fad5b09806e11a06fd163424abae9677ecfa50cb..bd5cd45d4730a5a744a4c5085d85f0d6134a31b9 100644 (file)
@@ -17,9 +17,10 @@ export class UserService {
   ) {
   }
 
-  changePassword (newPassword: string) {
+  changePassword (currentPassword: string, newPassword: string) {
     const url = UserService.BASE_USERS_URL + 'me'
     const body: UserUpdateMe = {
+      currentPassword,
       password: newPassword
     }
 
index ff3a87b7f181d7d30fb9aa3065057ab11c6c76f1..591ec6b254b9085e506125fc050738272e16135b 100644 (file)
@@ -87,7 +87,7 @@ meRouter.get('/me/videos/:videoId/rating',
 
 meRouter.put('/me',
   authenticate,
-  usersUpdateMeValidator,
+  asyncMiddleware(usersUpdateMeValidator),
   asyncRetryTransactionMiddleware(updateMe)
 )
 
index d3ba1ae232afa41d2ef6ddea9873ff9c3f0ba723..61297120ac80de42caaeff795f2c67f5dd05dc50 100644 (file)
@@ -22,6 +22,7 @@ import { Redis } from '../../lib/redis'
 import { UserModel } from '../../models/account/user'
 import { areValidationErrors } from './utils'
 import { ActorModel } from '../../models/activitypub/actor'
+import { comparePassword } from '../../helpers/peertube-crypto'
 
 const usersAddValidator = [
   body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -137,15 +138,31 @@ const usersUpdateValidator = [
 const usersUpdateMeValidator = [
   body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
   body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
+  body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'),
   body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
   body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
   body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
   body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
 
-  (req: express.Request, res: express.Response, next: express.NextFunction) => {
-    // TODO: Add old password verification
+  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
 
+    if (req.body.password) {
+      if (!req.body.currentPassword) {
+        return res.status(400)
+                  .send({ error: 'currentPassword parameter is missing.' })
+                  .end()
+      }
+
+      const user: UserModel = res.locals.oauth.token.User
+
+      if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
+        return res.status(401)
+                  .send({ error: 'currentPassword is invalid.' })
+                  .end()
+      }
+    }
+
     if (areValidationErrors(req, res)) return
 
     return next()
index 95903c8a57d020a18e9b8f1a86833673cac5d7cc..cbfa0c137cd879b2cdd2ce08f8a45294d48f5b2c 100644 (file)
@@ -254,6 +254,7 @@ describe('Test users API validators', function () {
 
     it('Should fail with a too small password', async function () {
       const fields = {
+        currentPassword: 'my super password',
         password: 'bla'
       }
 
@@ -262,12 +263,31 @@ describe('Test users API validators', function () {
 
     it('Should fail with a too long password', async function () {
       const fields = {
+        currentPassword: 'my super password',
         password: 'super'.repeat(61)
       }
 
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
     })
 
+    it('Should fail without the current password', async function () {
+      const fields = {
+        currentPassword: 'my super password',
+        password: 'super'.repeat(61)
+      }
+
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
+    })
+
+    it('Should fail with an invalid current password', async function () {
+      const fields = {
+        currentPassword: 'my super password fail',
+        password: 'super'.repeat(61)
+      }
+
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 401 })
+    })
+
     it('Should fail with an invalid NSFW policy attribute', async function () {
       const fields = {
         nsfwPolicy: 'hello'
@@ -286,6 +306,7 @@ describe('Test users API validators', function () {
 
     it('Should fail with an non authenticated user', async function () {
       const fields = {
+        currentPassword: 'my super password',
         password: 'my super password'
       }
 
@@ -300,8 +321,9 @@ describe('Test users API validators', function () {
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
     })
 
-    it('Should succeed with the correct params', async function () {
+    it('Should succeed to change password with the correct params', async function () {
       const fields = {
+        currentPassword: 'my super password',
         password: 'my super password',
         nsfwPolicy: 'blur',
         autoPlayVideo: false,
@@ -310,6 +332,16 @@ describe('Test users API validators', function () {
 
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 })
     })
+
+    it('Should succeed without password change with the correct params', async function () {
+      const fields = {
+        nsfwPolicy: 'blur',
+        autoPlayVideo: false,
+        email: 'super_email@example.com'
+      }
+
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 })
+    })
   })
 
   describe('When updating my avatar', function () {
index c0dd587eef72c7b84f7367817cc20254ef7b9610..8b9c6b45503caa0258dd4a0b1983a6d0b1f4e971 100644 (file)
@@ -4,10 +4,34 @@ import * as chai from 'chai'
 import 'mocha'
 import { User, UserRole } from '../../../../shared/index'
 import {
-  createUser, flushTests, getBlacklistedVideosList, getMyUserInformation, getMyUserVideoQuotaUsed, getMyUserVideoRating,
-  getUserInformation, getUsersList, getUsersListPaginationAndSort, getVideosList, killallServers, login, makePutBodyRequest, rateVideo,
-  registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin,
-  deleteMe, blockUser, unblockUser, updateCustomSubConfig
+  blockUser,
+  createUser,
+  deleteMe,
+  flushTests,
+  getBlacklistedVideosList,
+  getMyUserInformation,
+  getMyUserVideoQuotaUsed,
+  getMyUserVideoRating,
+  getUserInformation,
+  getUsersList,
+  getUsersListPaginationAndSort,
+  getVideosList,
+  killallServers,
+  login,
+  makePutBodyRequest,
+  rateVideo,
+  registerUser,
+  removeUser,
+  removeVideo,
+  runServer,
+  ServerInfo,
+  testImage,
+  unblockUser,
+  updateMyAvatar,
+  updateMyUser,
+  updateUser,
+  uploadVideo,
+  userLogin
 } from '../../utils/index'
 import { follow } from '../../utils/server/follows'
 import { setAccessTokensToServers } from '../../utils/users/login'
@@ -302,6 +326,7 @@ describe('Test users', function () {
     await updateMyUser({
       url: server.url,
       accessToken: accessTokenUser,
+      currentPassword: 'super password',
       newPassword: 'new password'
     })
     user.password = 'new password'
index cd1b0770149f43ca8b88844031354748b1f2e741..41d8ce265e5c5495e402af7c155d71a20090d985 100644 (file)
@@ -162,6 +162,7 @@ function unblockUser (url: string, userId: number | string, accessToken: string,
 function updateMyUser (options: {
   url: string
   accessToken: string,
+  currentPassword?: string,
   newPassword?: string,
   nsfwPolicy?: NSFWPolicyType,
   email?: string,
@@ -172,6 +173,7 @@ function updateMyUser (options: {
   const path = '/api/v1/users/me'
 
   const toSend = {}
+  if (options.currentPassword !== undefined && options.currentPassword !== null) toSend['currentPassword'] = options.currentPassword
   if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword
   if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy
   if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo