Skip to content

Commit 0083531

Browse files
fix: validate dashboard account username updates (AstrBotDevs#9175)
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent 4d31b9d commit 0083531

2 files changed

Lines changed: 107 additions & 2 deletions

File tree

astrbot/dashboard/services/auth_service.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,12 @@ async def edit_account(self, post_data: object) -> AuthServiceResult:
369369
if not new_pwd and not new_username:
370370
return self.error("新用户名和新密码不能同时为空")
371371

372+
username_to_save = None
373+
if new_username is not None and new_username != "":
374+
if not isinstance(new_username, str) or len(new_username.strip()) < 3:
375+
return self.error("用户名长度至少3位")
376+
username_to_save = new_username.strip()
377+
372378
if new_pwd:
373379
if not isinstance(new_pwd, str):
374380
return self.error("新密码无效")
@@ -384,8 +390,8 @@ async def edit_account(self, post_data: object) -> AuthServiceResult:
384390
await set_password_change_required(self.db, self.config, False)
385391
if is_totp_enabled(self.config):
386392
await revoke_user_trusted_devices(self.db)
387-
if new_username:
388-
self.config["dashboard"]["username"] = new_username
393+
if username_to_save:
394+
self.config["dashboard"]["username"] = username_to_save
389395

390396
self.config.save_config()
391397

tests/test_dashboard.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,105 @@ async def test_generated_password_requires_password_change_until_changed(
13601360
)
13611361

13621362

1363+
@pytest.mark.asyncio
1364+
@pytest.mark.parametrize(
1365+
("endpoint", "method"),
1366+
[
1367+
("/api/auth/account/edit", "post"),
1368+
("/api/v1/auth/account", "patch"),
1369+
],
1370+
)
1371+
@pytest.mark.parametrize("new_username", ["ab", " "])
1372+
async def test_account_edit_rejects_invalid_username(
1373+
app: FastAPIAppAdapter,
1374+
core_lifecycle_td: AstrBotCoreLifecycle,
1375+
endpoint: str,
1376+
method: str,
1377+
new_username: str,
1378+
):
1379+
original_dashboard_config = copy.deepcopy(
1380+
core_lifecycle_td.astrbot_config["dashboard"]
1381+
)
1382+
test_client = app.test_client()
1383+
current_username = core_lifecycle_td.astrbot_config["dashboard"]["username"]
1384+
current_password = _resolve_dashboard_password(core_lifecycle_td)
1385+
1386+
try:
1387+
login_response = await test_client.post(
1388+
"/api/auth/login",
1389+
json={"username": current_username, "password": current_password},
1390+
)
1391+
login_data = await login_response.get_json()
1392+
assert login_data["status"] == "ok"
1393+
headers = {"Authorization": f"Bearer {login_data['data']['token']}"}
1394+
1395+
payload = {
1396+
"password": current_password,
1397+
"new_password": "",
1398+
"confirm_password": "",
1399+
"new_username": new_username,
1400+
}
1401+
request = getattr(test_client, method)
1402+
response = await request(endpoint, headers=headers, json=payload)
1403+
data = await response.get_json()
1404+
1405+
assert data["status"] == "error"
1406+
assert data["message"] == "用户名长度至少3位"
1407+
assert (
1408+
core_lifecycle_td.astrbot_config["dashboard"]["username"]
1409+
== (original_dashboard_config["username"])
1410+
)
1411+
finally:
1412+
await _restore_dashboard_password_state(
1413+
core_lifecycle_td,
1414+
original_dashboard_config,
1415+
)
1416+
1417+
1418+
@pytest.mark.asyncio
1419+
async def test_account_edit_trims_valid_username(
1420+
app: FastAPIAppAdapter,
1421+
core_lifecycle_td: AstrBotCoreLifecycle,
1422+
):
1423+
original_dashboard_config = copy.deepcopy(
1424+
core_lifecycle_td.astrbot_config["dashboard"]
1425+
)
1426+
test_client = app.test_client()
1427+
current_username = core_lifecycle_td.astrbot_config["dashboard"]["username"]
1428+
current_password = _resolve_dashboard_password(core_lifecycle_td)
1429+
1430+
try:
1431+
login_response = await test_client.post(
1432+
"/api/auth/login",
1433+
json={"username": current_username, "password": current_password},
1434+
)
1435+
login_data = await login_response.get_json()
1436+
assert login_data["status"] == "ok"
1437+
headers = {"Authorization": f"Bearer {login_data['data']['token']}"}
1438+
1439+
response = await test_client.post(
1440+
"/api/auth/account/edit",
1441+
headers=headers,
1442+
json={
1443+
"password": current_password,
1444+
"new_password": "",
1445+
"confirm_password": "",
1446+
"new_username": " astrbot-admin ",
1447+
},
1448+
)
1449+
data = await response.get_json()
1450+
1451+
assert data["status"] == "ok"
1452+
assert core_lifecycle_td.astrbot_config["dashboard"]["username"] == (
1453+
"astrbot-admin"
1454+
)
1455+
finally:
1456+
await _restore_dashboard_password_state(
1457+
core_lifecycle_td,
1458+
original_dashboard_config,
1459+
)
1460+
1461+
13631462
@pytest.mark.asyncio
13641463
async def test_local_setup_can_skip_default_password_auth(
13651464
app: FastAPIAppAdapter,

0 commit comments

Comments
 (0)