Skip to content

Commit 31d4e30

Browse files
whatevertogoclaude
andauthored
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>
1 parent 9a7a594 commit 31d4e30

File tree

4 files changed

+33
-12
lines changed

4 files changed

+33
-12
lines changed

astrbot/dashboard/routes/auth.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ async def edit_account(self):
6868
Response().error("新用户名和新密码不能同时为空,你改了个寂寞").__dict__
6969
)
7070

71+
# Verify password confirmation
7172
if new_pwd:
73+
confirm_pwd = post_data.get("confirm_password", None)
74+
if confirm_pwd != new_pwd:
75+
return Response().error("两次输入的新密码不一致,健忘症患者?").__dict__
7276
self.config["dashboard"]["password"] = new_pwd
7377
if new_username:
7478
self.config["dashboard"]["username"] = new_username

dashboard/src/i18n/locales/en-US/core/header.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,17 @@
7272
"form": {
7373
"currentPassword": "Current Password",
7474
"newPassword": "New Password",
75+
"confirmPassword": "Confirm New Password",
7576
"newUsername": "New Username (Optional)",
7677
"passwordHint": "Password must be at least 8 characters",
78+
"confirmPasswordHint": "Please enter new password again to confirm",
7779
"usernameHint": "Leave blank to keep current username",
7880
"defaultCredentials": "Default username and password are both astrbot"
7981
},
8082
"validation": {
8183
"passwordRequired": "Please enter password",
8284
"passwordMinLength": "Password must be at least 8 characters",
85+
"passwordMatch": "Passwords do not match",
8386
"usernameMinLength": "Username must be at least 3 characters"
8487
},
8588
"actions": {

dashboard/src/i18n/locales/zh-CN/core/header.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,17 @@
7272
"form": {
7373
"currentPassword": "当前密码",
7474
"newPassword": "新密码",
75+
"confirmPassword": "确认新密码",
7576
"newUsername": "新用户名 (可选)",
7677
"passwordHint": "密码长度至少 8 位",
78+
"confirmPasswordHint": "请再次输入新密码以确认",
7779
"usernameHint": "留空表示不修改用户名",
7880
"defaultCredentials": "默认用户名和密码均为 astrbot"
7981
},
8082
"validation": {
8183
"passwordRequired": "请输入密码",
8284
"passwordMinLength": "密码长度至少 8 位",
85+
"passwordMatch": "两次输入的密码不一致",
8386
"usernameMinLength": "用户名长度至少3位"
8487
},
8588
"actions": {
@@ -90,4 +93,4 @@
9093
"updateFailed": "修改失败,请重试"
9194
}
9295
}
93-
}
96+
}

dashboard/src/layouts/full/vertical-header/VerticalHeader.vue

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ let aboutDialog = ref(false);
3333
const username = localStorage.getItem('user');
3434
let password = ref('');
3535
let newPassword = ref('');
36+
let confirmPassword = ref('');
3637
let newUsername = ref('');
3738
let status = ref('');
3839
let updateStatus = ref('')
@@ -89,13 +90,18 @@ const passwordRules = computed(() => [
8990
(v: string) => !!v || t('core.header.accountDialog.validation.passwordRequired'),
9091
(v: string) => v.length >= 8 || t('core.header.accountDialog.validation.passwordMinLength')
9192
]);
93+
const confirmPasswordRules = computed(() => [
94+
(v: string) => !newPassword.value || !!v || t('core.header.accountDialog.validation.passwordRequired'),
95+
(v: string) => !newPassword.value || v === newPassword.value || t('core.header.accountDialog.validation.passwordMatch')
96+
]);
9297
const usernameRules = computed(() => [
9398
(v: string) => !v || v.length >= 3 || t('core.header.accountDialog.validation.usernameMinLength')
9499
]);
95100
96101
// 显示密码相关
97102
const showPassword = ref(false);
98103
const showNewPassword = ref(false);
104+
const showConfirmPassword = ref(false);
99105
100106
// 账户修改状态
101107
const accountEditStatus = ref({
@@ -169,17 +175,14 @@ function accountEdit() {
169175
accountEditStatus.value.error = false;
170176
accountEditStatus.value.success = false;
171177
172-
// md5加密
173-
// @ts-ignore
174-
if (password.value != '') {
175-
password.value = md5(password.value);
176-
}
177-
if (newPassword.value != '') {
178-
newPassword.value = md5(newPassword.value);
179-
}
178+
const passwordHash = password.value ? md5(password.value) : '';
179+
const newPasswordHash = newPassword.value ? md5(newPassword.value) : '';
180+
const confirmPasswordHash = confirmPassword.value ? md5(confirmPassword.value) : '';
181+
180182
axios.post('/api/auth/account/edit', {
181-
password: password.value,
182-
new_password: newPassword.value,
183+
password: passwordHash,
184+
new_password: newPasswordHash,
185+
confirm_password: confirmPasswordHash,
183186
new_username: newUsername.value ? newUsername.value : username
184187
})
185188
.then((res) => {
@@ -188,6 +191,7 @@ function accountEdit() {
188191
accountEditStatus.value.message = res.data.message;
189192
password.value = '';
190193
newPassword.value = '';
194+
confirmPassword.value = '';
191195
return;
192196
}
193197
accountEditStatus.value.success = true;
@@ -204,6 +208,7 @@ function accountEdit() {
204208
accountEditStatus.value.message = typeof err === 'string' ? err : t('core.header.accountDialog.messages.updateFailed');
205209
password.value = '';
206210
newPassword.value = '';
211+
confirmPassword.value = '';
207212
})
208213
.finally(() => {
209214
accountEditStatus.value.loading = false;
@@ -734,10 +739,16 @@ onMounted(async () => {
734739

735740
<v-text-field v-model="newPassword" :append-inner-icon="showNewPassword ? 'mdi-eye-off' : 'mdi-eye'"
736741
:type="showNewPassword ? 'text' : 'password'" :rules="passwordRules"
737-
:label="t('core.header.accountDialog.form.newPassword')" variant="outlined" required clearable
742+
:label="t('core.header.accountDialog.form.newPassword')" variant="outlined" clearable
738743
@click:append-inner="showNewPassword = !showNewPassword" prepend-inner-icon="mdi-lock-plus-outline"
739744
:hint="t('core.header.accountDialog.form.passwordHint')" persistent-hint class="mb-4"></v-text-field>
740745

746+
<v-text-field v-model="confirmPassword" :append-inner-icon="showConfirmPassword ? 'mdi-eye-off' : 'mdi-eye'"
747+
:type="showConfirmPassword ? 'text' : 'password'" :rules="confirmPasswordRules"
748+
:label="t('core.header.accountDialog.form.confirmPassword')" variant="outlined" clearable
749+
@click:append-inner="showConfirmPassword = !showConfirmPassword" prepend-inner-icon="mdi-lock-check-outline"
750+
:hint="t('core.header.accountDialog.form.confirmPasswordHint')" persistent-hint class="mb-4"></v-text-field>
751+
741752
<v-text-field v-model="newUsername" :rules="usernameRules"
742753
:label="t('core.header.accountDialog.form.newUsername')" variant="outlined" clearable
743754
prepend-inner-icon="mdi-account-edit-outline" :hint="t('core.header.accountDialog.form.usernameHint')"

0 commit comments

Comments
 (0)