diff --git a/packages/backend/src/controllers/api/v1/users/update-current-user-password.js b/packages/backend/src/controllers/api/v1/users/update-current-user-password.js index b3727a20..982fe899 100644 --- a/packages/backend/src/controllers/api/v1/users/update-current-user-password.js +++ b/packages/backend/src/controllers/api/v1/users/update-current-user-password.js @@ -1,9 +1,12 @@ import { renderObject } from '../../../../helpers/renderer.js'; export default async (request, response) => { - const user = await request.currentUser - .$query() - .patchAndFetch({ password: request.body.password }); + const user = await request.currentUser.updatePassword(userParams(request)); renderObject(response, user); }; + +const userParams = (request) => { + const { currentPassword, password } = request.body; + return { currentPassword, password }; +}; diff --git a/packages/backend/src/controllers/api/v1/users/update-current-user-password.test.js b/packages/backend/src/controllers/api/v1/users/update-current-user-password.test.js index fc5b0305..505ede8a 100644 --- a/packages/backend/src/controllers/api/v1/users/update-current-user-password.test.js +++ b/packages/backend/src/controllers/api/v1/users/update-current-user-password.test.js @@ -9,15 +9,20 @@ describe('PATCH /api/v1/users/:userId/password', () => { let currentUser, token; beforeEach(async () => { - currentUser = await createUser(); + currentUser = await createUser({ password: 'old-password' }); token = await createAuthTokenByUserId(currentUser.id); }); it('should return updated user with valid password', async () => { + const userData = { + currentPassword: 'old-password', + password: 'new-password', + }; + const response = await request(app) .patch(`/api/v1/users/${currentUser.id}/password`) .set('Authorization', token) - .send({ password: 'new-password' }) + .send(userData) .expect(200); const refetchedCurrentUser = await currentUser.$query(); @@ -26,16 +31,21 @@ describe('PATCH /api/v1/users/:userId/password', () => { expect(response.body).toStrictEqual(expectedPayload); }); - it('should return HTTP 422 with invalid password', async () => { + it.only('should return HTTP 422 with invalid current password', async () => { + const userData = { + currentPassword: '', + password: 'new-password', + }; + const response = await request(app) .patch(`/api/v1/users/${currentUser.id}/password`) .set('Authorization', token) - .send({ password: '' }) + .send(userData) .expect(422); - expect(response.body.meta.type).toEqual('ModelValidation'); + expect(response.body.meta.type).toEqual('ValidationError'); expect(response.body.errors).toMatchObject({ - password: ['must NOT have fewer than 6 characters'], + currentPassword: ['is incorrect.'], }); }); }); diff --git a/packages/backend/src/models/user.js b/packages/backend/src/models/user.js index 1f8a5da9..58ecb533 100644 --- a/packages/backend/src/models/user.js +++ b/packages/backend/src/models/user.js @@ -1,6 +1,7 @@ import bcrypt from 'bcrypt'; import { DateTime, Duration } from 'luxon'; import crypto from 'node:crypto'; +import { ValidationError } from 'objection'; import appConfig from '../config/app.js'; import { hasValidLicense } from '../helpers/license.ee.js'; @@ -249,6 +250,27 @@ class User extends Base { }); } + async updatePassword({ currentPassword, password }) { + if (await User.authenticate(this.email, currentPassword)) { + const user = await this.$query().patchAndFetch({ + password, + }); + + return user; + } + + throw new ValidationError({ + data: { + currentPassword: [ + { + message: 'is incorrect.', + }, + ], + }, + type: 'ValidationError', + }); + } + async softRemove() { await this.softRemoveAssociations(); await this.$query().delete();