From 1e9d74fa4a732d0d56a077dbba61914e19ed4ab6 Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:46:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=99=BB=E5=BD=95=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 登录页支持 release、dev 和 custom 服务器 - 持久化服务器选择并统一登录与平台会话请求地址 - 校验自定义服务器 origin 与 HTTPS 安全边界 - 补充前端、AppSurface 和 Rust 平台会话测试 --- .../src-tauri/src/platform_session.rs | 17 ++- .../src/app/AuthenticatedClient.tsx | 100 +++++++++++++ .../src/services/clientHttp.ts | 140 ++++++++++++++++-- .../src/services/platformSession.ts | 19 +-- apps/ai-game-creator-shell/src/styles.css | 28 +++- .../tests/appSurface/auth.suite.ts | 35 +++++ .../tests/clientHttp.test.ts | 87 ++++++++++- .../shared-memory/decision-log.md | 5 + 8 files changed, 396 insertions(+), 35 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/platform_session.rs b/apps/ai-game-creator-shell/src-tauri/src/platform_session.rs index c76a3deb3..359ad50f5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/platform_session.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/platform_session.rs @@ -126,13 +126,12 @@ fn normalize_platform_api_base_url(value: &str) -> Result { let host = parsed .host_str() .ok_or_else(|| "陶泥儿服务地址缺少 host".to_string())?; - let production = matches!(host, "www.genarrative.world" | "dev.genarrative.world"); let loopback = host == "localhost" || host == "127.0.0.1" || host .parse::() .is_ok_and(|ip| ip.is_loopback()); - if !production && !(cfg!(debug_assertions) && loopback && parsed.scheme() == "http") { + if parsed.scheme() == "http" && !(cfg!(debug_assertions) && loopback) { return Err("陶泥儿服务地址不在受信任白名单内".to_string()); } Ok(value.to_string()) @@ -342,6 +341,20 @@ mod tests { ); } + #[test] + fn custom_server_selection_accepts_https_origins_and_rejects_plain_http() { + assert_eq!( + normalize_platform_api_base_url("https://staging.example.com/"), + Ok("https://staging.example.com".to_string()) + ); + assert_eq!( + normalize_platform_api_base_url("http://127.0.0.1:8080/"), + Ok("http://127.0.0.1:8080".to_string()) + ); + assert!(normalize_platform_api_base_url("http://staging.example.com").is_err()); + assert!(normalize_platform_api_base_url("https://staging.example.com/api").is_err()); + } + #[test] fn frozen_platform_session_rejects_logout_account_switch_and_token_rotation() { let expected = PlatformSessionSnapshot { diff --git a/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx b/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx index 80cfec5b6..d7efba85a 100644 --- a/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx +++ b/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx @@ -20,6 +20,15 @@ import { normalizeAuthPhoneInput, sendClientPhoneLoginCode, } from '../services/clientAuth'; +import { + AGC_DEVELOPMENT_API_BASE_URL, + AGC_RELEASE_API_BASE_URL, + type ClientServerPreset, + type ClientServerSelection, + getClientServerSelection, + normalizeClientServerBaseUrl, + setClientServerSelection, +} from '../services/clientHttp'; import { beginPlatformSessionTransition, clearCommittedPlatformSession, @@ -95,6 +104,46 @@ export function AuthenticatedClient({ const [loginBusy, setLoginBusy] = useState(false); const [codeBusy, setCodeBusy] = useState(false); const [codeCooldownSeconds, setCodeCooldownSeconds] = useState(0); + const initialServerSelection = getClientServerSelection(); + const [serverSelection, setServerSelection] = useState( + initialServerSelection, + ); + const [customServerUrl, setCustomServerUrl] = useState( + initialServerSelection.customBaseUrl, + ); + const selectedServerAddress = + serverSelection.preset === 'release' + ? AGC_RELEASE_API_BASE_URL + : serverSelection.preset === 'dev' + ? AGC_DEVELOPMENT_API_BASE_URL + : customServerUrl || '请输入自定义服务器地址'; + + function applyServerSelection() { + try { + const next = setClientServerSelection({ + preset: serverSelection.preset, + customBaseUrl: customServerUrl, + }); + setServerSelection(next); + return true; + } catch (error) { + setLoginStatus(error instanceof Error ? error.message : String(error)); + return false; + } + } + + function handleServerPresetChange(preset: ClientServerPreset) { + if (preset === 'custom') { + setServerSelection((current) => ({ ...current, preset })); + return; + } + const next = setClientServerSelection({ + preset, + customBaseUrl: customServerUrl, + }); + setServerSelection(next); + setLoginStatus(`已选择 ${preset} 服务器`); + } useEffect(() => { let disposed = false; @@ -229,6 +278,9 @@ export function AuthenticatedClient({ if (codeBusy || codeCooldownSeconds > 0) { return; } + if (!applyServerSelection()) { + return; + } const normalizedPhone = normalizeAuthPhoneInput(phone); if (!normalizedPhone) { setLoginStatus('请输入手机号'); @@ -265,6 +317,9 @@ export function AuthenticatedClient({ setLoginStatus('请输入密码'); return; } + if (!applyServerSelection()) { + return; + } setLoginBusy(true); setLoginStatus('正在登录'); const loginGeneration = beginPlatformSessionTransition(); @@ -324,6 +379,51 @@ export function AuthenticatedClient({

登录陶泥儿 GameAgent

登录后进入首页和本地项目工作区

+ + {serverSelection.preset === 'custom' ? ( + + ) : null}