mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
feat(password): add command to change AstrBot dashboard password (#8272)
closes: #8268
This commit is contained in:
@@ -5,7 +5,7 @@ import sys
|
||||
import click
|
||||
|
||||
from . import __version__
|
||||
from .commands import conf, init, plug, run
|
||||
from .commands import conf, init, password, plug, run
|
||||
|
||||
logo_tmpl = r"""
|
||||
___ _______.___________..______ .______ ______ .___________.
|
||||
@@ -54,6 +54,7 @@ cli.add_command(run)
|
||||
cli.add_command(help)
|
||||
cli.add_command(plug)
|
||||
cli.add_command(conf)
|
||||
cli.add_command(password)
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .cmd_conf import conf
|
||||
from .cmd_init import init
|
||||
from .cmd_password import password
|
||||
from .cmd_plug import plug
|
||||
from .cmd_run import run
|
||||
|
||||
__all__ = ["conf", "init", "plug", "run"]
|
||||
__all__ = ["conf", "init", "password", "plug", "run"]
|
||||
|
||||
@@ -137,6 +137,22 @@ def _get_nested_item(obj: dict[str, Any], path: str) -> Any:
|
||||
return obj
|
||||
|
||||
|
||||
def _set_dashboard_password(config: dict[str, Any], raw_password: str) -> None:
|
||||
"""Set dashboard password hashes and clear password migration flags."""
|
||||
_set_nested_item(
|
||||
config,
|
||||
"dashboard.pbkdf2_password",
|
||||
hash_dashboard_password(raw_password),
|
||||
)
|
||||
_set_nested_item(
|
||||
config,
|
||||
"dashboard.password",
|
||||
hash_legacy_dashboard_password(raw_password),
|
||||
)
|
||||
_set_nested_item(config, "dashboard.password_storage_upgraded", True)
|
||||
_set_nested_item(config, "dashboard.password_change_required", False)
|
||||
|
||||
|
||||
@click.group(name="conf")
|
||||
def conf() -> None:
|
||||
"""Configuration management commands
|
||||
@@ -171,16 +187,7 @@ def set_config(key: str, value: str) -> None:
|
||||
old_value = _get_nested_item(config, key)
|
||||
validated_value = CONFIG_VALIDATORS[key](value)
|
||||
if key == "dashboard.password":
|
||||
_set_nested_item(
|
||||
config,
|
||||
"dashboard.pbkdf2_password",
|
||||
hash_dashboard_password(validated_value),
|
||||
)
|
||||
_set_nested_item(
|
||||
config,
|
||||
"dashboard.password",
|
||||
hash_legacy_dashboard_password(validated_value),
|
||||
)
|
||||
_set_dashboard_password(config, validated_value)
|
||||
else:
|
||||
_set_nested_item(config, key, validated_value)
|
||||
_save_config(config)
|
||||
|
||||
38
astrbot/cli/commands/cmd_password.py
Normal file
38
astrbot/cli/commands/cmd_password.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import click
|
||||
|
||||
from .cmd_conf import (
|
||||
_load_config,
|
||||
_save_config,
|
||||
_set_dashboard_password,
|
||||
_set_nested_item,
|
||||
_validate_dashboard_password,
|
||||
_validate_dashboard_username,
|
||||
)
|
||||
|
||||
|
||||
@click.command(name="password")
|
||||
@click.option(
|
||||
"--username",
|
||||
help="Optional dashboard username to set together with the new password.",
|
||||
)
|
||||
def password(username: str | None) -> None:
|
||||
"""Change the AstrBot dashboard password."""
|
||||
config = _load_config()
|
||||
|
||||
new_password = click.prompt(
|
||||
"New dashboard password",
|
||||
hide_input=True,
|
||||
confirmation_prompt=True,
|
||||
)
|
||||
validated_password = _validate_dashboard_password(new_password)
|
||||
|
||||
if username is not None:
|
||||
validated_username = _validate_dashboard_username(username.strip())
|
||||
_set_nested_item(config, "dashboard.username", validated_username)
|
||||
|
||||
_set_dashboard_password(config, validated_password)
|
||||
_save_config(config)
|
||||
|
||||
click.echo("Dashboard password updated.")
|
||||
if username is not None:
|
||||
click.echo(f"Dashboard username updated: {validated_username}")
|
||||
90
tests/test_cli_password.py
Normal file
90
tests/test_cli_password.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import copy
|
||||
import json
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from astrbot.cli.commands.cmd_conf import conf
|
||||
from astrbot.cli.commands.cmd_password import password
|
||||
from astrbot.core.config.default import DEFAULT_CONFIG
|
||||
from astrbot.core.utils.auth_password import verify_dashboard_password
|
||||
|
||||
|
||||
def _write_config(root):
|
||||
(root / ".astrbot").touch()
|
||||
data_dir = root / "data"
|
||||
data_dir.mkdir()
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
config["dashboard"]["password_change_required"] = True
|
||||
config["dashboard"]["password_storage_upgraded"] = False
|
||||
config_path = data_dir / "cmd_config.json"
|
||||
config_path.write_text(
|
||||
json.dumps(config, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8-sig",
|
||||
)
|
||||
return config_path
|
||||
|
||||
|
||||
def _read_config(config_path):
|
||||
return json.loads(config_path.read_text(encoding="utf-8-sig"))
|
||||
|
||||
|
||||
def test_password_command_changes_dashboard_password(monkeypatch, tmp_path):
|
||||
config_path = _write_config(tmp_path)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
password,
|
||||
input="AstrbotChanged123\nAstrbotChanged123\n",
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
config = _read_config(config_path)
|
||||
dashboard_config = config["dashboard"]
|
||||
assert verify_dashboard_password(
|
||||
dashboard_config["pbkdf2_password"],
|
||||
"AstrbotChanged123",
|
||||
)
|
||||
assert verify_dashboard_password(
|
||||
dashboard_config["password"],
|
||||
"AstrbotChanged123",
|
||||
)
|
||||
assert dashboard_config["password_storage_upgraded"] is True
|
||||
assert dashboard_config["password_change_required"] is False
|
||||
|
||||
|
||||
def test_password_command_can_update_dashboard_username(monkeypatch, tmp_path):
|
||||
config_path = _write_config(tmp_path)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
password,
|
||||
["--username", "astrbot-admin"],
|
||||
input="AstrbotChanged123\nAstrbotChanged123\n",
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
config = _read_config(config_path)
|
||||
assert config["dashboard"]["username"] == "astrbot-admin"
|
||||
|
||||
|
||||
def test_conf_set_dashboard_password_updates_password_state(monkeypatch, tmp_path):
|
||||
config_path = _write_config(tmp_path)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
conf,
|
||||
["set", "dashboard.password", "AstrbotChanged123"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
config = _read_config(config_path)
|
||||
dashboard_config = config["dashboard"]
|
||||
assert verify_dashboard_password(
|
||||
dashboard_config["pbkdf2_password"],
|
||||
"AstrbotChanged123",
|
||||
)
|
||||
assert dashboard_config["password_storage_upgraded"] is True
|
||||
assert dashboard_config["password_change_required"] is False
|
||||
Reference in New Issue
Block a user