From c35ca695e0cf068484288088e4914644812df6c2 Mon Sep 17 00:00:00 2001 From: YukiRa1n <167516635+YukiRa1n@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:34:08 +0800 Subject: [PATCH 1/3] fix(dashboard): add SSL config validation to prevent saving invalid HTTPS settings When users enable WebUI HTTPS without configuring certificate and key file paths, the server would fail to start on the next launch. This fix adds: - Backend validation in save_config() to reject invalid SSL configurations - Frontend validation in ConfigPage.vue to provide immediate user feedback - i18n translations for the validation error message in both zh-CN and en-US --- astrbot/dashboard/routes/config.py | 39 +++++++++++++++++++ .../i18n/locales/en-US/features/config.json | 3 ++ .../i18n/locales/zh-CN/features/config.json | 3 ++ dashboard/src/views/ConfigPage.vue | 24 ++++++++++++ 4 files changed, 69 insertions(+) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 6d60fb6de0..59e1c9f8a5 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -206,6 +206,40 @@ def validate(data: dict, metadata: dict = schema, path="") -> None: return errors, data +def validate_ssl_config(post_config: dict) -> list[str]: + """验证 SSL 配置的有效性。 + + 当 dashboard.ssl.enable 为 true 时,必须配置 cert_file 和 key_file。 + + Returns: + 错误信息列表,为空表示验证通过。 + """ + errors = [] + dashboard_config = post_config.get("dashboard", {}) + if not isinstance(dashboard_config, dict): + return errors + + ssl_config = dashboard_config.get("ssl", {}) + if not isinstance(ssl_config, dict): + return errors + + ssl_enable = ssl_config.get("enable", False) + if not ssl_enable: + return errors + + cert_file = ssl_config.get("cert_file", "") + key_file = ssl_config.get("key_file", "") + + if not cert_file or not cert_file.strip(): + errors.append( + "启用 HTTPS 时必须配置 SSL 证书文件路径 (dashboard.ssl.cert_file)" + ) + if not key_file or not key_file.strip(): + errors.append("启用 HTTPS 时必须配置 SSL 私钥文件路径 (dashboard.ssl.key_file)") + + return errors + + def save_config( post_config: dict, config: AstrBotConfig, is_core: bool = False ) -> None: @@ -230,6 +264,11 @@ def save_config( if errors: raise ValueError(f"格式校验未通过: {errors}") + # 验证 SSL 配置 + ssl_errors = validate_ssl_config(post_config) + if ssl_errors: + raise ValueError(f"SSL 配置校验未通过: {ssl_errors}") + config.save_config(post_config) diff --git a/dashboard/src/i18n/locales/en-US/features/config.json b/dashboard/src/i18n/locales/en-US/features/config.json index 4b726ae3c8..5bf7b03ecb 100644 --- a/dashboard/src/i18n/locales/en-US/features/config.json +++ b/dashboard/src/i18n/locales/en-US/features/config.json @@ -125,5 +125,8 @@ "confirm": "confirm", "cancel": "cancel" } + }, + "sslValidation": { + "required": "When WebUI HTTPS is enabled, SSL certificate file path and private key file path must be configured" } } diff --git a/dashboard/src/i18n/locales/zh-CN/features/config.json b/dashboard/src/i18n/locales/zh-CN/features/config.json index e7cd90408b..7bda0ae5bd 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config.json @@ -125,5 +125,8 @@ "confirm": "确定", "cancel": "取消" } + }, + "sslValidation": { + "required": "启用 WebUI HTTPS 时,必须配置 SSL 证书文件路径和私钥文件路径" } } diff --git a/dashboard/src/views/ConfigPage.vue b/dashboard/src/views/ConfigPage.vue index 7c50fbb583..14237d42dc 100644 --- a/dashboard/src/views/ConfigPage.vue +++ b/dashboard/src/views/ConfigPage.vue @@ -500,9 +500,33 @@ export default { this.save_message_success = "error"; }); }, + validateSslConfig() { + const dashboard = this.config_data?.dashboard; + if (!dashboard || !dashboard.ssl) return true; + + const ssl = dashboard.ssl; + if (!ssl.enable) return true; + + const certFile = (ssl.cert_file || '').trim(); + const keyFile = (ssl.key_file || '').trim(); + + if (!certFile || !keyFile) { + return this.tm('sslValidation.required'); + } + return true; + }, updateConfig() { if (!this.fetched) return; + // 前端验证 SSL 配置 + const sslValidation = this.validateSslConfig(); + if (sslValidation !== true) { + this.save_message = sslValidation; + this.save_message_snack = true; + this.save_message_success = "error"; + return Promise.resolve({ success: false }); + } + const postData = { config: JSON.parse(JSON.stringify(this.config_data)), }; From d6d48c8c67431f9af91cf727b46b8e3eb28f0f7d Mon Sep 17 00:00:00 2001 From: YukiRa1n <167516635+YukiRa1n@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:43:55 +0800 Subject: [PATCH 2/3] fix(dashboard): improve SSL validation error message format Use semicolon-separated string instead of Python list format for better readability in error messages. --- astrbot/dashboard/routes/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 59e1c9f8a5..71e3520bba 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -267,7 +267,7 @@ def save_config( # 验证 SSL 配置 ssl_errors = validate_ssl_config(post_config) if ssl_errors: - raise ValueError(f"SSL 配置校验未通过: {ssl_errors}") + raise ValueError(f"SSL 配置校验未通过: {'; '.join(ssl_errors)}") config.save_config(post_config) From 8ad88588df9554f3b63eb8840f778ae982752b9d Mon Sep 17 00:00:00 2001 From: YukiRa1n <167516635+YukiRa1n@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:01:59 +0800 Subject: [PATCH 3/3] feat(dashboard): add file existence check for SSL certificate and key files --- astrbot/dashboard/routes/config.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 71e3520bba..21001f1070 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -209,11 +209,14 @@ def validate(data: dict, metadata: dict = schema, path="") -> None: def validate_ssl_config(post_config: dict) -> list[str]: """验证 SSL 配置的有效性。 - 当 dashboard.ssl.enable 为 true 时,必须配置 cert_file 和 key_file。 + 当 dashboard.ssl.enable 为 true 时,必须配置 cert_file 和 key_file, + 并且文件必须存在。 Returns: 错误信息列表,为空表示验证通过。 """ + from astrbot.core.utils.astrbot_path import get_astrbot_data_path + errors = [] dashboard_config = post_config.get("dashboard", {}) if not isinstance(dashboard_config, dict): @@ -234,8 +237,23 @@ def validate_ssl_config(post_config: dict) -> list[str]: errors.append( "启用 HTTPS 时必须配置 SSL 证书文件路径 (dashboard.ssl.cert_file)" ) + elif not os.path.isabs(cert_file): + # 相对路径,基于 data 目录解析 + cert_path = os.path.join(get_astrbot_data_path(), cert_file) + if not os.path.isfile(cert_path): + errors.append(f"SSL 证书文件不存在: {cert_file}") + elif not os.path.isfile(cert_file): + errors.append(f"SSL 证书文件不存在: {cert_file}") + if not key_file or not key_file.strip(): errors.append("启用 HTTPS 时必须配置 SSL 私钥文件路径 (dashboard.ssl.key_file)") + elif not os.path.isabs(key_file): + # 相对路径,基于 data 目录解析 + key_path = os.path.join(get_astrbot_data_path(), key_file) + if not os.path.isfile(key_path): + errors.append(f"SSL 私钥文件不存在: {key_file}") + elif not os.path.isfile(key_file): + errors.append(f"SSL 私钥文件不存在: {key_file}") return errors