Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions astrbot/dashboard/services/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,12 @@ async def edit_account(self, post_data: object) -> AuthServiceResult:
if not new_pwd and not new_username:
return self.error("新用户名和新密码不能同时为空")

username_to_save = None
if new_username is not None and new_username != "":
if not isinstance(new_username, str) or len(new_username.strip()) < 3:
return self.error("用户名长度至少3位")
username_to_save = new_username.strip()

if new_pwd:
if not isinstance(new_pwd, str):
return self.error("新密码无效")
Expand All @@ -384,8 +390,8 @@ async def edit_account(self, post_data: object) -> AuthServiceResult:
await set_password_change_required(self.db, self.config, False)
if is_totp_enabled(self.config):
await revoke_user_trusted_devices(self.db)
if new_username:
self.config["dashboard"]["username"] = new_username
if username_to_save:
self.config["dashboard"]["username"] = username_to_save

self.config.save_config()

Expand Down
99 changes: 99 additions & 0 deletions tests/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,105 @@ async def test_generated_password_requires_password_change_until_changed(
)


@pytest.mark.asyncio
@pytest.mark.parametrize(
("endpoint", "method"),
[
("/api/auth/account/edit", "post"),
("/api/v1/auth/account", "patch"),
],
)
@pytest.mark.parametrize("new_username", ["ab", " "])
async def test_account_edit_rejects_invalid_username(
app: FastAPIAppAdapter,
core_lifecycle_td: AstrBotCoreLifecycle,
endpoint: str,
method: str,
new_username: str,
):
original_dashboard_config = copy.deepcopy(
core_lifecycle_td.astrbot_config["dashboard"]
)
test_client = app.test_client()
current_username = core_lifecycle_td.astrbot_config["dashboard"]["username"]
current_password = _resolve_dashboard_password(core_lifecycle_td)

try:
login_response = await test_client.post(
"/api/auth/login",
json={"username": current_username, "password": current_password},
)
login_data = await login_response.get_json()
assert login_data["status"] == "ok"
headers = {"Authorization": f"Bearer {login_data['data']['token']}"}

payload = {
"password": current_password,
"new_password": "",
"confirm_password": "",
"new_username": new_username,
}
request = getattr(test_client, method)
response = await request(endpoint, headers=headers, json=payload)
data = await response.get_json()

assert data["status"] == "error"
assert data["message"] == "用户名长度至少3位"
assert (
core_lifecycle_td.astrbot_config["dashboard"]["username"]
== (original_dashboard_config["username"])
)
finally:
await _restore_dashboard_password_state(
core_lifecycle_td,
original_dashboard_config,
)


@pytest.mark.asyncio
async def test_account_edit_trims_valid_username(
app: FastAPIAppAdapter,
core_lifecycle_td: AstrBotCoreLifecycle,
):
original_dashboard_config = copy.deepcopy(
core_lifecycle_td.astrbot_config["dashboard"]
)
test_client = app.test_client()
current_username = core_lifecycle_td.astrbot_config["dashboard"]["username"]
current_password = _resolve_dashboard_password(core_lifecycle_td)

try:
login_response = await test_client.post(
"/api/auth/login",
json={"username": current_username, "password": current_password},
)
login_data = await login_response.get_json()
assert login_data["status"] == "ok"
headers = {"Authorization": f"Bearer {login_data['data']['token']}"}

response = await test_client.post(
"/api/auth/account/edit",
headers=headers,
json={
"password": current_password,
"new_password": "",
"confirm_password": "",
"new_username": " astrbot-admin ",
},
)
data = await response.get_json()

assert data["status"] == "ok"
assert core_lifecycle_td.astrbot_config["dashboard"]["username"] == (
"astrbot-admin"
)
finally:
await _restore_dashboard_password_state(
core_lifecycle_td,
original_dashboard_config,
)


@pytest.mark.asyncio
async def test_local_setup_can_skip_default_password_auth(
app: FastAPIAppAdapter,
Expand Down
Loading