| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| <template> |
| <div class="user-layout-wrapper"> |
| <div class="container"> |
| <div class="user-layout-content"> |
| <a-card :bordered="false" class="force-password-card"> |
| <template #title> |
| <div style="text-align: center; font-size: 18px; font-weight: bold;"> |
| {{ $t('label.action.change.password') }} |
| </div> |
| <div v-if="!isSubmitted" style="text-align: center; font-size: 14px; color: #666; margin-top: 5px;"> |
| {{ $t('message.change.password.required') }} |
| </div> |
| </template> |
| <a-spin :spinning="loading"> |
| <div v-if="isSubmitted" class="success-state"> |
| <check-outlined class="success-icon" /> |
| <div class="success-text"> |
| {{ $t('message.success.change.password') }} |
| </div> |
| <div class="success-subtext"> |
| {{ $t('message.please.login.new.password') }} |
| </div> |
| <a-button |
| type="primary" |
| size="large" |
| block |
| @click="redirectToLogin()" |
| style="margin-top: 20px;" |
| > |
| {{ $t('label.login') }} |
| </a-button> |
| </div> |
| |
| <a-form |
| v-else |
| :ref="formRef" |
| :model="form" |
| :rules="rules" |
| layout="vertical" |
| @finish="handleSubmit" |
| v-ctrl-enter="handleSubmit" |
| > |
| <a-form-item name="currentpassword" :label="$t('label.currentpassword')"> |
| <a-input-password |
| v-model:value="form.currentpassword" |
| :placeholder="$t('label.currentpassword')" |
| size="large" |
| v-focus="true" |
| /> |
| </a-form-item> |
| |
| <a-form-item name="password" :label="$t('label.new.password')"> |
| <a-input-password |
| v-model:value="form.password" |
| :placeholder="$t('label.new.password')" |
| size="large" |
| /> |
| </a-form-item> |
| |
| <a-form-item name="confirmpassword" :label="$t('label.confirmpassword')"> |
| <a-input-password |
| v-model:value="form.confirmpassword" |
| :placeholder="$t('label.confirmpassword')" |
| size="large" |
| /> |
| </a-form-item> |
| |
| <a-form-item> |
| <a-button |
| html-type="submit" |
| type="primary" |
| size="large" |
| block |
| :disabled="loading" |
| :loading="loading" |
| @click="handleSubmit" |
| > |
| {{ $t('label.ok') }} |
| </a-button> |
| </a-form-item> |
| |
| <div class="actions"> |
| <a @click="logoutAndRedirectToLogin()">{{ $t('label.logout') }}</a> |
| </div> |
| </a-form> |
| </a-spin> |
| </a-card> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| import { ref, reactive, toRaw } from 'vue' |
| import { postAPI } from '@/api' |
| import Cookies from 'js-cookie' |
| import { PASSWORD_CHANGE_REQUIRED } from '@/store/mutation-types' |
| |
| export default { |
| name: 'ForceChangePassword', |
| data () { |
| return { |
| loading: false, |
| isSubmitted: false |
| } |
| }, |
| created () { |
| this.formRef = ref() |
| this.form = reactive({}) |
| this.isPasswordChangeRequired() |
| }, |
| computed: { |
| rules () { |
| return { |
| currentpassword: [{ required: true, message: this.$t('message.error.current.password') }], |
| password: [ |
| { required: true, message: this.$t('message.error.new.password') }, |
| { validator: this.validateNewPassword, trigger: 'change' } |
| ], |
| confirmpassword: [ |
| { required: true, message: this.$t('message.error.confirm.password') }, |
| { validator: this.validateTwoPassword, trigger: 'change' } |
| ] |
| } |
| } |
| }, |
| methods: { |
| async validateNewPassword (rule, value) { |
| const currentPassword = this.form.currentpassword |
| if (!value || value.length === 0) { |
| return Promise.resolve() |
| } |
| // Ensure new password is different from current password |
| if (currentPassword && value === currentPassword) { |
| return Promise.reject(this.$t('message.error.newpassword.sameascurrent')) |
| } |
| return Promise.resolve() |
| }, |
| async validateTwoPassword (rule, value) { |
| if (!value || value.length === 0) { |
| return Promise.resolve() |
| } else if (rule.field === 'confirmpassword') { |
| const form = this.form |
| const messageConfirm = this.$t('message.validate.equalto') |
| const passwordVal = form.password |
| if (passwordVal && passwordVal !== value) { |
| return Promise.reject(messageConfirm) |
| } else { |
| return Promise.resolve() |
| } |
| } else { |
| return Promise.resolve() |
| } |
| }, |
| handleSubmit (e) { |
| e.preventDefault() |
| if (this.loading) return |
| this.formRef.value.validate().then(() => { |
| this.loading = true |
| const values = toRaw(this.form) |
| const userId = Cookies.get('userid') |
| |
| const params = { |
| id: userId, |
| password: values.password, |
| currentpassword: values.currentpassword |
| } |
| postAPI('updateUser', params).then(async () => { |
| this.$localStorage.remove(PASSWORD_CHANGE_REQUIRED) |
| await this.handleLogout() |
| this.isSubmitted = true |
| }).catch(error => { |
| console.error(error) |
| this.$message.error(this.$t('message.error.change.password')) |
| }).finally(() => { |
| this.loading = false |
| }) |
| }).catch(error => { |
| console.log('Validation failed:', error) |
| }) |
| }, |
| async handleLogout () { |
| try { |
| await this.$store.dispatch('Logout') |
| } catch (e) { |
| console.error('Logout failed:', e) |
| } finally { |
| Cookies.remove('userid') |
| Cookies.remove('token') |
| } |
| }, |
| redirectToLogin () { |
| this.$router.replace('/user/login') |
| }, |
| logoutAndRedirectToLogin () { |
| this.handleLogout().then(() => { |
| this.redirectToLogin() |
| }) |
| }, |
| async isPasswordChangeRequired () { |
| const passwordChangeRequired = this.$localStorage.get(PASSWORD_CHANGE_REQUIRED) |
| this.isSubmitted = !passwordChangeRequired |
| } |
| } |
| } |
| </script> |
| |
| <style scoped lang="less"> |
| .user-layout-wrapper { |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| |
| .container { |
| width: 100%; |
| padding: 16px; |
| |
| .user-layout-content { |
| display: flex; |
| justify-content: center; |
| |
| .force-password-card { |
| width: 100%; |
| max-width: 420px; |
| border-radius: 8px; |
| box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08); |
| } |
| } |
| } |
| } |
| |
| .actions { |
| text-align: center; |
| margin-top: 16px; |
| |
| a { |
| color: #1890ff; |
| transition: color 0.3s; |
| |
| &:hover { |
| color: #40a9ff; |
| } |
| } |
| } |
| |
| .success-state { |
| text-align: center; |
| padding: 20px 0; |
| |
| .success-icon { |
| font-size: 48px; |
| color: #52c41a; |
| margin-bottom: 16px; |
| } |
| |
| .success-text { |
| font-size: 20px; |
| font-weight: 500; |
| color: #333; |
| margin-bottom: 8px; |
| } |
| |
| .success-subtext { |
| font-size: 14px; |
| color: #666; |
| } |
| } |
| </style> |