You've already forked AstralRinth
forked from didirus/AstralRinth
Fix auth timestamps (#1184)
* Fix auth timestamps * Update error message, get valid timestamp on token refresh * fix lint
This commit is contained in:
@@ -106,7 +106,8 @@ pub enum ErrorKind {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
source: tracing_error::TracedError<ErrorKind>,
|
pub raw: std::sync::Arc<ErrorKind>,
|
||||||
|
pub source: tracing_error::TracedError<std::sync::Arc<ErrorKind>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
impl std::error::Error for Error {
|
||||||
@@ -123,8 +124,12 @@ impl std::fmt::Display for Error {
|
|||||||
|
|
||||||
impl<E: Into<ErrorKind>> From<E> for Error {
|
impl<E: Into<ErrorKind>> From<E> for Error {
|
||||||
fn from(source: E) -> Self {
|
fn from(source: E) -> Self {
|
||||||
|
let error = Into::<ErrorKind>::into(source);
|
||||||
|
let boxed_error = std::sync::Arc::new(error);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
source: Into::<ErrorKind>::into(source).in_current_span(),
|
raw: boxed_error.clone(),
|
||||||
|
source: boxed_error.in_current_span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use crate::data::DirectoryInfo;
|
use crate::data::DirectoryInfo;
|
||||||
use crate::util::fetch::{read_json, write, IoSemaphore, REQWEST_CLIENT};
|
use crate::util::fetch::{read_json, write, IoSemaphore, REQWEST_CLIENT};
|
||||||
use crate::State;
|
use crate::{ErrorKind, 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, NaiveDate, Utc};
|
use chrono::{DateTime, Duration, 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};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
use reqwest::{Error, Response};
|
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;
|
||||||
@@ -82,8 +82,6 @@ pub struct SaveDeviceToken {
|
|||||||
pub x: String,
|
pub x: String,
|
||||||
pub y: String,
|
pub y: String,
|
||||||
pub token: DeviceToken,
|
pub token: DeviceToken,
|
||||||
#[serde(default)]
|
|
||||||
modern: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
@@ -136,11 +134,13 @@ impl MinecraftAuthStore {
|
|||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
async fn refresh_and_get_device_token(
|
async fn refresh_and_get_device_token(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> crate::Result<(DeviceTokenKey, DeviceToken)> {
|
current_date: DateTime<Utc>,
|
||||||
|
force_generate: bool,
|
||||||
|
) -> crate::Result<(DeviceTokenKey, DeviceToken, DateTime<Utc>, bool)> {
|
||||||
macro_rules! generate_key {
|
macro_rules! generate_key {
|
||||||
($self:ident, $generate_key:expr, $device_token:expr, $SaveDeviceToken:path) => {{
|
($self:ident, $generate_key:expr, $device_token:expr, $SaveDeviceToken:path) => {{
|
||||||
let key = generate_key()?;
|
let key = generate_key()?;
|
||||||
let token = device_token(&key).await?;
|
let res = device_token(&key, current_date).await?;
|
||||||
|
|
||||||
self.token = Some(SaveDeviceToken {
|
self.token = Some(SaveDeviceToken {
|
||||||
id: key.id.clone(),
|
id: key.id.clone(),
|
||||||
@@ -153,30 +153,20 @@ impl MinecraftAuthStore {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
x: key.x.clone(),
|
x: key.x.clone(),
|
||||||
y: key.y.clone(),
|
y: key.y.clone(),
|
||||||
token: token.clone(),
|
token: res.value.clone(),
|
||||||
modern: true,
|
|
||||||
});
|
});
|
||||||
self.save().await?;
|
self.save().await?;
|
||||||
|
|
||||||
(key, token)
|
(key, res.value, res.date, true)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key, token) = if let Some(ref token) = self.token {
|
let (key, token, date, valid_date) = if let Some(ref token) = self.token
|
||||||
// reset device token for legacy launcher versions with broken values
|
{
|
||||||
if self.users.is_empty() && !token.modern {
|
if let Ok(private_key) =
|
||||||
return Ok(generate_key!(
|
SigningKey::from_pkcs8_pem(&token.private_key)
|
||||||
self,
|
{
|
||||||
generate_key,
|
if token.token.not_after > Utc::now() && !force_generate {
|
||||||
device_token,
|
|
||||||
SaveDeviceToken
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.token.not_after > Utc::now() {
|
|
||||||
if let Ok(private_key) =
|
|
||||||
SigningKey::from_pkcs8_pem(&token.private_key)
|
|
||||||
{
|
|
||||||
(
|
(
|
||||||
DeviceTokenKey {
|
DeviceTokenKey {
|
||||||
id: token.id.clone(),
|
id: token.id.clone(),
|
||||||
@@ -185,14 +175,20 @@ impl MinecraftAuthStore {
|
|||||||
y: token.y.clone(),
|
y: token.y.clone(),
|
||||||
},
|
},
|
||||||
token.token.clone(),
|
token.token.clone(),
|
||||||
|
current_date,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
generate_key!(
|
let key = DeviceTokenKey {
|
||||||
self,
|
id: token.id.clone(),
|
||||||
generate_key,
|
key: private_key,
|
||||||
device_token,
|
x: token.x.clone(),
|
||||||
SaveDeviceToken
|
y: token.y.clone(),
|
||||||
)
|
};
|
||||||
|
|
||||||
|
let res = device_token(&key, current_date).await?;
|
||||||
|
|
||||||
|
(key, res.value, res.date, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
generate_key!(self, generate_key, device_token, SaveDeviceToken)
|
generate_key!(self, generate_key, device_token, SaveDeviceToken)
|
||||||
@@ -201,28 +197,60 @@ impl MinecraftAuthStore {
|
|||||||
generate_key!(self, generate_key, device_token, SaveDeviceToken)
|
generate_key!(self, generate_key, device_token, SaveDeviceToken)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((key, token))
|
Ok((key, token, date, valid_date))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
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, current_date, valid_date) =
|
||||||
|
self.refresh_and_get_device_token(Utc::now(), false).await?;
|
||||||
|
|
||||||
let verifier = generate_oauth_challenge();
|
let verifier = generate_oauth_challenge();
|
||||||
let mut hasher = sha2::Sha256::new();
|
let mut hasher = sha2::Sha256::new();
|
||||||
hasher.update(&verifier);
|
hasher.update(&verifier);
|
||||||
let result = hasher.finalize();
|
let result = hasher.finalize();
|
||||||
let challenge = BASE64_URL_SAFE_NO_PAD.encode(&result);
|
let challenge = BASE64_URL_SAFE_NO_PAD.encode(result);
|
||||||
|
|
||||||
let (session_id, redirect_uri) =
|
match sisu_authenticate(&token.token, &challenge, &key, current_date)
|
||||||
sisu_authenticate(&token.token, &challenge, &key).await?;
|
.await
|
||||||
|
{
|
||||||
|
Ok((session_id, redirect_uri)) => Ok(MinecraftLoginFlow {
|
||||||
|
verifier,
|
||||||
|
challenge,
|
||||||
|
session_id,
|
||||||
|
redirect_uri: redirect_uri.value.msa_oauth_redirect,
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
if !valid_date {
|
||||||
|
let (key, token, current_date, _) = self
|
||||||
|
.refresh_and_get_device_token(Utc::now(), false)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(MinecraftLoginFlow {
|
let verifier = generate_oauth_challenge();
|
||||||
verifier,
|
let mut hasher = sha2::Sha256::new();
|
||||||
challenge,
|
hasher.update(&verifier);
|
||||||
session_id,
|
let result = hasher.finalize();
|
||||||
redirect_uri: redirect_uri.msa_oauth_redirect,
|
let challenge = BASE64_URL_SAFE_NO_PAD.encode(result);
|
||||||
})
|
|
||||||
|
let (session_id, redirect_uri) = sisu_authenticate(
|
||||||
|
&token.token,
|
||||||
|
&challenge,
|
||||||
|
&key,
|
||||||
|
current_date,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(MinecraftLoginFlow {
|
||||||
|
verifier,
|
||||||
|
challenge,
|
||||||
|
session_id,
|
||||||
|
redirect_uri: redirect_uri.value.msa_oauth_redirect,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(crate::ErrorKind::from(err).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
@@ -231,20 +259,27 @@ impl MinecraftAuthStore {
|
|||||||
code: &str,
|
code: &str,
|
||||||
flow: MinecraftLoginFlow,
|
flow: MinecraftLoginFlow,
|
||||||
) -> 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(Utc::now(), false).await?;
|
||||||
|
|
||||||
let oauth_token = oauth_token(code, &flow.verifier).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.value.access_token,
|
||||||
&token.token,
|
&token.token,
|
||||||
&key,
|
&key,
|
||||||
|
oauth_token.date,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let xbox_token =
|
let xbox_token = xsts_authorize(
|
||||||
xsts_authorize(sisu_authorize, &token.token, &key).await?;
|
sisu_authorize.value,
|
||||||
let minecraft_token = minecraft_token(xbox_token).await?;
|
&token.token,
|
||||||
|
&key,
|
||||||
|
sisu_authorize.date,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let minecraft_token = minecraft_token(xbox_token.value).await?;
|
||||||
|
|
||||||
minecraft_entitlements(&minecraft_token.access_token).await?;
|
minecraft_entitlements(&minecraft_token.access_token).await?;
|
||||||
|
|
||||||
@@ -256,9 +291,9 @@ impl MinecraftAuthStore {
|
|||||||
id: profile_id,
|
id: profile_id,
|
||||||
username: profile.name,
|
username: profile.name,
|
||||||
access_token: minecraft_token.access_token,
|
access_token: minecraft_token.access_token,
|
||||||
refresh_token: oauth_token.refresh_token,
|
refresh_token: oauth_token.value.refresh_token,
|
||||||
expires: Utc::now()
|
expires: oauth_token.date
|
||||||
+ Duration::seconds(oauth_token.expires_in as i64),
|
+ Duration::seconds(oauth_token.value.expires_in as i64),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.users.insert(profile_id, credentials.clone());
|
self.users.insert(profile_id, credentials.clone());
|
||||||
@@ -272,6 +307,52 @@ impl MinecraftAuthStore {
|
|||||||
Ok(credentials)
|
Ok(credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn refresh_token(
|
||||||
|
&mut self,
|
||||||
|
creds: &Credentials,
|
||||||
|
) -> crate::Result<Option<Credentials>> {
|
||||||
|
let cred_id = creds.id;
|
||||||
|
let profile_name = creds.username.clone();
|
||||||
|
|
||||||
|
let oauth_token = oauth_refresh(&creds.refresh_token).await?;
|
||||||
|
let (key, token, current_date, _) = self
|
||||||
|
.refresh_and_get_device_token(oauth_token.date, false)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let sisu_authorize = sisu_authorize(
|
||||||
|
None,
|
||||||
|
&oauth_token.value.access_token,
|
||||||
|
&token.token,
|
||||||
|
&key,
|
||||||
|
current_date,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let xbox_token = xsts_authorize(
|
||||||
|
sisu_authorize.value,
|
||||||
|
&token.token,
|
||||||
|
&key,
|
||||||
|
sisu_authorize.date,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let minecraft_token = minecraft_token(xbox_token.value).await?;
|
||||||
|
|
||||||
|
let val = Credentials {
|
||||||
|
id: cred_id,
|
||||||
|
username: profile_name,
|
||||||
|
access_token: minecraft_token.access_token,
|
||||||
|
refresh_token: oauth_token.value.refresh_token,
|
||||||
|
expires: oauth_token.date
|
||||||
|
+ Duration::seconds(oauth_token.value.expires_in as i64),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.users.insert(val.id, val.clone());
|
||||||
|
self.save().await?;
|
||||||
|
|
||||||
|
Ok(Some(val))
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub async fn get_default_credential(
|
pub async fn get_default_credential(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -293,38 +374,28 @@ impl MinecraftAuthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if creds.expires < Utc::now() {
|
if creds.expires < Utc::now() {
|
||||||
let cred_id = creds.id;
|
let old_credentials = creds.clone();
|
||||||
let profile_name = creds.username.clone();
|
|
||||||
|
|
||||||
let oauth_token = oauth_refresh(&creds.refresh_token).await?;
|
let res = self.refresh_token(&old_credentials).await;
|
||||||
let (key, token) = self.refresh_and_get_device_token().await?;
|
|
||||||
|
|
||||||
let sisu_authorize = sisu_authorize(
|
match res {
|
||||||
None,
|
Ok(val) => Ok(val),
|
||||||
&oauth_token.access_token,
|
Err(err) => {
|
||||||
&token.token,
|
if let ErrorKind::MinecraftAuthenticationError(
|
||||||
&key,
|
MinecraftAuthenticationError::Request {
|
||||||
)
|
ref source,
|
||||||
.await?;
|
..
|
||||||
|
},
|
||||||
|
) = *err.raw
|
||||||
|
{
|
||||||
|
if source.is_connect() || source.is_timeout() {
|
||||||
|
return Ok(Some(old_credentials));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let xbox_token =
|
Err(err)
|
||||||
xsts_authorize(sisu_authorize, &token.token, &key).await?;
|
}
|
||||||
|
}
|
||||||
let minecraft_token = minecraft_token(xbox_token).await?;
|
|
||||||
|
|
||||||
let val = Credentials {
|
|
||||||
id: cred_id,
|
|
||||||
username: profile_name,
|
|
||||||
access_token: minecraft_token.access_token,
|
|
||||||
refresh_token: oauth_token.refresh_token,
|
|
||||||
expires: Utc::now()
|
|
||||||
+ Duration::seconds(oauth_token.expires_in as i64),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.users.insert(val.id, val.clone());
|
|
||||||
self.save().await?;
|
|
||||||
|
|
||||||
Ok(Some(val))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Some(creds.clone()))
|
Ok(Some(creds.clone()))
|
||||||
}
|
}
|
||||||
@@ -357,6 +428,11 @@ const MICROSOFT_CLIENT_ID: &str = "00000000402b5328";
|
|||||||
const REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf";
|
const REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf";
|
||||||
const REQUESTED_SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL";
|
const REQUESTED_SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL";
|
||||||
|
|
||||||
|
struct RequestWithDate<T> {
|
||||||
|
pub date: DateTime<Utc>,
|
||||||
|
pub value: T,
|
||||||
|
}
|
||||||
|
|
||||||
// flow steps
|
// flow steps
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
@@ -370,8 +446,9 @@ pub struct DeviceToken {
|
|||||||
#[tracing::instrument(skip(key))]
|
#[tracing::instrument(skip(key))]
|
||||||
pub async fn device_token(
|
pub async fn device_token(
|
||||||
key: &DeviceTokenKey,
|
key: &DeviceTokenKey,
|
||||||
) -> Result<DeviceToken, MinecraftAuthenticationError> {
|
current_date: DateTime<Utc>,
|
||||||
Ok(send_signed_request(
|
) -> Result<RequestWithDate<DeviceToken>, MinecraftAuthenticationError> {
|
||||||
|
let res = send_signed_request(
|
||||||
None,
|
None,
|
||||||
"https://device.auth.xboxlive.com/device/authenticate",
|
"https://device.auth.xboxlive.com/device/authenticate",
|
||||||
"/device/authenticate",
|
"/device/authenticate",
|
||||||
@@ -396,9 +473,14 @@ pub async fn device_token(
|
|||||||
}),
|
}),
|
||||||
key,
|
key,
|
||||||
MinecraftAuthStep::GetDeviceToken,
|
MinecraftAuthStep::GetDeviceToken,
|
||||||
|
current_date,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.1)
|
|
||||||
|
Ok(RequestWithDate {
|
||||||
|
date: res.current_date,
|
||||||
|
value: res.body,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -412,8 +494,10 @@ async fn sisu_authenticate(
|
|||||||
token: &str,
|
token: &str,
|
||||||
challenge: &str,
|
challenge: &str,
|
||||||
key: &DeviceTokenKey,
|
key: &DeviceTokenKey,
|
||||||
) -> Result<(String, RedirectUri), MinecraftAuthenticationError> {
|
current_date: DateTime<Utc>,
|
||||||
let (headers, res) = send_signed_request(
|
) -> Result<(String, RequestWithDate<RedirectUri>), MinecraftAuthenticationError>
|
||||||
|
{
|
||||||
|
let res = send_signed_request::<RedirectUri>(
|
||||||
None,
|
None,
|
||||||
"https://sisu.xboxlive.com/authenticate",
|
"https://sisu.xboxlive.com/authenticate",
|
||||||
"/authenticate",
|
"/authenticate",
|
||||||
@@ -436,16 +520,24 @@ async fn sisu_authenticate(
|
|||||||
}),
|
}),
|
||||||
key,
|
key,
|
||||||
MinecraftAuthStep::SisuAuthenicate,
|
MinecraftAuthStep::SisuAuthenicate,
|
||||||
|
current_date,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let session_id = headers
|
let session_id = res
|
||||||
|
.headers
|
||||||
.get("X-SessionId")
|
.get("X-SessionId")
|
||||||
.and_then(|x| x.to_str().ok())
|
.and_then(|x| x.to_str().ok())
|
||||||
.ok_or_else(|| MinecraftAuthenticationError::NoSessionId)?
|
.ok_or_else(|| MinecraftAuthenticationError::NoSessionId)?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
Ok((session_id, res))
|
Ok((
|
||||||
|
session_id,
|
||||||
|
RequestWithDate {
|
||||||
|
date: res.current_date,
|
||||||
|
value: res.body,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -463,11 +555,11 @@ struct OAuthToken {
|
|||||||
async fn oauth_token(
|
async fn oauth_token(
|
||||||
code: &str,
|
code: &str,
|
||||||
verifier: &str,
|
verifier: &str,
|
||||||
) -> Result<OAuthToken, MinecraftAuthenticationError> {
|
) -> Result<RequestWithDate<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", &*verifier);
|
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");
|
||||||
@@ -486,6 +578,7 @@ async fn oauth_token(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let status = res.status();
|
let status = res.status();
|
||||||
|
let current_date = get_date_header(res.headers());
|
||||||
let text = res.text().await.map_err(|source| {
|
let text = res.text().await.map_err(|source| {
|
||||||
MinecraftAuthenticationError::Request {
|
MinecraftAuthenticationError::Request {
|
||||||
source,
|
source,
|
||||||
@@ -493,20 +586,25 @@ async fn oauth_token(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
serde_json::from_str(&text).map_err(|source| {
|
let body = serde_json::from_str(&text).map_err(|source| {
|
||||||
MinecraftAuthenticationError::DeserializeResponse {
|
MinecraftAuthenticationError::DeserializeResponse {
|
||||||
source,
|
source,
|
||||||
raw: text,
|
raw: text,
|
||||||
step: MinecraftAuthStep::GetOAuthToken,
|
step: MinecraftAuthStep::GetOAuthToken,
|
||||||
status_code: status,
|
status_code: status,
|
||||||
}
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(RequestWithDate {
|
||||||
|
date: current_date,
|
||||||
|
value: body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
async fn oauth_refresh(
|
async fn oauth_refresh(
|
||||||
refresh_token: &str,
|
refresh_token: &str,
|
||||||
) -> Result<OAuthToken, MinecraftAuthenticationError> {
|
) -> Result<RequestWithDate<OAuthToken>, MinecraftAuthenticationError> {
|
||||||
let mut query = HashMap::new();
|
let mut query = HashMap::new();
|
||||||
query.insert("client_id", "00000000402b5328");
|
query.insert("client_id", "00000000402b5328");
|
||||||
query.insert("refresh_token", refresh_token);
|
query.insert("refresh_token", refresh_token);
|
||||||
@@ -528,6 +626,7 @@ async fn oauth_refresh(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let status = res.status();
|
let status = res.status();
|
||||||
|
let current_date = get_date_header(res.headers());
|
||||||
let text = res.text().await.map_err(|source| {
|
let text = res.text().await.map_err(|source| {
|
||||||
MinecraftAuthenticationError::Request {
|
MinecraftAuthenticationError::Request {
|
||||||
source,
|
source,
|
||||||
@@ -535,13 +634,18 @@ async fn oauth_refresh(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
serde_json::from_str(&text).map_err(|source| {
|
let body = serde_json::from_str(&text).map_err(|source| {
|
||||||
MinecraftAuthenticationError::DeserializeResponse {
|
MinecraftAuthenticationError::DeserializeResponse {
|
||||||
source,
|
source,
|
||||||
raw: text,
|
raw: text,
|
||||||
step: MinecraftAuthStep::RefreshOAuthToken,
|
step: MinecraftAuthStep::RefreshOAuthToken,
|
||||||
status_code: status,
|
status_code: status,
|
||||||
}
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(RequestWithDate {
|
||||||
|
date: current_date,
|
||||||
|
value: body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,8 +666,9 @@ async fn sisu_authorize(
|
|||||||
access_token: &str,
|
access_token: &str,
|
||||||
device_token: &str,
|
device_token: &str,
|
||||||
key: &DeviceTokenKey,
|
key: &DeviceTokenKey,
|
||||||
) -> Result<SisuAuthorize, MinecraftAuthenticationError> {
|
current_date: DateTime<Utc>,
|
||||||
Ok(send_signed_request(
|
) -> Result<RequestWithDate<SisuAuthorize>, MinecraftAuthenticationError> {
|
||||||
|
let res = send_signed_request(
|
||||||
None,
|
None,
|
||||||
"https://sisu.xboxlive.com/authorize",
|
"https://sisu.xboxlive.com/authorize",
|
||||||
"/authorize",
|
"/authorize",
|
||||||
@@ -587,9 +692,14 @@ async fn sisu_authorize(
|
|||||||
}),
|
}),
|
||||||
key,
|
key,
|
||||||
MinecraftAuthStep::SisuAuthorize,
|
MinecraftAuthStep::SisuAuthorize,
|
||||||
|
current_date,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.1)
|
|
||||||
|
Ok(RequestWithDate {
|
||||||
|
date: res.current_date,
|
||||||
|
value: res.body,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(key))]
|
#[tracing::instrument(skip(key))]
|
||||||
@@ -597,8 +707,9 @@ async fn xsts_authorize(
|
|||||||
authorize: SisuAuthorize,
|
authorize: SisuAuthorize,
|
||||||
device_token: &str,
|
device_token: &str,
|
||||||
key: &DeviceTokenKey,
|
key: &DeviceTokenKey,
|
||||||
) -> Result<DeviceToken, MinecraftAuthenticationError> {
|
current_date: DateTime<Utc>,
|
||||||
Ok(send_signed_request(
|
) -> Result<RequestWithDate<DeviceToken>, MinecraftAuthenticationError> {
|
||||||
|
let res = send_signed_request(
|
||||||
None,
|
None,
|
||||||
"https://xsts.auth.xboxlive.com/xsts/authorize",
|
"https://xsts.auth.xboxlive.com/xsts/authorize",
|
||||||
"/xsts/authorize",
|
"/xsts/authorize",
|
||||||
@@ -614,9 +725,14 @@ async fn xsts_authorize(
|
|||||||
}),
|
}),
|
||||||
key,
|
key,
|
||||||
MinecraftAuthStep::XstsAuthorize,
|
MinecraftAuthStep::XstsAuthorize,
|
||||||
|
current_date,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.1)
|
|
||||||
|
Ok(RequestWithDate {
|
||||||
|
date: res.current_date,
|
||||||
|
value: res.body,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -820,6 +936,12 @@ fn generate_key() -> Result<DeviceTokenKey, MinecraftAuthenticationError> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SignedRequestResponse<T> {
|
||||||
|
pub headers: HeaderMap,
|
||||||
|
pub current_date: DateTime<Utc>,
|
||||||
|
pub body: T,
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(key))]
|
#[tracing::instrument(skip(key))]
|
||||||
async fn send_signed_request<T: DeserializeOwned>(
|
async fn send_signed_request<T: DeserializeOwned>(
|
||||||
authorization: Option<&str>,
|
authorization: Option<&str>,
|
||||||
@@ -828,14 +950,15 @@ async fn send_signed_request<T: DeserializeOwned>(
|
|||||||
raw_body: serde_json::Value,
|
raw_body: serde_json::Value,
|
||||||
key: &DeviceTokenKey,
|
key: &DeviceTokenKey,
|
||||||
step: MinecraftAuthStep,
|
step: MinecraftAuthStep,
|
||||||
) -> Result<(HeaderMap, T), MinecraftAuthenticationError> {
|
current_date: DateTime<Utc>,
|
||||||
|
) -> Result<SignedRequestResponse<T>, MinecraftAuthenticationError> {
|
||||||
let auth = authorization.map_or(Vec::new(), |v| v.as_bytes().to_vec());
|
let auth = authorization.map_or(Vec::new(), |v| v.as_bytes().to_vec());
|
||||||
|
|
||||||
let body = serde_json::to_vec(&raw_body).map_err(|source| {
|
let body = serde_json::to_vec(&raw_body).map_err(|source| {
|
||||||
MinecraftAuthenticationError::SerializeBody { source, step }
|
MinecraftAuthenticationError::SerializeBody { source, step }
|
||||||
})?;
|
})?;
|
||||||
let time: u128 =
|
let time: u128 =
|
||||||
{ ((Utc::now().timestamp() as u128) + 11644473600) * 10000000 };
|
{ ((current_date.timestamp() as u128) + 11644473600) * 10000000 };
|
||||||
|
|
||||||
use byteorder::WriteBytesExt;
|
use byteorder::WriteBytesExt;
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
@@ -914,6 +1037,9 @@ async fn send_signed_request<T: DeserializeOwned>(
|
|||||||
|
|
||||||
let status = res.status();
|
let status = res.status();
|
||||||
let headers = res.headers().clone();
|
let headers = res.headers().clone();
|
||||||
|
|
||||||
|
let current_date = get_date_header(&headers);
|
||||||
|
|
||||||
let body = res.text().await.map_err(|source| {
|
let body = res.text().await.map_err(|source| {
|
||||||
MinecraftAuthenticationError::Request { source, step }
|
MinecraftAuthenticationError::Request { source, step }
|
||||||
})?;
|
})?;
|
||||||
@@ -926,7 +1052,21 @@ async fn send_signed_request<T: DeserializeOwned>(
|
|||||||
status_code: status,
|
status_code: status,
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
Ok((headers, body))
|
Ok(SignedRequestResponse {
|
||||||
|
headers,
|
||||||
|
current_date,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn get_date_header(headers: &HeaderMap) -> DateTime<Utc> {
|
||||||
|
headers
|
||||||
|
.get(reqwest::header::DATE)
|
||||||
|
.and_then(|x| x.to_str().ok())
|
||||||
|
.and_then(|x| DateTime::parse_from_rfc2822(x).ok())
|
||||||
|
.map(|x| x.with_timezone(&Utc))
|
||||||
|
.unwrap_or(Utc::now())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
|
|||||||
@@ -73,10 +73,6 @@ async function loginMinecraft() {
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="markdown-body">
|
<div class="markdown-body">
|
||||||
<template v-if="errorType === 'minecraft_auth'">
|
<template v-if="errorType === 'minecraft_auth'">
|
||||||
<p>
|
|
||||||
Signing into Microsoft account is a complex task for the launchers, and there are a lot
|
|
||||||
of things can go wrong.
|
|
||||||
</p>
|
|
||||||
<template v-if="metadata.network">
|
<template v-if="metadata.network">
|
||||||
<h3>Network issues</h3>
|
<h3>Network issues</h3>
|
||||||
<p>
|
<p>
|
||||||
@@ -106,23 +102,26 @@ async function loginMinecraft() {
|
|||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<h3>Make sure you are signing into the right Microsoft account</h3>
|
<h3>Try another Microsoft account</h3>
|
||||||
<p>
|
<p>
|
||||||
More often than not, this error is caused by you signing into an incorrect Microsoft
|
Double check you've signed in with the right account. You may own Minecraft on a
|
||||||
account which isn't linked to Minecraft. Double check and try again!
|
different Microsoft account.
|
||||||
</p>
|
</p>
|
||||||
<h3>Try signing in and launching through the official launcher first</h3>
|
<div class="cta-button">
|
||||||
|
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||||
|
<LogInIcon /> Try another account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h3>Using PC Game Pass, coming from Bedrock, or just bought the game?</h3>
|
||||||
<p>
|
<p>
|
||||||
If you just bought Minecraft, are coming from the Bedrock Edition world and have never
|
Try signing in with the
|
||||||
played Java before, or just subscribed to PC Game Pass, you would need to start the
|
<a href="https://www.minecraft.net/en-us/download">official Minecraft Launcher</a>
|
||||||
game at least once using the
|
first. Once you're done, come back here and sign in!
|
||||||
<a href="https://www.minecraft.net/en-us/download">official Minecraft Launcher</a>.
|
|
||||||
Once you're done, come back here and sign in!
|
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<div class="cta-button">
|
<div class="cta-button">
|
||||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||||
<LogInIcon /> Sign in to Minecraft
|
<LogInIcon /> Try signing in again
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
@@ -168,7 +167,7 @@ async function loginMinecraft() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
<a :href="supportLink" class="btn" @click="errorModal.hide()"><ChatIcon /> Get support</a>
|
<a :href="supportLink" class="btn" @click="errorModal.hide()"><ChatIcon /> Get support</a>
|
||||||
<button class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
|
<button class="btn" @clicdck="errorModal.hide()"><XIcon /> Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
Reference in New Issue
Block a user