feat: add password confirmation when changing password (#5247)
* feat: add password confirmation when changing password Fixes #5177 Adds a password confirmation field to prevent accidental password typos. Changes: - Backend: validate confirm_password matches new_password - Frontend: add confirmation input with validation - i18n: add labels and error messages for password mismatch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(auth): improve error message for password confirmation mismatch * fix(auth): update password hashing logic and improve confirmation validation --------- Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,7 +68,11 @@ class AuthRoute(Route):
|
||||
Response().error("新用户名和新密码不能同时为空,你改了个寂寞").__dict__
|
||||
)
|
||||
|
||||
# Verify password confirmation
|
||||
if new_pwd:
|
||||
confirm_pwd = post_data.get("confirm_password", None)
|
||||
if confirm_pwd != new_pwd:
|
||||
return Response().error("两次输入的新密码不一致,健忘症患者?").__dict__
|
||||
self.config["dashboard"]["password"] = new_pwd
|
||||
if new_username:
|
||||
self.config["dashboard"]["username"] = new_username
|
||||
|
||||
@@ -72,14 +72,17 @@
|
||||
"form": {
|
||||
"currentPassword": "Current Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm New Password",
|
||||
"newUsername": "New Username (Optional)",
|
||||
"passwordHint": "Password must be at least 8 characters",
|
||||
"confirmPasswordHint": "Please enter new password again to confirm",
|
||||
"usernameHint": "Leave blank to keep current username",
|
||||
"defaultCredentials": "Default username and password are both astrbot"
|
||||
},
|
||||
"validation": {
|
||||
"passwordRequired": "Please enter password",
|
||||
"passwordMinLength": "Password must be at least 8 characters",
|
||||
"passwordMatch": "Passwords do not match",
|
||||
"usernameMinLength": "Username must be at least 3 characters"
|
||||
},
|
||||
"actions": {
|
||||
|
||||
@@ -72,14 +72,17 @@
|
||||
"form": {
|
||||
"currentPassword": "当前密码",
|
||||
"newPassword": "新密码",
|
||||
"confirmPassword": "确认新密码",
|
||||
"newUsername": "新用户名 (可选)",
|
||||
"passwordHint": "密码长度至少 8 位",
|
||||
"confirmPasswordHint": "请再次输入新密码以确认",
|
||||
"usernameHint": "留空表示不修改用户名",
|
||||
"defaultCredentials": "默认用户名和密码均为 astrbot"
|
||||
},
|
||||
"validation": {
|
||||
"passwordRequired": "请输入密码",
|
||||
"passwordMinLength": "密码长度至少 8 位",
|
||||
"passwordMatch": "两次输入的密码不一致",
|
||||
"usernameMinLength": "用户名长度至少3位"
|
||||
},
|
||||
"actions": {
|
||||
@@ -90,4 +93,4 @@
|
||||
"updateFailed": "修改失败,请重试"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ let aboutDialog = ref(false);
|
||||
const username = localStorage.getItem('user');
|
||||
let password = ref('');
|
||||
let newPassword = ref('');
|
||||
let confirmPassword = ref('');
|
||||
let newUsername = ref('');
|
||||
let status = ref('');
|
||||
let updateStatus = ref('')
|
||||
@@ -89,6 +90,10 @@ const passwordRules = computed(() => [
|
||||
(v: string) => !!v || t('core.header.accountDialog.validation.passwordRequired'),
|
||||
(v: string) => v.length >= 8 || t('core.header.accountDialog.validation.passwordMinLength')
|
||||
]);
|
||||
const confirmPasswordRules = computed(() => [
|
||||
(v: string) => !newPassword.value || !!v || t('core.header.accountDialog.validation.passwordRequired'),
|
||||
(v: string) => !newPassword.value || v === newPassword.value || t('core.header.accountDialog.validation.passwordMatch')
|
||||
]);
|
||||
const usernameRules = computed(() => [
|
||||
(v: string) => !v || v.length >= 3 || t('core.header.accountDialog.validation.usernameMinLength')
|
||||
]);
|
||||
@@ -96,6 +101,7 @@ const usernameRules = computed(() => [
|
||||
// 显示密码相关
|
||||
const showPassword = ref(false);
|
||||
const showNewPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
|
||||
// 账户修改状态
|
||||
const accountEditStatus = ref({
|
||||
@@ -169,17 +175,14 @@ function accountEdit() {
|
||||
accountEditStatus.value.error = false;
|
||||
accountEditStatus.value.success = false;
|
||||
|
||||
// md5加密
|
||||
// @ts-ignore
|
||||
if (password.value != '') {
|
||||
password.value = md5(password.value);
|
||||
}
|
||||
if (newPassword.value != '') {
|
||||
newPassword.value = md5(newPassword.value);
|
||||
}
|
||||
const passwordHash = password.value ? md5(password.value) : '';
|
||||
const newPasswordHash = newPassword.value ? md5(newPassword.value) : '';
|
||||
const confirmPasswordHash = confirmPassword.value ? md5(confirmPassword.value) : '';
|
||||
|
||||
axios.post('/api/auth/account/edit', {
|
||||
password: password.value,
|
||||
new_password: newPassword.value,
|
||||
password: passwordHash,
|
||||
new_password: newPasswordHash,
|
||||
confirm_password: confirmPasswordHash,
|
||||
new_username: newUsername.value ? newUsername.value : username
|
||||
})
|
||||
.then((res) => {
|
||||
@@ -188,6 +191,7 @@ function accountEdit() {
|
||||
accountEditStatus.value.message = res.data.message;
|
||||
password.value = '';
|
||||
newPassword.value = '';
|
||||
confirmPassword.value = '';
|
||||
return;
|
||||
}
|
||||
accountEditStatus.value.success = true;
|
||||
@@ -204,6 +208,7 @@ function accountEdit() {
|
||||
accountEditStatus.value.message = typeof err === 'string' ? err : t('core.header.accountDialog.messages.updateFailed');
|
||||
password.value = '';
|
||||
newPassword.value = '';
|
||||
confirmPassword.value = '';
|
||||
})
|
||||
.finally(() => {
|
||||
accountEditStatus.value.loading = false;
|
||||
@@ -734,10 +739,16 @@ onMounted(async () => {
|
||||
|
||||
<v-text-field v-model="newPassword" :append-inner-icon="showNewPassword ? 'mdi-eye-off' : 'mdi-eye'"
|
||||
:type="showNewPassword ? 'text' : 'password'" :rules="passwordRules"
|
||||
:label="t('core.header.accountDialog.form.newPassword')" variant="outlined" required clearable
|
||||
:label="t('core.header.accountDialog.form.newPassword')" variant="outlined" clearable
|
||||
@click:append-inner="showNewPassword = !showNewPassword" prepend-inner-icon="mdi-lock-plus-outline"
|
||||
:hint="t('core.header.accountDialog.form.passwordHint')" persistent-hint class="mb-4"></v-text-field>
|
||||
|
||||
<v-text-field v-model="confirmPassword" :append-inner-icon="showConfirmPassword ? 'mdi-eye-off' : 'mdi-eye'"
|
||||
:type="showConfirmPassword ? 'text' : 'password'" :rules="confirmPasswordRules"
|
||||
:label="t('core.header.accountDialog.form.confirmPassword')" variant="outlined" clearable
|
||||
@click:append-inner="showConfirmPassword = !showConfirmPassword" prepend-inner-icon="mdi-lock-check-outline"
|
||||
:hint="t('core.header.accountDialog.form.confirmPasswordHint')" persistent-hint class="mb-4"></v-text-field>
|
||||
|
||||
<v-text-field v-model="newUsername" :rules="usernameRules"
|
||||
:label="t('core.header.accountDialog.form.newUsername')" variant="outlined" clearable
|
||||
prepend-inner-icon="mdi-account-edit-outline" :hint="t('core.header.accountDialog.form.usernameHint')"
|
||||
|
||||
Reference in New Issue
Block a user