diff options
8 files changed, 105 insertions, 33 deletions
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html index 00b6d20fa..ae797d1bc 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html | |||
@@ -1,14 +1,14 @@ | |||
1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
2 | 2 | ||
3 | <form role="form" (ngSubmit)="checkPassword()" [formGroup]="form"> | 3 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> |
4 | 4 | ||
5 | <label i18n for="new-password">Change password</label> | 5 | <label i18n for="new-password">Change password</label> |
6 | <input | 6 | <input |
7 | type="password" id="old-password" i18n-placeholder placeholder="Old password" | 7 | type="password" id="current-password" i18n-placeholder placeholder="Current password" |
8 | formControlName="old-password" [ngClass]="{ 'input-error': formErrors['old-password'] }" | 8 | formControlName="current-password" [ngClass]="{ 'input-error': formErrors['current-password'] }" |
9 | > | 9 | > |
10 | <div *ngIf="formErrors['old-password']" class="form-error"> | 10 | <div *ngIf="formErrors['current-password']" class="form-error"> |
11 | {{ formErrors['old-password'] }} | 11 | {{ formErrors['current-password'] }} |
12 | </div> | 12 | </div> |
13 | 13 | ||
14 | <input | 14 | <input |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts index da0021df9..e5343b33d 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts | |||
@@ -30,7 +30,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On | |||
30 | 30 | ||
31 | ngOnInit () { | 31 | ngOnInit () { |
32 | this.buildForm({ | 32 | this.buildForm({ |
33 | 'old-password': this.userValidatorsService.USER_PASSWORD, | 33 | 'current-password': this.userValidatorsService.USER_PASSWORD, |
34 | 'new-password': this.userValidatorsService.USER_PASSWORD, | 34 | 'new-password': this.userValidatorsService.USER_PASSWORD, |
35 | 'new-confirmed-password': this.userValidatorsService.USER_CONFIRM_PASSWORD | 35 | 'new-confirmed-password': this.userValidatorsService.USER_CONFIRM_PASSWORD |
36 | }) | 36 | }) |
@@ -44,31 +44,26 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On | |||
44 | .subscribe(() => confirmPasswordControl.setErrors({ matchPassword: true })) | 44 | .subscribe(() => confirmPasswordControl.setErrors({ matchPassword: true })) |
45 | } | 45 | } |
46 | 46 | ||
47 | checkPassword () { | 47 | changePassword () { |
48 | this.error = null | 48 | const currentPassword = this.form.value[ 'current-password' ] |
49 | const oldPassword = this.form.value[ 'old-password' ] | 49 | const newPassword = this.form.value[ 'new-password' ] |
50 | 50 | ||
51 | // compare old password | 51 | this.userService.changePassword(currentPassword, newPassword).subscribe( |
52 | this.authService.login(this.user.account.name, oldPassword) | ||
53 | .subscribe( | ||
54 | () => this.changePassword(), | ||
55 | err => { | ||
56 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect old password.') | ||
57 | else this.error = err.message | ||
58 | } | ||
59 | ) | ||
60 | |||
61 | } | ||
62 | |||
63 | private changePassword () { | ||
64 | this.userService.changePassword(this.form.value[ 'new-password' ]).subscribe( | ||
65 | () => { | 52 | () => { |
66 | this.notificationsService.success(this.i18n('Success'), this.i18n('Password updated.')) | 53 | this.notificationsService.success(this.i18n('Success'), this.i18n('Password updated.')) |
67 | 54 | ||
68 | this.form.reset() | 55 | this.form.reset() |
56 | this.error = null | ||
69 | }, | 57 | }, |
70 | 58 | ||
71 | err => this.error = err.message | 59 | err => { |
60 | if (err.status === 401) { | ||
61 | this.error = this.i18n('You current password is invalid.') | ||
62 | return | ||
63 | } | ||
64 | |||
65 | this.error = err.message | ||
66 | } | ||
72 | ) | 67 | ) |
73 | } | 68 | } |
74 | } | 69 | } |
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index fad5b0980..bd5cd45d4 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts | |||
@@ -17,9 +17,10 @@ export class UserService { | |||
17 | ) { | 17 | ) { |
18 | } | 18 | } |
19 | 19 | ||
20 | changePassword (newPassword: string) { | 20 | changePassword (currentPassword: string, newPassword: string) { |
21 | const url = UserService.BASE_USERS_URL + 'me' | 21 | const url = UserService.BASE_USERS_URL + 'me' |
22 | const body: UserUpdateMe = { | 22 | const body: UserUpdateMe = { |
23 | currentPassword, | ||
23 | password: newPassword | 24 | password: newPassword |
24 | } | 25 | } |
25 | 26 | ||
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index ff3a87b7f..591ec6b25 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -87,7 +87,7 @@ meRouter.get('/me/videos/:videoId/rating', | |||
87 | 87 | ||
88 | meRouter.put('/me', | 88 | meRouter.put('/me', |
89 | authenticate, | 89 | authenticate, |
90 | usersUpdateMeValidator, | 90 | asyncMiddleware(usersUpdateMeValidator), |
91 | asyncRetryTransactionMiddleware(updateMe) | 91 | asyncRetryTransactionMiddleware(updateMe) |
92 | ) | 92 | ) |
93 | 93 | ||
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index d3ba1ae23..61297120a 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -22,6 +22,7 @@ import { Redis } from '../../lib/redis' | |||
22 | import { UserModel } from '../../models/account/user' | 22 | import { UserModel } from '../../models/account/user' |
23 | import { areValidationErrors } from './utils' | 23 | import { areValidationErrors } from './utils' |
24 | import { ActorModel } from '../../models/activitypub/actor' | 24 | import { ActorModel } from '../../models/activitypub/actor' |
25 | import { comparePassword } from '../../helpers/peertube-crypto' | ||
25 | 26 | ||
26 | const usersAddValidator = [ | 27 | const usersAddValidator = [ |
27 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 28 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
@@ -137,15 +138,31 @@ const usersUpdateValidator = [ | |||
137 | const usersUpdateMeValidator = [ | 138 | const usersUpdateMeValidator = [ |
138 | body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'), | 139 | body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'), |
139 | body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'), | 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'), | ||
140 | body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'), | 142 | body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'), |
141 | body('email').optional().isEmail().withMessage('Should have a valid email attribute'), | 143 | body('email').optional().isEmail().withMessage('Should have a valid email attribute'), |
142 | body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'), | 144 | body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'), |
143 | body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), | 145 | body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), |
144 | 146 | ||
145 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 147 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
146 | // TODO: Add old password verification | ||
147 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) | 148 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) |
148 | 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 | |||
149 | if (areValidationErrors(req, res)) return | 166 | if (areValidationErrors(req, res)) return |
150 | 167 | ||
151 | return next() | 168 | return next() |
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 95903c8a5..cbfa0c137 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -254,6 +254,7 @@ describe('Test users API validators', function () { | |||
254 | 254 | ||
255 | it('Should fail with a too small password', async function () { | 255 | it('Should fail with a too small password', async function () { |
256 | const fields = { | 256 | const fields = { |
257 | currentPassword: 'my super password', | ||
257 | password: 'bla' | 258 | password: 'bla' |
258 | } | 259 | } |
259 | 260 | ||
@@ -262,12 +263,31 @@ describe('Test users API validators', function () { | |||
262 | 263 | ||
263 | it('Should fail with a too long password', async function () { | 264 | it('Should fail with a too long password', async function () { |
264 | const fields = { | 265 | const fields = { |
266 | currentPassword: 'my super password', | ||
265 | password: 'super'.repeat(61) | 267 | password: 'super'.repeat(61) |
266 | } | 268 | } |
267 | 269 | ||
268 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | 270 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) |
269 | }) | 271 | }) |
270 | 272 | ||
273 | it('Should fail without the current password', async function () { | ||
274 | const fields = { | ||
275 | currentPassword: 'my super password', | ||
276 | password: 'super'.repeat(61) | ||
277 | } | ||
278 | |||
279 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
280 | }) | ||
281 | |||
282 | it('Should fail with an invalid current password', async function () { | ||
283 | const fields = { | ||
284 | currentPassword: 'my super password fail', | ||
285 | password: 'super'.repeat(61) | ||
286 | } | ||
287 | |||
288 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 401 }) | ||
289 | }) | ||
290 | |||
271 | it('Should fail with an invalid NSFW policy attribute', async function () { | 291 | it('Should fail with an invalid NSFW policy attribute', async function () { |
272 | const fields = { | 292 | const fields = { |
273 | nsfwPolicy: 'hello' | 293 | nsfwPolicy: 'hello' |
@@ -286,6 +306,7 @@ describe('Test users API validators', function () { | |||
286 | 306 | ||
287 | it('Should fail with an non authenticated user', async function () { | 307 | it('Should fail with an non authenticated user', async function () { |
288 | const fields = { | 308 | const fields = { |
309 | currentPassword: 'my super password', | ||
289 | password: 'my super password' | 310 | password: 'my super password' |
290 | } | 311 | } |
291 | 312 | ||
@@ -300,8 +321,9 @@ describe('Test users API validators', function () { | |||
300 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | 321 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) |
301 | }) | 322 | }) |
302 | 323 | ||
303 | it('Should succeed with the correct params', async function () { | 324 | it('Should succeed to change password with the correct params', async function () { |
304 | const fields = { | 325 | const fields = { |
326 | currentPassword: 'my super password', | ||
305 | password: 'my super password', | 327 | password: 'my super password', |
306 | nsfwPolicy: 'blur', | 328 | nsfwPolicy: 'blur', |
307 | autoPlayVideo: false, | 329 | autoPlayVideo: false, |
@@ -310,6 +332,16 @@ describe('Test users API validators', function () { | |||
310 | 332 | ||
311 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) | 333 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) |
312 | }) | 334 | }) |
335 | |||
336 | it('Should succeed without password change with the correct params', async function () { | ||
337 | const fields = { | ||
338 | nsfwPolicy: 'blur', | ||
339 | autoPlayVideo: false, | ||
340 | email: 'super_email@example.com' | ||
341 | } | ||
342 | |||
343 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) | ||
344 | }) | ||
313 | }) | 345 | }) |
314 | 346 | ||
315 | describe('When updating my avatar', function () { | 347 | describe('When updating my avatar', function () { |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index c0dd587ee..8b9c6b455 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -4,10 +4,34 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { User, UserRole } from '../../../../shared/index' | 5 | import { User, UserRole } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | createUser, flushTests, getBlacklistedVideosList, getMyUserInformation, getMyUserVideoQuotaUsed, getMyUserVideoRating, | 7 | blockUser, |
8 | getUserInformation, getUsersList, getUsersListPaginationAndSort, getVideosList, killallServers, login, makePutBodyRequest, rateVideo, | 8 | createUser, |
9 | registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin, | 9 | deleteMe, |
10 | deleteMe, blockUser, unblockUser, updateCustomSubConfig | 10 | flushTests, |
11 | getBlacklistedVideosList, | ||
12 | getMyUserInformation, | ||
13 | getMyUserVideoQuotaUsed, | ||
14 | getMyUserVideoRating, | ||
15 | getUserInformation, | ||
16 | getUsersList, | ||
17 | getUsersListPaginationAndSort, | ||
18 | getVideosList, | ||
19 | killallServers, | ||
20 | login, | ||
21 | makePutBodyRequest, | ||
22 | rateVideo, | ||
23 | registerUser, | ||
24 | removeUser, | ||
25 | removeVideo, | ||
26 | runServer, | ||
27 | ServerInfo, | ||
28 | testImage, | ||
29 | unblockUser, | ||
30 | updateMyAvatar, | ||
31 | updateMyUser, | ||
32 | updateUser, | ||
33 | uploadVideo, | ||
34 | userLogin | ||
11 | } from '../../utils/index' | 35 | } from '../../utils/index' |
12 | import { follow } from '../../utils/server/follows' | 36 | import { follow } from '../../utils/server/follows' |
13 | import { setAccessTokensToServers } from '../../utils/users/login' | 37 | import { setAccessTokensToServers } from '../../utils/users/login' |
@@ -302,6 +326,7 @@ describe('Test users', function () { | |||
302 | await updateMyUser({ | 326 | await updateMyUser({ |
303 | url: server.url, | 327 | url: server.url, |
304 | accessToken: accessTokenUser, | 328 | accessToken: accessTokenUser, |
329 | currentPassword: 'super password', | ||
305 | newPassword: 'new password' | 330 | newPassword: 'new password' |
306 | }) | 331 | }) |
307 | user.password = 'new password' | 332 | user.password = 'new password' |
diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts index cd1b07701..41d8ce265 100644 --- a/server/tests/utils/users/users.ts +++ b/server/tests/utils/users/users.ts | |||
@@ -162,6 +162,7 @@ function unblockUser (url: string, userId: number | string, accessToken: string, | |||
162 | function updateMyUser (options: { | 162 | function updateMyUser (options: { |
163 | url: string | 163 | url: string |
164 | accessToken: string, | 164 | accessToken: string, |
165 | currentPassword?: string, | ||
165 | newPassword?: string, | 166 | newPassword?: string, |
166 | nsfwPolicy?: NSFWPolicyType, | 167 | nsfwPolicy?: NSFWPolicyType, |
167 | email?: string, | 168 | email?: string, |
@@ -172,6 +173,7 @@ function updateMyUser (options: { | |||
172 | const path = '/api/v1/users/me' | 173 | const path = '/api/v1/users/me' |
173 | 174 | ||
174 | const toSend = {} | 175 | const toSend = {} |
176 | if (options.currentPassword !== undefined && options.currentPassword !== null) toSend['currentPassword'] = options.currentPassword | ||
175 | if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword | 177 | if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword |
176 | if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy | 178 | if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy |
177 | if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo | 179 | if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo |