From 4f2abfdaa35d43a8d3ec868a1b27f6f8d0ebf547 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Tue, 13 Jan 2026 19:48:27 +0800 Subject: feat: enhance Microsoft login flow with status updates and polling management --- src-tauri/src/core/auth.rs | 28 +++++++++++++++++++++++----- ui/src/App.svelte | 19 +++++++++++++------ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/core/auth.rs b/src-tauri/src/core/auth.rs index 99481d5..e26f850 100644 --- a/src-tauri/src/core/auth.rs +++ b/src-tauri/src/core/auth.rs @@ -74,9 +74,9 @@ pub fn generate_offline_uuid(username: &str) -> String { Uuid::new_v3(&namespace, username.as_bytes()).to_string() } -// Constants -const CLIENT_ID: &str = "fe165602-5410-4441-92f7-326e10a7cb82"; -const SCOPE: &str = "XboxLive.Signin offline_access openid profile email"; +// const CLIENT_ID: &str = "fe165602-5410-4441-92f7-326e10a7cb82"; +const CLIENT_ID: &str = "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb"; // ATLauncher's Client ID +const SCOPE: &str = "XboxLive.SignIn XboxLive.offline_access"; #[derive(Debug, Serialize, Deserialize)] pub struct DeviceCodeResponse { @@ -184,19 +184,25 @@ pub async fn exchange_code_for_token(device_code: &str) -> Result(&text) { + println!("[Auth] Token received successfully!"); return Ok(token_resp); } // Try parse error if let Ok(err_resp) = serde_json::from_str::(&text) { + if err_resp.error != "authorization_pending" { + println!("[Auth] Polling error: {}", err_resp.error); + } return Err(err_resp.error); // "authorization_pending", "expired_token", "access_denied" } + println!("[Auth] Unknown response body: {}", text); Err(format!("Unknown response: {}", text)) } // 3. Authenticate with Xbox Live pub async fn method_xbox_live(ms_access_token: &str) -> Result<(String, String), String> { + println!("[Auth] Starting Xbox Live auth..."); let client = get_client(); let url = "https://user.auth.xboxlive.com/user/authenticate"; @@ -222,10 +228,12 @@ pub async fn method_xbox_live(ms_access_token: &str) -> Result<(String, String), if !resp.status().is_success() { let status = resp.status(); let text = resp.text().await.unwrap_or_default(); + println!("[Auth] Xbox Live auth failed: {} - {}", status, text); return Err(format!("Xbox Live auth failed: {} - {}", status, text)); } let xbl_resp: XboxLiveResponse = resp.json().await.map_err(|e| e.to_string())?; + println!("[Auth] Xbox Live auth success!"); // Extract UHS (User Hash) let uhs = xbl_resp @@ -242,6 +250,7 @@ pub async fn method_xbox_live(ms_access_token: &str) -> Result<(String, String), // 4. Authenticate with XSTS pub async fn method_xsts(xbl_token: &str) -> Result { + println!("[Auth] Starting XSTS auth..."); let client = get_client(); let url = "https://xsts.auth.xboxlive.com/xsts/authorize"; @@ -265,25 +274,32 @@ pub async fn method_xsts(xbl_token: &str) -> Result { // Should handle specific errors like "Account not verified", "Age restriction" let status = resp.status(); let text = resp.text().await.unwrap_or_default(); + println!("[Auth] XSTS auth failed: {} - {}", status, text); return Err(format!("XSTS auth failed: {} - {}", status, text)); } let xsts_resp: XboxLiveResponse = resp.json().await.map_err(|e| e.to_string())?; + println!("[Auth] XSTS auth success!"); Ok(xsts_resp.token) } // 5. Authenticate with Minecraft +// Using the newer /launcher/login endpoint which is what modern launchers use pub async fn login_minecraft(xsts_token: &str, uhs: &str) -> Result { + println!("[Auth] Starting Minecraft auth..."); let client = get_client(); - let url = "https://api.minecraftservices.com/authentication/login_with_xbox"; + let url = "https://api.minecraftservices.com/launcher/login"; let payload = serde_json::json!({ - "identityToken": format!("XBL3.0 x={};{}", uhs, xsts_token) + "xtoken": format!("XBL3.0 x={};{}", uhs, xsts_token), + "platform": "PC_LAUNCHER" }); let resp = client .post(url) .json(&payload) + .header("Content-Type", "application/json") + .header("Accept", "application/json") .send() .await .map_err(|e| e.to_string())?; @@ -291,6 +307,7 @@ pub async fn login_minecraft(xsts_token: &str, uhs: &str) -> Result Result { checkAccount(); @@ -126,6 +128,7 @@ } function closeLoginModal() { + stopPolling(); isLoginModalOpen = false; } @@ -154,6 +157,7 @@ async function startMicrosoftLogin() { loginMode = "microsoft"; msLoginLoading = true; + msLoginStatus = "Waiting for authorization..."; stopPolling(); // Ensure no duplicates try { @@ -188,6 +192,9 @@ } async function checkLoginStatus(deviceCode: string) { + if (isPollingRequestActive) return; + isPollingRequestActive = true; + console.log("Polling Microsoft API..."); try { // This will fail with "authorization_pending" until user logs in @@ -204,9 +211,12 @@ const errStr = e.toString(); if (errStr.includes("authorization_pending")) { console.log("Status: Waiting for user to authorize..."); + // Keep checking } else { // Real error console.error("Polling Error:", errStr); + msLoginStatus = "Error: " + errStr; + // Optional: Stop polling on fatal errors? // expired_token should stop it. if ( @@ -218,6 +228,8 @@ loginMode = "select"; } } + } finally { + isPollingRequestActive = false; } } @@ -226,11 +238,6 @@ if (deviceCodeData) checkLoginStatus(deviceCodeData.device_code); } - function closeLoginModal() { - stopPolling(); - isLoginModalOpen = false; - } - function openLink(url: string) { open(url); } @@ -669,7 +676,7 @@
- Waiting for authorization... + {msLoginStatus}

This window will update automatically.

-- cgit v1.2.3-70-g09d2