Fix high-frequency disabled realtime learning calls#195
Merged
EterUltimate merged 2 commits intoJun 11, 2026
Merged
Conversation
Contributor
Reviewer's GuideImplements safer defaults for realtime learning and LLM hooks, adds a per‑group cooldown for realtime expression learning, and introduces an optional WebUI password authentication flow (with default initial password and forced rotation) wired through config, middleware, blueprints, and tests. Sequence diagram for WebUI optional password login and password changesequenceDiagram
actor User
participant Browser
participant AuthBlueprint as auth_blueprint
participant AuthService as auth_service
User->>Browser: Open /api/login
Browser->>auth_blueprint: GET /login
auth_blueprint->>AuthService: AuthService(get_container)
alt [auth_service.is_password_enabled]
alt [is_authenticated]
auth_blueprint-->>Browser: 302 -> /api/index
else [not authenticated]
auth_blueprint-->>Browser: Render login.html
end
else [password not enabled]
auth_blueprint-->>Browser: 302 -> /api/index
end
User->>Browser: Submit password
Browser->>auth_blueprint: POST /login (password)
auth_blueprint->>AuthService: login(password, client_ip)
alt [passwordless mode]
AuthService-->>auth_blueprint: success, redirect /api/index
auth_blueprint-->>Browser: 200 {redirect:/api/index}
else [password enabled]
alt [valid password]
AuthService-->>auth_blueprint: success, must_change flag
auth_blueprint->>Browser: 200 {redirect, must_change}
alt [must_change true]
Browser->>auth_blueprint: GET /plugin_change_password
auth_blueprint->>AuthService: is_password_enabled
auth_blueprint-->>Browser: Render change_password.html
User->>Browser: Submit old_password, new_password
Browser->>auth_blueprint: POST /plugin_change_password
auth_blueprint->>AuthService: change_password(old_password, new_password)
alt [change success]
AuthService-->>auth_blueprint: (True, message)
auth_blueprint-->>Browser: 200 {redirect:/api/index}
else [change failed]
AuthService-->>auth_blueprint: (False, error)
auth_blueprint-->>Browser: 400 {error}
end
else [must_change false]
note over Browser,AuthService: User goes directly to dashboard
end
else [invalid password]
AuthService-->>auth_blueprint: False, error, remaining_attempts
auth_blueprint-->>Browser: 401/429 {error}
end
end
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The logic for determining whether WebUI password auth is enabled is duplicated between
AuthService.is_password_enabled()and_webui_password_enabled()in the middleware; consider centralizing this into a single helper or source of truth to avoid future drift between behaviors. - In
RealtimeProcessor._process_expression_style_learning,_last_expression_learning_timesis keyed only bygroup_id; if you ever introduce per-plugin-instance or per-platform separation, you may want to include additional dimensions (e.g. platform or tenant) in the key to avoid unintended cross-group throttling collisions.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The logic for determining whether WebUI password auth is enabled is duplicated between `AuthService.is_password_enabled()` and `_webui_password_enabled()` in the middleware; consider centralizing this into a single helper or source of truth to avoid future drift between behaviors.
- In `RealtimeProcessor._process_expression_style_learning`, `_last_expression_learning_times` is keyed only by `group_id`; if you ever introduce per-plugin-instance or per-platform separation, you may want to include additional dimensions (e.g. platform or tenant) in the key to avoid unintended cross-group throttling collisions.
## Individual Comments
### Comment 1
<location path="webui/services/auth_service.py" line_range="29-30" />
<code_context>
logger = get_astrbot_logger("self_learning.webui.auth")
-DEFAULT_PASSWORD_CONFIG = {"must_change": False}
+PASSWORDLESS_PASSWORD_CONFIG = {"must_change": False}
+DEFAULT_WEBUI_PASSWORD = "self_learning_pwd"
+DEFAULT_PASSWORD_CONFIG = {
+ "password": DEFAULT_WEBUI_PASSWORD,
+ "must_change": True,
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Relying on a hardcoded default WebUI password has security implications and might warrant a safer initialization path.
With `enable_webui_password=True` and no stored config, this path uses the hardcoded `DEFAULT_WEBUI_PASSWORD`, so anyone who knows the default can log in until it’s changed, despite `must_change=True`.
Consider instead requiring an explicit initial password (env/config) when enabling the WebUI password, or at least emitting a prominent warning with clear instructions to change it immediately. Another option is to refuse enabling password mode without a persisted config so you never run with a known default secret.
Suggested implementation:
```python
import os
logger = get_astrbot_logger("self_learning.webui.auth")
PASSWORDLESS_PASSWORD_CONFIG = {"must_change": False}
INITIAL_WEBUI_PASSWORD_ENV_VAR = "ASTRBOT_WEBUI_INITIAL_PASSWORD"
def _build_default_password_config() -> Dict[str, Any]:
"""
Build the default WebUI password configuration.
This requires an explicit initial password to be provided via
the environment variable defined in INITIAL_WEBUI_PASSWORD_ENV_VAR.
If no password is configured, this function logs a prominent warning
and returns a passwordless configuration instead of using a known
default secret.
"""
initial_password = os.getenv(INITIAL_WEBUI_PASSWORD_ENV_VAR)
if not initial_password:
logger.warning(
(
"WebUI password authentication is enabled but no initial password "
"is configured. Refusing to use a built-in default password. "
"Set %s to a strong secret or persist a password configuration "
"before enabling WebUI password protection."
),
INITIAL_WEBUI_PASSWORD_ENV_VAR,
)
# Fall back to a passwordless configuration so we never run with a
# known default password.
return PASSWORDLESS_PASSWORD_CONFIG.copy()
return {
"password": initial_password,
"must_change": True,
}
# NOTE: Do not call _build_default_password_config() eagerly here based solely on
# imports; it should only be invoked in the logic that enables WebUI password
# mode and detects a missing persisted configuration.
DEFAULT_PASSWORD_CONFIG: Dict[str, Any] = PASSWORDLESS_PASSWORD_CONFIG.copy()
```
To fully implement the safer initialization path and avoid ever running with a known default password, you should:
1. Locate the logic in `webui/services/auth_service.py` where:
- `enable_webui_password` is checked (via `self.plugin_config.enable_webui_password` or similar), and
- the password configuration is loaded or defaulted (where `DEFAULT_PASSWORD_CONFIG` was previously used when no stored config exists).
2. Replace any direct use of `DEFAULT_PASSWORD_CONFIG` as the "default when nothing is stored" with a call to `_build_default_password_config()` **only in the branch where**:
- `enable_webui_password` is `True`, and
- there is no persisted password configuration.
For example (pseudo‑code):
- `password_config = _build_default_password_config()` instead of `password_config = DEFAULT_PASSWORD_CONFIG`.
3. In that same branch, after calling `_build_default_password_config()`:
- If the returned config is `PASSWORDLESS_PASSWORD_CONFIG` (i.e. `config.get("password") is None`), treat this as a configuration error:
- Either:
- disable password mode (log at `warning` or `error` level that password auth is being disabled because no explicit password is configured), **or**
- raise an exception to refuse starting with `enable_webui_password=True` and no configured initial password.
Pick the behavior that matches your application's startup/error‑handling conventions.
4. Ensure that `DEFAULT_PASSWORD_CONFIG` (now initialized to `PASSWORDLESS_PASSWORD_CONFIG.copy()`) is only used where a passwordless configuration is explicitly desired, and not as a fallback for password‑protected mode.
These additional adjustments will guarantee that:
- You never run the WebUI with a known hardcoded default password.
- Enabling password mode without an explicit initial password results in a clear warning and either disabled password mode or a hard failure, as per your chosen policy.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
enable_llm_hooksto avoid high-frequency model calls when realtime LLM filtering is off.self_learning_pwdand the user is forced to change it.Tests
python -m json.tool _conf_schema.json > $nullpython -m py_compile config.py services\learning\message_pipeline.py services\learning\realtime_processor.py services\hooks\llm_hook_handler.py webui\services\auth_service.py webui\middleware\auth.py webui\blueprints\auth.py webui\services\config_service.pypython -m pytest -q tests\unit\test_learning_chain_regressions.py tests\unit\test_feature_delegation.py tests\unit\test_config.py tests\unit\test_config_service.py tests\unit\test_auth_service.py tests\integration\test_auth_blueprint.py tests\integration\test_webui_static_assets.py -o cache_dir=.tmp\pytest-cache --basetemp=.tmp\pytest-issue-192-final(142 passed, 2 warnings)C:\Users\zacza\Desktop\x\AstrBot\data\plugins\astrbot_plugin_self_learning: py_compile, json schema validation, import smoke, and WebUI/auth/config static subset (105 passed, 2 warnings). Two learning-chain tests are package-name bound toself_learning_EterUand do not collect from the real plugin package nameastrbot_plugin_self_learning.Summary by Sourcery
Tighten realtime learning and LLM hook behavior to avoid unintended high-frequency model calls, and add optional WebUI password authentication with supporting UI, routing, and configuration changes.
New Features:
Bug Fixes:
Enhancements:
Tests: