diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index c59edfce0..5805f49ed 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -6,6 +6,12 @@ class Api::V1::ProfilesController < Api::BaseController end def update + if password_params[:password].present? + render_could_not_create_error('Invalid current password') and return unless @user.valid_password?(password_params[:current_password]) + + @user.update!(password_params.except(:current_password)) + end + @user.update!(profile_params) end @@ -20,11 +26,17 @@ class Api::V1::ProfilesController < Api::BaseController :email, :name, :display_name, - :password, - :password_confirmation, :avatar, :availability, ui_settings: {} ) end + + def password_params + params.require(:profile).permit( + :current_password, + :password, + :password_confirmation + ) + end end diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index 314bbbbda..aa51c35be 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -74,6 +74,11 @@ "ERROR": "Please enter a valid email address", "PLACEHOLDER": "Please enter your email address, this would be displayed in conversations" }, + "CURRENT_PASSWORD": { + "LABEL": "Current password", + "ERROR": "Please enter the current password", + "PLACEHOLDER": "Please enter the current password" + }, "PASSWORD": { "LABEL": "Password", "ERROR": "Please enter a password of length 6 or more", diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue new file mode 100644 index 000000000..2b226add9 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue index 90d6f7012..93d965421 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue @@ -55,53 +55,7 @@ -
-
-
-

- {{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.TITLE') }} -

-

{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.NOTE') }}

-
-
- - - - {{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.BTN_TEXT') }} - -
-
-
+
@@ -123,10 +77,12 @@ import { mapGetters } from 'vuex'; import { clearCookiesOnLogout } from '../../../../store/utils/api'; import NotificationSettings from './NotificationSettings'; import alertMixin from 'shared/mixins/alertMixin'; +import ChangePassword from './ChangePassword.vue'; export default { components: { NotificationSettings, + ChangePassword, }, mixins: [alertMixin], data() { @@ -136,10 +92,8 @@ export default { name: '', displayName: '', email: '', - password: '', - passwordConfirmation: '', isProfileUpdating: false, - isPasswordChanging: false, + errorMessage: '', }; }, validations: { @@ -152,18 +106,6 @@ export default { required, email, }, - password: { - minLength: minLength(6), - }, - passwordConfirmation: { - minLength: minLength(6), - isEqPassword(value) { - if (value !== this.password) { - return false; - } - return true; - }, - }, }, computed: { ...mapGetters({ @@ -190,41 +132,36 @@ export default { this.avatarUrl = this.currentUser.avatar_url; this.displayName = this.currentUser.display_name; }, - async updateUser(type) { + async updateUser() { this.$v.$touch(); if (this.$v.$invalid) { this.showAlert(this.$t('PROFILE_SETTINGS.FORM.ERROR')); return; } - if (type === 'profile') { - this.isProfileUpdating = true; - } else if (type === 'password') { - this.isPasswordChanging = true; - } + + this.isProfileUpdating = true; const hasEmailChanged = this.currentUser.email !== this.email; try { await this.$store.dispatch('updateProfile', { name: this.name, email: this.email, avatar: this.avatarFile, - password: this.password, displayName: this.displayName, - password_confirmation: this.passwordConfirmation, }); this.isProfileUpdating = false; - this.isPasswordChanging = false; if (hasEmailChanged) { clearCookiesOnLogout(); - this.showAlert(this.$t('PROFILE_SETTINGS.AFTER_EMAIL_CHANGED')); - } - if (type === 'profile') { - this.showAlert(this.$t('PROFILE_SETTINGS.UPDATE_SUCCESS')); - } else if (type === 'password') { - this.showAlert(this.$t('PROFILE_SETTINGS.PASSWORD_UPDATE_SUCCESS')); + this.errorMessage = this.$t('PROFILE_SETTINGS.AFTER_EMAIL_CHANGED'); } + this.errorMessage = this.$t('PROFILE_SETTINGS.UPDATE_SUCCESS'); } catch (error) { + this.errorMessage = this.$t('RESET_PASSWORD.API.ERROR_MESSAGE'); + if (error?.response?.data?.error) { + this.errorMessage = error.response.data.error; + } + } finally { this.isProfileUpdating = false; - this.isPasswordChanging = false; + this.showAlert(this.errorMessage); } }, handleImageUpload({ file, url }) { diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js index 3cdc045d1..1ed23a41a 100644 --- a/app/javascript/dashboard/store/modules/auth.js +++ b/app/javascript/dashboard/store/modules/auth.js @@ -102,12 +102,13 @@ export const actions = { }, updateProfile: async ({ commit }, params) => { + // eslint-disable-next-line no-useless-catch try { const response = await authAPI.profileUpdate(params); setUser(response.data, getHeaderExpiry(response)); commit(types.default.SET_CURRENT_USER); } catch (error) { - // Ignore error + throw error; } }, diff --git a/spec/controllers/api/v1/profiles_controller_spec.rb b/spec/controllers/api/v1/profiles_controller_spec.rb index 4282b02a7..0a936c6d4 100644 --- a/spec/controllers/api/v1/profiles_controller_spec.rb +++ b/spec/controllers/api/v1/profiles_controller_spec.rb @@ -39,7 +39,7 @@ RSpec.describe 'Profile API', type: :request do end context 'when it is an authenticated user' do - let(:agent) { create(:user, account: account, role: :agent) } + let(:agent) { create(:user, password: 'Test123!', account: account, role: :agent) } it 'updates the name & email' do new_email = Faker::Internet.email @@ -56,13 +56,23 @@ RSpec.describe 'Profile API', type: :request do expect(agent.email).to eq(new_email) end - it 'updates the password' do + it 'updates the password when current password is provided' do put '/api/v1/profile', - params: { profile: { password: 'test123', password_confirmation: 'test123' } }, + params: { profile: { current_password: 'Test123!', password: 'test123', password_confirmation: 'test123' } }, headers: agent.create_new_auth_token, as: :json expect(response).to have_http_status(:success) + expect(agent.reload.valid_password?('test123')).to eq true + end + + it 'throws error when current password provided is invalid' do + put '/api/v1/profile', + params: { profile: { current_password: 'Test', password: 'test123', password_confirmation: 'test123' } }, + headers: agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:unprocessable_entity) end it 'updates avatar' do