From deedf4fc8bdcc26357b27be0e0010d0a26b33a1e Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:27:25 -0700 Subject: [PATCH] Switch to PKSE OAuth impl (#1146) * Auth pkse * add additional fields * fix actions * fix lint * Purge broken auth + bump version --- .github/workflows/tauri-build.yml | 2 +- Cargo.lock | 4 +- theseus/Cargo.toml | 2 +- theseus/src/state/minecraft_auth.rs | 48 ++++++++++++++++---- theseus_gui/package.json | 2 +- theseus_gui/src-tauri/Cargo.toml | 2 +- theseus_gui/src-tauri/tauri.conf.json | 2 +- theseus_gui/src/components/ui/ErrorModal.vue | 5 +- 8 files changed, 50 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index f2d754442..26cbac226 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -23,7 +23,7 @@ jobs: uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - targets: aarch64-apple-darwin + targets: aarch64-apple-darwin, x86_64-apple-darwin - name: Rust setup if: "!startsWith(matrix.platform, 'macos')" diff --git a/Cargo.lock b/Cargo.lock index beb4423be..2d2d2fc6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5074,7 +5074,7 @@ dependencies = [ [[package]] name = "theseus" -version = "0.7.1" +version = "0.7.2" dependencies = [ "async-recursion", "async-tungstenite", @@ -5126,7 +5126,7 @@ dependencies = [ [[package]] name = "theseus_gui" -version = "0.7.1" +version = "0.7.2" dependencies = [ "chrono", "cocoa 0.25.0", diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml index 839a28052..f72b8fec0 100644 --- a/theseus/Cargo.toml +++ b/theseus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "theseus" -version = "0.7.1" +version = "0.7.2" authors = ["Jai A "] edition = "2018" diff --git a/theseus/src/state/minecraft_auth.rs b/theseus/src/state/minecraft_auth.rs index ce41b98f4..813a04eb8 100644 --- a/theseus/src/state/minecraft_auth.rs +++ b/theseus/src/state/minecraft_auth.rs @@ -4,7 +4,7 @@ use crate::State; use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD}; use base64::Engine; use byteorder::BigEndian; -use chrono::{DateTime, Duration, Utc}; +use chrono::{DateTime, Duration, NaiveDate, Utc}; use p256::ecdsa::signature::Signer; use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; use p256::pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; @@ -15,6 +15,7 @@ use reqwest::Response; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::json; +use sha2::Digest; use std::collections::HashMap; use std::future::Future; use uuid::Uuid; @@ -84,6 +85,7 @@ pub struct SaveDeviceToken { #[derive(Serialize, Deserialize, Debug)] pub struct MinecraftLoginFlow { + pub verifier: String, pub challenge: String, pub session_id: String, pub redirect_uri: String, @@ -157,6 +159,22 @@ impl MinecraftAuthStore { } let (key, token) = if let Some(ref token) = self.token { + // reset device token for legacy launcher versions with broken values + if self.users.is_empty() + && token.token.issue_instant + < DateTime::parse_from_rfc3339( + "2024-04-25T23:59:59.999999999Z", + ) + .unwrap() + { + return Ok(generate_key!( + self, + generate_key, + device_token, + SaveDeviceToken + )); + } + if token.token.not_after > Utc::now() { if let Ok(private_key) = SigningKey::from_pkcs8_pem(&token.private_key) @@ -192,11 +210,17 @@ impl MinecraftAuthStore { pub async fn login_begin(&mut self) -> crate::Result { let (key, token) = self.refresh_and_get_device_token().await?; - let challenge = generate_oauth_challenge(); + let verifier = generate_oauth_challenge(); + let mut hasher = sha2::Sha256::new(); + hasher.update(&verifier); + let result = hasher.finalize(); + let challenge = BASE64_URL_SAFE_NO_PAD.encode(&result); + let (session_id, redirect_uri) = sisu_authenticate(&token.token, &challenge, &key).await?; Ok(MinecraftLoginFlow { + verifier, challenge, session_id, redirect_uri: redirect_uri.msa_oauth_redirect, @@ -211,7 +235,7 @@ impl MinecraftAuthStore { ) -> crate::Result { let (key, token) = self.refresh_and_get_device_token().await?; - let oauth_token = oauth_token(code, &flow.challenge).await?; + let oauth_token = oauth_token(code, &flow.verifier).await?; let sisu_authorize = sisu_authorize( Some(&flow.session_id), &oauth_token.access_token, @@ -403,13 +427,14 @@ async fn sisu_authenticate( ], "Query": { "code_challenge": challenge, - "code_challenge_method": "plain", - "state": "", + "code_challenge_method": "S256", + "state": generate_oauth_challenge(), "prompt": "select_account" }, "RedirectUri": REDIRECT_URL, "Sandbox": "RETAIL", "TokenType": "code", + "TitleId": 1794566092, }), key, MinecraftAuthStep::SisuAuthenicate, @@ -439,12 +464,12 @@ struct OAuthToken { #[tracing::instrument] async fn oauth_token( code: &str, - challenge: &str, + verifier: &str, ) -> Result { let mut query = HashMap::new(); query.insert("client_id", "00000000402b5328"); query.insert("code", code); - query.insert("code_verifier", challenge); + query.insert("code_verifier", &*verifier); query.insert("grant_type", "authorization_code"); query.insert("redirect_uri", "https://login.live.com/oauth20_desktop.srf"); query.insert("scope", "service::user.auth.xboxlive.com::MBI_SSL"); @@ -555,6 +580,8 @@ async fn sisu_authorize( "Sandbox": "RETAIL", "SessionId": session_id, "SiteName": "user.auth.xboxlive.com", + "RelyingParty": "http://xboxlive.com", + "UseModernGamertag": "true" }), key, MinecraftAuthStep::SisuAuthorize, @@ -854,9 +881,12 @@ async fn send_signed_request( .post(url) .header("Content-Type", "application/json") .header("Accept", "application/json") - .header("x-xbl-contract-version", "1") .header("signature", &signature); + if url != "https://sisu.xboxlive.com/authorize" { + request = request.header("x-xbl-contract-version", "1"); + } + if let Some(auth) = authorization { request = request.header("Authorization", auth); } @@ -885,6 +915,6 @@ async fn send_signed_request( fn generate_oauth_challenge() -> String { let mut rng = rand::thread_rng(); - let bytes: Vec = (0..32).map(|_| rng.gen()).collect(); + let bytes: Vec = (0..64).map(|_| rng.gen()).collect(); bytes.iter().map(|byte| format!("{:02x}", byte)).collect() } diff --git a/theseus_gui/package.json b/theseus_gui/package.json index e30a42134..913c3aed8 100644 --- a/theseus_gui/package.json +++ b/theseus_gui/package.json @@ -1,7 +1,7 @@ { "name": "theseus_gui", "private": true, - "version": "0.7.1", + "version": "0.7.2", "type": "module", "scripts": { "dev": "vite", diff --git a/theseus_gui/src-tauri/Cargo.toml b/theseus_gui/src-tauri/Cargo.toml index f5480a728..21ac3938d 100644 --- a/theseus_gui/src-tauri/Cargo.toml +++ b/theseus_gui/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "theseus_gui" -version = "0.7.1" +version = "0.7.2" description = "A Tauri App" authors = ["you"] license = "" diff --git a/theseus_gui/src-tauri/tauri.conf.json b/theseus_gui/src-tauri/tauri.conf.json index 1c65d2a3b..1948db362 100644 --- a/theseus_gui/src-tauri/tauri.conf.json +++ b/theseus_gui/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "Modrinth App", - "version": "0.7.1" + "version": "0.7.2" }, "tauri": { "allowlist": { diff --git a/theseus_gui/src/components/ui/ErrorModal.vue b/theseus_gui/src/components/ui/ErrorModal.vue index bc32e8da2..ffe170b73 100644 --- a/theseus_gui/src/components/ui/ErrorModal.vue +++ b/theseus_gui/src/components/ui/ErrorModal.vue @@ -23,7 +23,10 @@ defineExpose({ supportLink.value = 'https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues' - if (errorVal.message.includes('existing connection was forcibly closed')) { + if ( + errorVal.message.includes('existing connection was forcibly closed') || + errorVal.message.includes('error sending request for url') + ) { metadata.value.network = true } if (errorVal.message.includes('because the target machine actively refused it')) {