Files
AstrBot/astrbot/dashboard/password_state.py
Weilong Liao 7ddf6371b9 fix(webui): enforce 12-char dashboard password policy with backend+frontend validation (#7338)
* fix(webui): enforce 12-char dashboard password policy with backend+frontend validation

* fix(i18n): update password policy hints and validation rules for improved security

* test: adapt dashboard auth fixtures for hashed default password

* fix(security): increase PBKDF2 iterations

* feat(auth): implement secure login challenge and proof verification

* chore: ruff format

* fix(auth): update md5 import syntax for consistency

* feat(dashboard): implement random password generation for empty dashboard password

* feat(auth): enforce plaintext password requirement for legacy MD5 hashes

* fix(i18n): update password hint texts to reflect auto-generated initial passwords

* feat(dashboard): implement password change requirement and reset logic

* feat(auth): implement account setup flow and password change requirements

* feat: Implement legacy password support and upgrade mechanism

- Added `hash_legacy_dashboard_password` function for MD5 hashing of passwords.
- Updated configuration handling to store both PBKDF2 and legacy password hashes.
- Introduced logic to check if password storage has been upgraded and if a password change is required.
- Modified dashboard authentication routes to handle legacy password checks and prompts for upgrades.
- Updated frontend to display warnings for legacy password storage and required upgrades.
- Enhanced tests to cover scenarios for legacy password handling and migration to new storage format.

* fix(logo): update text color styles to use CSS variables for consistency

* feat(dashboard): upgrade password storage and enforce change requirement

* fix(dashboard): update minimum password length from 12 to 10 characters

* fix(dashboard): update minimum password length from 10 to 8 characters

---------

Co-authored-by: RC-CHN <1051989940@qq.com>
2026-05-12 23:46:57 +08:00

95 lines
2.9 KiB
Python

from astrbot.core.config.astrbot_config import AstrBotConfig
from astrbot.core.db import BaseDatabase
from astrbot.core.utils.auth_password import (
hash_dashboard_password,
hash_legacy_dashboard_password,
is_legacy_dashboard_password,
)
PASSWORD_STORAGE_UPGRADED_KEY = "password_storage_upgraded"
PASSWORD_CHANGE_REQUIRED_KEY = "password_change_required"
def _set_dashboard_flag(config: AstrBotConfig, key: str, value: bool) -> None:
if config["dashboard"].get(key) == bool(value):
return
config["dashboard"][key] = bool(value)
config.save_config()
def _has_usable_pbkdf2_password(config: AstrBotConfig) -> bool:
password = config["dashboard"].get("pbkdf2_password", "")
if not isinstance(password, str) or not password.startswith("pbkdf2_sha256$"):
return False
parts = password.split("$")
if len(parts) != 4:
return False
_, iterations, salt, digest = parts
try:
int(iterations)
bytes.fromhex(salt)
bytes.fromhex(digest)
except ValueError:
return False
return True
async def is_password_storage_upgraded(
db: BaseDatabase,
config: AstrBotConfig,
) -> bool:
config_upgraded = _has_usable_pbkdf2_password(config)
if config["dashboard"].get(PASSWORD_STORAGE_UPGRADED_KEY) != config_upgraded:
_set_dashboard_flag(config, PASSWORD_STORAGE_UPGRADED_KEY, config_upgraded)
return config_upgraded
async def set_password_storage_upgraded(
db: BaseDatabase,
config: AstrBotConfig,
upgraded: bool,
) -> None:
_set_dashboard_flag(config, PASSWORD_STORAGE_UPGRADED_KEY, upgraded)
async def is_password_change_required(
db: BaseDatabase,
config: AstrBotConfig,
) -> bool:
stored = config["dashboard"].get(PASSWORD_CHANGE_REQUIRED_KEY, None)
if stored is not None:
return bool(stored)
required = bool(
getattr(config, "_generated_dashboard_password_change_required", False)
or getattr(config, "_dashboard_password_change_required_from_config", False)
)
if required:
_set_dashboard_flag(config, PASSWORD_CHANGE_REQUIRED_KEY, True)
return required
async def set_password_change_required(
db: BaseDatabase,
config: AstrBotConfig,
required: bool,
) -> None:
_set_dashboard_flag(config, PASSWORD_CHANGE_REQUIRED_KEY, required)
def get_dashboard_password_hash(config: AstrBotConfig, *, upgraded: bool) -> str:
if upgraded and _has_usable_pbkdf2_password(config):
return config["dashboard"].get("pbkdf2_password", "")
legacy_password = config["dashboard"].get("password", "")
if upgraded and not is_legacy_dashboard_password(legacy_password):
return ""
return legacy_password
def set_dashboard_password_hashes(config: AstrBotConfig, raw_password: str) -> None:
config["dashboard"]["pbkdf2_password"] = hash_dashboard_password(raw_password)
config["dashboard"]["password"] = hash_legacy_dashboard_password(raw_password)