From b7cadfe704453ff09fd9b22315bac600b95052ec Mon Sep 17 00:00:00 2001 From: FuShang114 <18500350168@163.com> Date: Wed, 24 Jun 2026 22:11:49 +0800 Subject: [PATCH] fix: allow plugin page asset token fallback (#8970) Co-authored-by: beidou-lab-macmini --- astrbot/dashboard/server.py | 85 ++++++++++++++++++++++++++----------- tests/test_dashboard.py | 6 +++ 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index b488559ab..6776927ba 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -279,37 +279,74 @@ class AstrBotDashboard: ): return None is_plugin_page_path = PluginPageAuth.is_protected_path(path) - token = self._extract_dashboard_jwt(current_request) - if not token and is_plugin_page_path: - token = PluginPageAuth.extract_asset_token(current_request.query_params) - if not token: + dashboard_token = self._extract_dashboard_jwt(current_request) + asset_token = ( + PluginPageAuth.extract_asset_token(current_request.query_params) + if is_plugin_page_path + else None + ) + token_candidates = [] + if dashboard_token: + token_candidates.append(dashboard_token) + if asset_token and asset_token != dashboard_token: + token_candidates.append(asset_token) + if not token_candidates: r = JSONResponse(error("未授权")) r.status_code = 401 return r + + token_errors: list[str] = [] + for token in token_candidates: + payload, token_error = self._validate_dashboard_token(token, path) + if payload is not None: + current_request.state.dashboard_g.username = cast( + str, payload["username"] + ) + return None + token_errors.append(token_error) + + error_message = ( + "Token 过期" + if token_errors and all(item == "Token 过期" for item in token_errors) + else "Token 无效" + ) + r = JSONResponse(error(error_message)) + r.status_code = 401 + return r + + def _validate_dashboard_token( + self, + token: str, + path: str, + ) -> tuple[dict[str, Any] | None, str]: + """Validate a dashboard JWT or scoped plugin page asset token. + + Args: + token: JWT value from the Authorization header, cookie, or query string. + path: Current request path used for plugin page asset token scope checks. + + Returns: + A tuple of the decoded payload and an error message. The payload is + present only when the token is valid for the current request path. + """ try: payload = jwt.decode(token, self._jwt_secret, algorithms=["HS256"]) - if PluginPageAuth.is_asset_token( - payload - ) and not PluginPageAuth.is_scope_valid( - payload, - path, - ): - r = JSONResponse(error("Token 无效")) - r.status_code = 401 - return r - - username = payload.get("username") - if not isinstance(username, str) or not username.strip(): - raise jwt.InvalidTokenError("missing username in token payload") - current_request.state.dashboard_g.username = username except jwt.ExpiredSignatureError: - r = JSONResponse(error("Token 过期")) - r.status_code = 401 - return r + return None, "Token 过期" except jwt.InvalidTokenError: - r = JSONResponse(error("Token 无效")) - r.status_code = 401 - return r + return None, "Token 无效" + + if PluginPageAuth.is_asset_token(payload) and not PluginPageAuth.is_scope_valid( + payload, + path, + ): + return None, "Token 无效" + + username = payload.get("username") + if not isinstance(username, str) or not username.strip(): + return None, "Token 无效" + + return payload, "" async def _apply_auth_rate_limit( self, diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index b06ff2ff6..3376b25d6 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -1773,6 +1773,12 @@ async def test_plugin_page_content_issues_scoped_asset_token( css_response = await anonymous_client.get(css_url.group(1)) assert css_response.status_code == 200 + stale_cookie_response = await anonymous_client.get( + app_js_url.group(1), + headers={"Cookie": f"{DASHBOARD_JWT_COOKIE_NAME}=stale.dashboard.token"}, + ) + assert stale_cookie_response.status_code == 200 + out_of_scope_response = await anonymous_client.get( f"/api/plugin/get?asset_token={asset_token}" )