Fix auth timestamps (#1184)

* Fix auth timestamps

* Update error message, get valid timestamp on token refresh

* fix lint
This commit is contained in:
Geometrically
2024-05-10 10:31:34 -07:00
committed by GitHub
parent a4f133eb46
commit 7394fdc162
3 changed files with 264 additions and 120 deletions

View File

@@ -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(),
} }
} }
} }

View File

@@ -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]

View File

@@ -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>