Check current password on server side

This commit is contained in:
Chocobozzz 2018-09-26 16:28:15 +02:00
parent be1206bb93
commit a890d1e0d3
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
8 changed files with 105 additions and 33 deletions

View File

@ -1,14 +1,14 @@
<div *ngIf="error" class="alert alert-danger">{{ error }}</div> <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> <label i18n for="new-password">Change password</label>
<input <input
type="password" id="old-password" i18n-placeholder placeholder="Old password" type="password" id="current-password" i18n-placeholder placeholder="Current password"
formControlName="old-password" [ngClass]="{ 'input-error': formErrors['old-password'] }" formControlName="current-password" [ngClass]="{ 'input-error': formErrors['current-password'] }"
> >
<div *ngIf="formErrors['old-password']" class="form-error"> <div *ngIf="formErrors['current-password']" class="form-error">
{{ formErrors['old-password'] }} {{ formErrors['current-password'] }}
</div> </div>
<input <input

View File

@ -30,7 +30,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
ngOnInit () { ngOnInit () {
this.buildForm({ this.buildForm({
'old-password': this.userValidatorsService.USER_PASSWORD, 'current-password': this.userValidatorsService.USER_PASSWORD,
'new-password': this.userValidatorsService.USER_PASSWORD, 'new-password': this.userValidatorsService.USER_PASSWORD,
'new-confirmed-password': this.userValidatorsService.USER_CONFIRM_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 })) .subscribe(() => confirmPasswordControl.setErrors({ matchPassword: true }))
} }
checkPassword () { changePassword () {
this.error = null const currentPassword = this.form.value[ 'current-password' ]
const oldPassword = this.form.value[ 'old-password' ] const newPassword = this.form.value[ 'new-password' ]
// compare old password this.userService.changePassword(currentPassword, newPassword).subscribe(
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.notificationsService.success(this.i18n('Success'), this.i18n('Password updated.')) this.notificationsService.success(this.i18n('Success'), this.i18n('Password updated.'))
this.form.reset() 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
}
) )
} }
} }

View File

@ -17,9 +17,10 @@ export class UserService {
) { ) {
} }
changePassword (newPassword: string) { changePassword (currentPassword: string, newPassword: string) {
const url = UserService.BASE_USERS_URL + 'me' const url = UserService.BASE_USERS_URL + 'me'
const body: UserUpdateMe = { const body: UserUpdateMe = {
currentPassword,
password: newPassword password: newPassword
} }

View File

@ -87,7 +87,7 @@ meRouter.get('/me/videos/:videoId/rating',
meRouter.put('/me', meRouter.put('/me',
authenticate, authenticate,
usersUpdateMeValidator, asyncMiddleware(usersUpdateMeValidator),
asyncRetryTransactionMiddleware(updateMe) asyncRetryTransactionMiddleware(updateMe)
) )

View File

@ -22,6 +22,7 @@ import { Redis } from '../../lib/redis'
import { UserModel } from '../../models/account/user' import { UserModel } from '../../models/account/user'
import { areValidationErrors } from './utils' import { areValidationErrors } from './utils'
import { ActorModel } from '../../models/activitypub/actor' import { ActorModel } from '../../models/activitypub/actor'
import { comparePassword } from '../../helpers/peertube-crypto'
const usersAddValidator = [ const usersAddValidator = [
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@ -137,15 +138,31 @@ const usersUpdateValidator = [
const usersUpdateMeValidator = [ const usersUpdateMeValidator = [
body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'), body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'), 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('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
body('email').optional().isEmail().withMessage('Should have a valid email attribute'), 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('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'), body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
(req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
// TODO: Add old password verification
logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) 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 if (areValidationErrors(req, res)) return
return next() return next()

View File

@ -254,6 +254,7 @@ describe('Test users API validators', function () {
it('Should fail with a too small password', async function () { it('Should fail with a too small password', async function () {
const fields = { const fields = {
currentPassword: 'my super password',
password: 'bla' password: 'bla'
} }
@ -262,12 +263,31 @@ describe('Test users API validators', function () {
it('Should fail with a too long password', async function () { it('Should fail with a too long password', async function () {
const fields = { const fields = {
currentPassword: 'my super password',
password: 'super'.repeat(61) password: 'super'.repeat(61)
} }
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) 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 () { it('Should fail with an invalid NSFW policy attribute', async function () {
const fields = { const fields = {
nsfwPolicy: 'hello' nsfwPolicy: 'hello'
@ -286,6 +306,7 @@ describe('Test users API validators', function () {
it('Should fail with an non authenticated user', async function () { it('Should fail with an non authenticated user', async function () {
const fields = { const fields = {
currentPassword: 'my super password',
password: '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 }) 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 = { const fields = {
currentPassword: 'my super password',
password: 'my super password', password: 'my super password',
nsfwPolicy: 'blur', nsfwPolicy: 'blur',
autoPlayVideo: false, 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 }) 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 () { describe('When updating my avatar', function () {

View File

@ -4,10 +4,34 @@ import * as chai from 'chai'
import 'mocha' import 'mocha'
import { User, UserRole } from '../../../../shared/index' import { User, UserRole } from '../../../../shared/index'
import { import {
createUser, flushTests, getBlacklistedVideosList, getMyUserInformation, getMyUserVideoQuotaUsed, getMyUserVideoRating, blockUser,
getUserInformation, getUsersList, getUsersListPaginationAndSort, getVideosList, killallServers, login, makePutBodyRequest, rateVideo, createUser,
registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin, deleteMe,
deleteMe, blockUser, unblockUser, updateCustomSubConfig 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' } from '../../utils/index'
import { follow } from '../../utils/server/follows' import { follow } from '../../utils/server/follows'
import { setAccessTokensToServers } from '../../utils/users/login' import { setAccessTokensToServers } from '../../utils/users/login'
@ -302,6 +326,7 @@ describe('Test users', function () {
await updateMyUser({ await updateMyUser({
url: server.url, url: server.url,
accessToken: accessTokenUser, accessToken: accessTokenUser,
currentPassword: 'super password',
newPassword: 'new password' newPassword: 'new password'
}) })
user.password = 'new password' user.password = 'new password'

View File

@ -162,6 +162,7 @@ function unblockUser (url: string, userId: number | string, accessToken: string,
function updateMyUser (options: { function updateMyUser (options: {
url: string url: string
accessToken: string, accessToken: string,
currentPassword?: string,
newPassword?: string, newPassword?: string,
nsfwPolicy?: NSFWPolicyType, nsfwPolicy?: NSFWPolicyType,
email?: string, email?: string,
@ -172,6 +173,7 @@ function updateMyUser (options: {
const path = '/api/v1/users/me' const path = '/api/v1/users/me'
const toSend = {} 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.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword
if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy
if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo