summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorHsiangNianian <i@jyunko.cn>2026-01-13 19:48:27 +0800
committerHsiangNianian <i@jyunko.cn>2026-01-13 19:48:27 +0800
commit4f2abfdaa35d43a8d3ec868a1b27f6f8d0ebf547 (patch)
treef69b161804ec977943b921c775ca8c0cbb6de4c9
parent203c24d2da82841cae0b5c29708d07ebce1efb85 (diff)
downloadDropOut-4f2abfdaa35d43a8d3ec868a1b27f6f8d0ebf547.tar.gz
DropOut-4f2abfdaa35d43a8d3ec868a1b27f6f8d0ebf547.zip
feat: enhance Microsoft login flow with status updates and polling management
-rw-r--r--src-tauri/src/core/auth.rs28
-rw-r--r--ui/src/App.svelte19
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<TokenResponse,
// Try parse success
if let Ok(token_resp) = serde_json::from_str::<TokenResponse>(&text) {
+ println!("[Auth] Token received successfully!");
return Ok(token_resp);
}
// Try parse error
if let Ok(err_resp) = serde_json::from_str::<TokenError>(&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<String, String> {
+ 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<String, String> {
// 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<String, String> {
+ 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<String, Stri
if !resp.status().is_success() {
let status = resp.status();
let text = resp.text().await.unwrap_or_else(|_| "No body".to_string());
+ println!("[Auth] Minecraft auth failed: {} - {}", status, text);
return Err(format!(
"Minecraft auth failed: {} - Body: {}",
status, text
@@ -298,6 +315,7 @@ pub async fn login_minecraft(xsts_token: &str, uhs: &str) -> Result<String, Stri
}
let mc_resp: MinecraftAuthResponse = resp.json().await.map_err(|e| e.to_string())?;
+ println!("[Auth] Minecraft auth success!");
Ok(mc_resp.access_token)
}
diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 914a5ea..beb3403 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -64,6 +64,8 @@
let offlineUsername = "";
let deviceCodeData: DeviceCodeResponse | null = null;
let msLoginLoading = false;
+ let msLoginStatus = "Waiting for authorization...";
+ let isPollingRequestActive = false;
onMount(async () => {
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 @@
<div class="pt-6 space-y-3">
<div class="flex flex-col items-center gap-3">
<div class="animate-spin rounded-full h-6 w-6 border-2 border-zinc-600 border-t-indigo-500"></div>
- <span class="text-sm text-zinc-400 font-medium">Waiting for authorization...</span>
+ <span class="text-sm text-zinc-400 font-medium break-all text-center">{msLoginStatus}</span>
</div>
<p class="text-xs text-zinc-600">This window will update automatically.</p>
</div>