Switch to PKSE OAuth impl (#1146)
* Auth pkse * add additional fields * fix actions * fix lint * Purge broken auth + bump version
This commit is contained in:
parent
e9e99956ad
commit
deedf4fc8b
2
.github/workflows/tauri-build.yml
vendored
2
.github/workflows/tauri-build.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
targets: aarch64-apple-darwin
|
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
||||||
|
|
||||||
- name: Rust setup
|
- name: Rust setup
|
||||||
if: "!startsWith(matrix.platform, 'macos')"
|
if: "!startsWith(matrix.platform, 'macos')"
|
||||||
|
|||||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -5074,7 +5074,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@ -5126,7 +5126,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus_gui"
|
name = "theseus_gui"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"cocoa 0.25.0",
|
"cocoa 0.25.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use crate::State;
|
|||||||
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
|
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use byteorder::BigEndian;
|
use byteorder::BigEndian;
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, NaiveDate, Utc};
|
||||||
use p256::ecdsa::signature::Signer;
|
use p256::ecdsa::signature::Signer;
|
||||||
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
|
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
|
||||||
use p256::pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding};
|
use p256::pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding};
|
||||||
@ -15,6 +15,7 @@ use reqwest::Response;
|
|||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use sha2::Digest;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -84,6 +85,7 @@ pub struct SaveDeviceToken {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct MinecraftLoginFlow {
|
pub struct MinecraftLoginFlow {
|
||||||
|
pub verifier: String,
|
||||||
pub challenge: String,
|
pub challenge: String,
|
||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
pub redirect_uri: String,
|
pub redirect_uri: String,
|
||||||
@ -157,6 +159,22 @@ impl MinecraftAuthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (key, token) = if let Some(ref token) = self.token {
|
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 token.token.not_after > Utc::now() {
|
||||||
if let Ok(private_key) =
|
if let Ok(private_key) =
|
||||||
SigningKey::from_pkcs8_pem(&token.private_key)
|
SigningKey::from_pkcs8_pem(&token.private_key)
|
||||||
@ -192,11 +210,17 @@ impl MinecraftAuthStore {
|
|||||||
pub async fn login_begin(&mut self) -> crate::Result<MinecraftLoginFlow> {
|
pub async fn login_begin(&mut self) -> crate::Result<MinecraftLoginFlow> {
|
||||||
let (key, token) = self.refresh_and_get_device_token().await?;
|
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) =
|
let (session_id, redirect_uri) =
|
||||||
sisu_authenticate(&token.token, &challenge, &key).await?;
|
sisu_authenticate(&token.token, &challenge, &key).await?;
|
||||||
|
|
||||||
Ok(MinecraftLoginFlow {
|
Ok(MinecraftLoginFlow {
|
||||||
|
verifier,
|
||||||
challenge,
|
challenge,
|
||||||
session_id,
|
session_id,
|
||||||
redirect_uri: redirect_uri.msa_oauth_redirect,
|
redirect_uri: redirect_uri.msa_oauth_redirect,
|
||||||
@ -211,7 +235,7 @@ impl MinecraftAuthStore {
|
|||||||
) -> crate::Result<Credentials> {
|
) -> crate::Result<Credentials> {
|
||||||
let (key, token) = self.refresh_and_get_device_token().await?;
|
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(
|
let sisu_authorize = sisu_authorize(
|
||||||
Some(&flow.session_id),
|
Some(&flow.session_id),
|
||||||
&oauth_token.access_token,
|
&oauth_token.access_token,
|
||||||
@ -403,13 +427,14 @@ async fn sisu_authenticate(
|
|||||||
],
|
],
|
||||||
"Query": {
|
"Query": {
|
||||||
"code_challenge": challenge,
|
"code_challenge": challenge,
|
||||||
"code_challenge_method": "plain",
|
"code_challenge_method": "S256",
|
||||||
"state": "",
|
"state": generate_oauth_challenge(),
|
||||||
"prompt": "select_account"
|
"prompt": "select_account"
|
||||||
},
|
},
|
||||||
"RedirectUri": REDIRECT_URL,
|
"RedirectUri": REDIRECT_URL,
|
||||||
"Sandbox": "RETAIL",
|
"Sandbox": "RETAIL",
|
||||||
"TokenType": "code",
|
"TokenType": "code",
|
||||||
|
"TitleId": 1794566092,
|
||||||
}),
|
}),
|
||||||
key,
|
key,
|
||||||
MinecraftAuthStep::SisuAuthenicate,
|
MinecraftAuthStep::SisuAuthenicate,
|
||||||
@ -439,12 +464,12 @@ struct OAuthToken {
|
|||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
async fn oauth_token(
|
async fn oauth_token(
|
||||||
code: &str,
|
code: &str,
|
||||||
challenge: &str,
|
verifier: &str,
|
||||||
) -> Result<OAuthToken, MinecraftAuthenticationError> {
|
) -> Result<OAuthToken, MinecraftAuthenticationError> {
|
||||||
let mut query = HashMap::new();
|
let mut query = HashMap::new();
|
||||||
query.insert("client_id", "00000000402b5328");
|
query.insert("client_id", "00000000402b5328");
|
||||||
query.insert("code", code);
|
query.insert("code", code);
|
||||||
query.insert("code_verifier", challenge);
|
query.insert("code_verifier", &*verifier);
|
||||||
query.insert("grant_type", "authorization_code");
|
query.insert("grant_type", "authorization_code");
|
||||||
query.insert("redirect_uri", "https://login.live.com/oauth20_desktop.srf");
|
query.insert("redirect_uri", "https://login.live.com/oauth20_desktop.srf");
|
||||||
query.insert("scope", "service::user.auth.xboxlive.com::MBI_SSL");
|
query.insert("scope", "service::user.auth.xboxlive.com::MBI_SSL");
|
||||||
@ -555,6 +580,8 @@ async fn sisu_authorize(
|
|||||||
"Sandbox": "RETAIL",
|
"Sandbox": "RETAIL",
|
||||||
"SessionId": session_id,
|
"SessionId": session_id,
|
||||||
"SiteName": "user.auth.xboxlive.com",
|
"SiteName": "user.auth.xboxlive.com",
|
||||||
|
"RelyingParty": "http://xboxlive.com",
|
||||||
|
"UseModernGamertag": "true"
|
||||||
}),
|
}),
|
||||||
key,
|
key,
|
||||||
MinecraftAuthStep::SisuAuthorize,
|
MinecraftAuthStep::SisuAuthorize,
|
||||||
@ -854,9 +881,12 @@ async fn send_signed_request<T: DeserializeOwned>(
|
|||||||
.post(url)
|
.post(url)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("x-xbl-contract-version", "1")
|
|
||||||
.header("signature", &signature);
|
.header("signature", &signature);
|
||||||
|
|
||||||
|
if url != "https://sisu.xboxlive.com/authorize" {
|
||||||
|
request = request.header("x-xbl-contract-version", "1");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(auth) = authorization {
|
if let Some(auth) = authorization {
|
||||||
request = request.header("Authorization", auth);
|
request = request.header("Authorization", auth);
|
||||||
}
|
}
|
||||||
@ -885,6 +915,6 @@ async fn send_signed_request<T: DeserializeOwned>(
|
|||||||
fn generate_oauth_challenge() -> String {
|
fn generate_oauth_challenge() -> String {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let bytes: Vec<u8> = (0..32).map(|_| rng.gen()).collect();
|
let bytes: Vec<u8> = (0..64).map(|_| rng.gen()).collect();
|
||||||
bytes.iter().map(|byte| format!("{:02x}", byte)).collect()
|
bytes.iter().map(|byte| format!("{:02x}", byte)).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "theseus_gui",
|
"name": "theseus_gui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_gui"
|
name = "theseus_gui"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
license = ""
|
license = ""
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Modrinth App",
|
"productName": "Modrinth App",
|
||||||
"version": "0.7.1"
|
"version": "0.7.2"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
|||||||
@ -23,7 +23,10 @@ defineExpose({
|
|||||||
supportLink.value =
|
supportLink.value =
|
||||||
'https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues'
|
'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
|
metadata.value.network = true
|
||||||
}
|
}
|
||||||
if (errorVal.message.includes('because the target machine actively refused it')) {
|
if (errorVal.message.includes('because the target machine actively refused it')) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user