Skip to content

Commit 6b7b1eb

Browse files
committed
fix: allow display name updates without requiring the current password
1 parent 1e9638c commit 6b7b1eb

7 files changed

Lines changed: 61 additions & 13 deletions

File tree

scheduler-server/src/api/routes/auth.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class AuthUserRead(BaseModel):
2727

2828
class ProfileUpdateRequest(BaseModel):
2929
display_name: str = Field(min_length=1, max_length=120)
30-
current_password: str = Field(min_length=1, max_length=255)
30+
current_password: str | None = Field(default=None, min_length=1, max_length=255)
3131
new_password: str | None = Field(default=None, min_length=8, max_length=255)
3232

3333

@@ -81,10 +81,17 @@ def update_profile(
8181
user = get_current_user_from_request(request, db)
8282
if user is None:
8383
raise HTTPException(status_code=401, detail="Authentication required")
84-
if not verify_password(payload.current_password, user.password_hash):
85-
raise HTTPException(status_code=401, detail="Invalid username or password")
84+
display_name = payload.display_name.strip()
85+
if not display_name:
86+
raise HTTPException(status_code=400, detail="Display name is required")
87+
88+
if payload.new_password is not None:
89+
if not payload.current_password or not verify_password(payload.current_password, user.password_hash):
90+
raise HTTPException(status_code=401, detail="Invalid username or password")
91+
if not payload.new_password.strip():
92+
raise HTTPException(status_code=400, detail="New password cannot be empty")
8693

87-
user.display_name = payload.display_name.strip()
94+
user.display_name = display_name
8895
if payload.new_password:
8996
user.password_hash = hash_password(payload.new_password)
9097
db.commit()

scheduler-server/tests/test_stage1_backend.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,29 @@ def test_auth_profile_update_changes_display_name_and_password(self) -> None:
225225
self.assertEqual(new_login.json()["display_name"], "Owner")
226226
self.assertFalse(new_login.json()["using_default_password"])
227227

228+
def test_auth_profile_update_allows_display_name_change_without_current_password(self) -> None:
229+
self.client.cookies.clear()
230+
login_response = self.client.post(
231+
"/api/auth/login",
232+
json={"username": "admin", "password": "admin123456"},
233+
)
234+
self.assertEqual(login_response.status_code, 200)
235+
236+
update_response = self.client.put(
237+
"/api/auth/profile",
238+
json={"display_name": "Owner Only"},
239+
)
240+
self.assertEqual(update_response.status_code, 200)
241+
self.assertEqual(update_response.json()["display_name"], "Owner Only")
242+
self.assertTrue(update_response.json()["using_default_password"])
243+
244+
login_again = self.client.post(
245+
"/api/auth/login",
246+
json={"username": "admin", "password": "admin123456"},
247+
)
248+
self.assertEqual(login_again.status_code, 200)
249+
self.assertEqual(login_again.json()["display_name"], "Owner Only")
250+
228251
def test_health_remains_public_without_login(self) -> None:
229252
self.client.cookies.clear()
230253
response = self.client.get("/api/health")

web-client/src/api/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type LoginPayload = {
1414

1515
export type UpdateProfilePayload = {
1616
display_name: string;
17-
current_password: string;
17+
current_password?: string;
1818
new_password?: string;
1919
};
2020

web-client/src/components/common/AccountDialog.vue

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
<template>
22
<el-dialog
33
:model-value="visible"
4-
:title="t('auth.account')"
4+
:title="t('auth.accountSettings')"
55
width="460px"
66
@close="emit('update:visible', false)"
77
>
88
<el-form label-position="top">
9+
<el-form-item :label="t('auth.username')">
10+
<div class="account-dialog__static-value">{{ username }}</div>
11+
</el-form-item>
912
<el-form-item :label="t('auth.displayName')">
1013
<el-input v-model="form.display_name" maxlength="120" />
1114
</el-form-item>
@@ -37,14 +40,15 @@ import { useI18n } from "@/composables/useI18n";
3740
const props = defineProps<{
3841
visible: boolean;
3942
submitting: boolean;
43+
username: string;
4044
displayName: string;
4145
}>();
4246
4347
const emit = defineEmits<{
4448
"update:visible": [value: boolean];
4549
submit: [{
4650
display_name: string;
47-
current_password: string;
51+
current_password?: string;
4852
new_password?: string;
4953
}];
5054
}>();
@@ -76,11 +80,15 @@ function handleSave() {
7680
const newPassword = form.new_password.trim();
7781
const confirmPassword = form.confirm_password.trim();
7882
79-
if (!displayName || !currentPassword) {
83+
if (!displayName) {
8084
ElMessage.error(t("auth.accountRequired"));
8185
return;
8286
}
8387
if (newPassword || confirmPassword) {
88+
if (!currentPassword) {
89+
ElMessage.error(t("auth.currentPasswordRequired"));
90+
return;
91+
}
8492
if (newPassword !== confirmPassword) {
8593
ElMessage.error(t("auth.passwordMismatch"));
8694
return;
@@ -93,7 +101,7 @@ function handleSave() {
93101
94102
emit("submit", {
95103
display_name: displayName,
96-
current_password: currentPassword,
104+
current_password: currentPassword || undefined,
97105
new_password: newPassword || undefined,
98106
});
99107
}
@@ -105,4 +113,9 @@ function handleSave() {
105113
justify-content: flex-end;
106114
gap: 8px;
107115
}
116+
117+
.account-dialog__static-value {
118+
color: #303133;
119+
line-height: 32px;
120+
}
108121
</style>

web-client/src/components/common/AppTopNav.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
</el-button>
4747
<template #dropdown>
4848
<el-dropdown-menu>
49-
<el-dropdown-item command="account">{{ t("auth.account") }}</el-dropdown-item>
49+
<el-dropdown-item command="account">{{ t("auth.accountSettings") }}</el-dropdown-item>
5050
<el-dropdown-item divided command="logout">{{ t("auth.logout") }}</el-dropdown-item>
5151
</el-dropdown-menu>
5252
</template>
@@ -57,6 +57,7 @@
5757
<AccountDialog
5858
:visible="accountDialogVisible"
5959
:submitting="accountSubmitting"
60+
:username="authStore.user?.username ?? ''"
6061
:display-name="authStore.user?.display_name ?? ''"
6162
@update:visible="accountDialogVisible = $event"
6263
@submit="handleAccountSubmit"
@@ -104,7 +105,7 @@ async function handleCommand(command: string) {
104105
105106
async function handleAccountSubmit(payload: {
106107
display_name: string;
107-
current_password: string;
108+
current_password?: string;
108109
new_password?: string;
109110
}) {
110111
accountSubmitting.value = true;

web-client/src/i18n/en.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ export const en = {
2020
displayName: "Display name",
2121
password: "Password",
2222
account: "Account",
23+
accountSettings: "Account Settings",
2324
accountUpdated: "Account updated",
2425
currentPassword: "Current password",
2526
newPassword: "New password",
2627
confirmPassword: "Confirm password",
2728
passwordMismatch: "The new passwords do not match",
2829
passwordTooShort: "The new password must be at least 8 characters",
29-
accountRequired: "Username and current password are required",
30+
accountRequired: "Display name is required",
31+
currentPasswordRequired: "Current password is required to change the password",
3032
},
3133
messagesPage: {
3234
eyebrow: "Messages",

web-client/src/i18n/zh-CN.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ export const zhCN = {
2020
displayName: "显示名",
2121
password: "密码",
2222
account: "账户",
23+
accountSettings: "账号管理",
2324
accountUpdated: "账户信息已更新",
2425
currentPassword: "当前密码",
2526
newPassword: "新密码",
2627
confirmPassword: "确认新密码",
2728
passwordMismatch: "两次输入的新密码不一致",
2829
passwordTooShort: "新密码至少需要 8 位",
29-
accountRequired: "用户名和当前密码不能为空",
30+
accountRequired: "显示名不能为空",
31+
currentPasswordRequired: "修改密码时必须填写当前密码",
3032
},
3133
messagesPage: {
3234
eyebrow: "消息",

0 commit comments

Comments
 (0)