fix: secure quick start webui setup

Add Quick Start endpoint choices for subscription plan providers, require explicit WebSocket confirmation, and require a WebUI password when enabling the WebSocket channel. Update docs to route Quick Start users through the WebUI instead of agent -m.
This commit is contained in:
chengyongru
2026-06-22 13:04:05 +08:00
committed by Xubin Ren
parent e5294002ed
commit fca1f2ad02
9 changed files with 361 additions and 35 deletions
+180
View File
@@ -942,6 +942,13 @@ class TestMainMenuUpdate:
pause_messages: list[str] = []
class FakePrompt:
def __init__(self, response):
self.response = response
def ask(self):
return self.response
monkeypatch.setattr(onboard_wizard.console, "clear", lambda: None)
monkeypatch.setattr(onboard_wizard, "_show_section_header", lambda *a, **kw: None)
monkeypatch.setattr(onboard_wizard, "_select_with_back", lambda *a, **kw: "DeepSeek")
@@ -951,6 +958,14 @@ class TestMainMenuUpdate:
"_input_model_with_autocomplete",
lambda *a, **kw: "deepseek-v4-flash",
)
monkeypatch.setattr(
onboard_wizard,
"questionary",
SimpleNamespace(
confirm=lambda *a, **kw: FakePrompt(True),
password=lambda *a, **kw: FakePrompt("webui-secret"),
),
)
monkeypatch.setattr(onboard_wizard, "_configure_pydantic_model", fail_websocket_config)
monkeypatch.setattr(onboard_wizard, "_print_summary_panel", lambda *a, **kw: None)
monkeypatch.setattr(onboard_wizard, "_pause", lambda message="": pause_messages.append(message))
@@ -966,6 +981,7 @@ class TestMainMenuUpdate:
websocket = getattr(config.channels, "websocket")
assert websocket["enabled"] is True
assert websocket["websocketRequiresToken"] is True
assert websocket["tokenIssueSecret"] == "webui-secret"
def test_quick_start_provider_choice_asks_for_model_id(self, monkeypatch):
"""Known providers should ask users for the model instead of fetching one."""
@@ -1034,6 +1050,90 @@ class TestMainMenuUpdate:
assert config.model_presets["primary"].provider == "openai"
assert config.model_presets["primary"].model == "gpt-4o-mini"
def test_quick_start_zhipu_coding_plan_uses_coding_base_url(self, monkeypatch):
"""Zhipu Coding Plan should not use the standard Zhipu base URL."""
config = Config()
choices = iter(["Zhipu AI", "Coding Plan"])
monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None)
monkeypatch.setattr(onboard_wizard, "_select_with_back", lambda *a, **kw: next(choices))
monkeypatch.setattr(onboard_wizard, "_input_text", lambda *a, **kw: "zhipu-key")
monkeypatch.setattr(
onboard_wizard,
"_input_model_with_autocomplete",
lambda *a, **kw: "glm-4.6",
)
assert onboard_wizard._configure_quick_start_provider(config) is True
assert config.providers.zhipu.api_key == "zhipu-key"
assert config.providers.zhipu.api_base == "https://open.bigmodel.cn/api/coding/paas/v4"
assert config.model_presets["primary"].provider == "zhipu"
assert config.model_presets["primary"].model == "glm-4.6"
def test_quick_start_minimax_mainland_token_plan_uses_mainland_base_url(self, monkeypatch):
"""MiniMax mainland token plan should not use the global MiniMax base URL."""
config = Config()
choices = iter(["MiniMax", "Mainland China Token Plan"])
monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None)
monkeypatch.setattr(onboard_wizard, "_select_with_back", lambda *a, **kw: next(choices))
monkeypatch.setattr(onboard_wizard, "_input_text", lambda *a, **kw: "minimax-key")
monkeypatch.setattr(
onboard_wizard,
"_input_model_with_autocomplete",
lambda *a, **kw: "MiniMax-M2",
)
assert onboard_wizard._configure_quick_start_provider(config) is True
assert config.providers.minimax.api_key == "minimax-key"
assert config.providers.minimax.api_base == "https://api.minimaxi.com/v1"
assert config.model_presets["primary"].provider == "minimax"
assert config.model_presets["primary"].model == "MiniMax-M2"
def test_quick_start_stepfun_step_plan_uses_plan_base_url(self, monkeypatch):
"""StepFun Step Plan should not use the standard StepFun base URL."""
config = Config()
choices = iter(["Step Fun", "Step Plan"])
monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None)
monkeypatch.setattr(onboard_wizard, "_select_with_back", lambda *a, **kw: next(choices))
monkeypatch.setattr(onboard_wizard, "_input_text", lambda *a, **kw: "stepfun-key")
monkeypatch.setattr(
onboard_wizard,
"_input_model_with_autocomplete",
lambda *a, **kw: "step-3.5-flash",
)
assert onboard_wizard._configure_quick_start_provider(config) is True
assert config.providers.stepfun.api_key == "stepfun-key"
assert config.providers.stepfun.api_base == "https://api.stepfun.ai/step_plan/v1"
assert config.model_presets["primary"].provider == "stepfun"
assert config.model_presets["primary"].model == "step-3.5-flash"
def test_quick_start_xiaomi_mimo_token_plan_uses_token_plan_base_url(self, monkeypatch):
"""Xiaomi MiMo Token Plan should not use the standard MiMo base URL."""
config = Config()
choices = iter(["Xiaomi MIMO", "Token Plan"])
monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None)
monkeypatch.setattr(onboard_wizard, "_select_with_back", lambda *a, **kw: next(choices))
monkeypatch.setattr(onboard_wizard, "_input_text", lambda *a, **kw: "mimo-key")
monkeypatch.setattr(
onboard_wizard,
"_input_model_with_autocomplete",
lambda *a, **kw: "mimo-v2.5-pro",
)
assert onboard_wizard._configure_quick_start_provider(config) is True
assert config.providers.xiaomi_mimo.api_key == "mimo-key"
assert config.providers.xiaomi_mimo.api_base == "https://token-plan-sgp.xiaomimimo.com/v1"
assert config.model_presets["primary"].provider == "xiaomi_mimo"
assert config.model_presets["primary"].model == "mimo-v2.5-pro"
def test_quick_start_custom_base_url_asks_for_model_id(self, monkeypatch):
"""Custom providers should ask for base URL and model ID."""
config = Config()
@@ -1080,6 +1180,85 @@ class TestMainMenuUpdate:
assert config.model_presets["primary"].provider == "azure_openai"
assert config.model_presets["primary"].model == "deployment-name"
def test_quick_start_websocket_step_explains_channel_enablement(self, monkeypatch):
"""Quick Start should confirm and protect WebSocket for WebUI."""
config = Config()
messages: list[str] = []
class FakePrompt:
def __init__(self, response):
self.response = response
def ask(self):
return self.response
monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None)
monkeypatch.setattr(onboard_wizard.console, "print", lambda message="", *a, **kw: messages.append(str(message)))
monkeypatch.setattr(
onboard_wizard,
"questionary",
SimpleNamespace(
confirm=lambda *a, **kw: FakePrompt(True),
password=lambda *a, **kw: FakePrompt("webui-secret"),
),
)
assert onboard_wizard._enable_quick_start_websocket_defaults(config) is True
assert any("WebSocket channel" in message for message in messages)
assert any("http://127.0.0.1:8765" in message for message in messages)
websocket = getattr(config.channels, "websocket")
assert websocket["enabled"] is True
assert websocket["websocketRequiresToken"] is True
assert websocket["tokenIssueSecret"] == "webui-secret"
def test_quick_start_websocket_step_can_be_declined(self, monkeypatch):
"""Declining WebSocket should stop Quick Start before changing channel config."""
config = Config()
class FakePrompt:
def __init__(self, response):
self.response = response
def ask(self):
return self.response
monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None)
monkeypatch.setattr(onboard_wizard.console, "print", lambda *a, **kw: None)
monkeypatch.setattr(
onboard_wizard,
"questionary",
SimpleNamespace(confirm=lambda *a, **kw: FakePrompt(False)),
)
assert onboard_wizard._enable_quick_start_websocket_defaults(config) is False
assert getattr(config.channels, "websocket", None) is None
def test_quick_start_websocket_requires_password(self, monkeypatch):
"""Accepting WebSocket with an empty password should not enable the channel."""
config = Config()
class FakePrompt:
def __init__(self, response):
self.response = response
def ask(self):
return self.response
monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None)
monkeypatch.setattr(onboard_wizard.console, "print", lambda *a, **kw: None)
monkeypatch.setattr(
onboard_wizard,
"questionary",
SimpleNamespace(
confirm=lambda *a, **kw: FakePrompt(True),
password=lambda *a, **kw: FakePrompt(""),
),
)
assert onboard_wizard._enable_quick_start_websocket_defaults(config) is False
assert getattr(config.channels, "websocket", None) is None
def test_quick_start_requires_api_key_before_setting_defaults(self, monkeypatch):
"""Quick Start should not create a ready-looking config without an API key."""
config = Config()
@@ -1135,6 +1314,7 @@ class TestMainMenuUpdate:
assert rows["Status"] == "DeepSeek API key missing"
assert "API key" in rows["Next"]
assert "nanobot gateway" in rows["Next"]
assert "agent -m" not in rows["Next"]
assert labels.index("Next") < labels.index("Open")
assert "Model" not in rows
assert "Entry point" not in rows