OneLauncher-Cracked/packages/oneclient_auth/src/diagnostics.rs
2026-07-31 23:22:14 +02:00

403 lines
17 KiB
Rust

//! Adapted from Modrinth's `minecraft-auth-errors.ts`
//! (GPL-3.0)
use reqwest::StatusCode;
use super::error::{friendly_xbox_error, MinecraftAuthError, MinecraftAuthStep};
#[derive(Debug, Clone, serde::Serialize)]
pub struct AuthErrorGuidance {
pub what_happened: String,
pub steps_to_fix: Vec<String>,
}
impl AuthErrorGuidance {
fn new(what_happened: &str, steps_to_fix: &[&str]) -> Self {
Self {
what_happened: what_happened.to_string(),
steps_to_fix: steps_to_fix.iter().map(|s| (*s).to_string()).collect(),
}
}
}
#[must_use]
pub fn diagnose_auth_error(err: &MinecraftAuthError) -> Option<AuthErrorGuidance> {
match err {
MinecraftAuthError::XboxError { error_code, .. } => guidance_for_xerr(*error_code),
MinecraftAuthError::ServiceError { step, status_code } => {
guidance_for_service_error(*step, *status_code)
}
MinecraftAuthError::DeserializeError { step, .. } => guidance_for_step(*step),
MinecraftAuthError::RequestError { source, .. } => {
Some(network_guidance(classify_network_failure(source)))
}
MinecraftAuthError::BrowserAuthorizationExpired
| MinecraftAuthError::BrowserLoginNotFound => Some(AuthErrorGuidance::new(
"The Microsoft sign-in window was closed or expired before sign-in finished.",
&[
"Start the sign-in again",
"Complete the Microsoft sign-in in your browser without closing the window",
"If your browser blocked the redirect back, allow it and try once more",
],
)),
_ => None,
}
}
fn guidance_for_xerr(code: u64) -> Option<AuthErrorGuidance> {
Some(match code {
2_148_916_222 => AuthErrorGuidance::new(
"This account requires age verification to comply with UK regulations before it can sign in.",
&[
"Go to the Minecraft login page and sign in (https://www.minecraft.net/en-us/login)",
"Follow the instructions to verify your age",
"Once verified, try signing in again",
"For more help see UK age verification on Xbox (https://support.xbox.com/en-GB/help/family-online-safety/online-safety/UK-age-verification)",
],
),
2_148_916_227 => AuthErrorGuidance::new(
"This account was suspended for violating Xbox Community Standards.",
&[
"Visit Xbox Support and review the enforcement details (https://support.xbox.com)",
"Submit an appeal if one is available",
],
),
2_148_916_229 => AuthErrorGuidance::new(
"This account is restricted and does not have permission to play online.",
&[
"Have a guardian sign in to Microsoft Family (https://account.microsoft.com/family/)",
"Update the online play permissions",
"Once finished, try signing in again",
],
),
2_148_916_233 => AuthErrorGuidance::new(
"This account does not have an Xbox profile set up, or does not own Minecraft.",
&[
"Make sure Minecraft is purchased on this account",
"Visit the Minecraft login page and sign in (https://www.minecraft.net/en-us/login)",
"Complete Xbox profile setup if prompted",
"Once finished, try signing in again",
],
),
2_148_916_234 => AuthErrorGuidance::new(
"This account has not accepted Xbox's Terms of Service.",
&[
"Visit Xbox and sign in (https://www.xbox.com)",
"Accept the Terms if prompted",
"Once finished, try signing in again",
],
),
2_148_916_235 => AuthErrorGuidance::new(
"Xbox Live is not available in your region, so sign-in is blocked.",
&[
"Xbox services must be supported in your country before you can sign in",
"Check Xbox availability for supported regions (https://www.xbox.com/en-US/regions)",
],
),
2_148_916_236 | 2_148_916_237 => AuthErrorGuidance::new(
"This account requires adult verification under South Korean regulations.",
&[
"Visit Xbox and sign in (https://www.xbox.com)",
"Complete the identity verification process",
"Once finished, try signing in again",
],
),
2_148_916_238 => AuthErrorGuidance::new(
"This account is underage and not linked to a Microsoft family group.",
&[
"Review the Minecraft Family Setup guide (https://help.minecraft.net/hc/en-us/articles/4408968616077)",
"Join or create a family group as instructed",
"Once finished, try signing in again",
],
),
_ => return None,
})
}
fn guidance_for_service_error(
step: MinecraftAuthStep,
status: StatusCode,
) -> Option<AuthErrorGuidance> {
if step == MinecraftAuthStep::MinecraftToken {
if status == StatusCode::TOO_MANY_REQUESTS {
return Some(AuthErrorGuidance::new(
"Microsoft or Minecraft temporarily blocked the sign-in because there were too many recent attempts.",
&[
"Wait about an hour before trying again",
"Restart OneClient after waiting",
"Try signing in once more",
"If it keeps happening, wait longer before retrying so the temporary limit can clear",
],
));
}
if status.is_server_error() {
return Some(AuthErrorGuidance::new(
"Minecraft's authentication service is returning a server error, so sign-in cannot finish right now.",
&[
"Wait a few minutes and try signing in again",
"Check Xbox status for current service issues (https://support.xbox.com/xbox-live-status)",
"Try the official Minecraft Launcher to confirm whether Minecraft sign-in is affected there too (https://www.minecraft.net/en-us/download)",
"If the service is healthy and this keeps happening, contact support with the debug information",
],
));
}
}
guidance_for_step(step)
}
fn guidance_for_step(step: MinecraftAuthStep) -> Option<AuthErrorGuidance> {
Some(match step {
MinecraftAuthStep::RefreshToken | MinecraftAuthStep::AuthCodeExchange => {
AuthErrorGuidance::new(
"Your saved Microsoft sign-in has expired or was revoked, so your Minecraft session could not be renewed.",
&[
"Sign out of the affected Minecraft account in OneClient",
"Sign in to the account again",
"Once the new sign-in finishes, try launching Minecraft again",
],
)
}
MinecraftAuthStep::XblAuthenticate => AuthErrorGuidance::new(
"Xbox rejected the first sign-in step. This is most often caused by your system clock or time zone being out of sync, or by a temporary Xbox block.",
&[
"Open your system date and time settings",
"Turn on automatic time zone and automatic time, if available",
"Use the sync option in your settings to synchronise the clock",
"Restart OneClient and try signing in again",
"If it persists, check Xbox status (https://support.xbox.com/xbox-live-status)",
],
),
MinecraftAuthStep::XstsAuthorize => AuthErrorGuidance::new(
"Xbox rejected the request to authorize this account for Minecraft, but did not return a specific account restriction we recognise.",
&[
"Sign in with the official Minecraft Launcher (https://www.minecraft.net/en-us/download)",
"Complete any prompts shown by Microsoft, Xbox, or Minecraft",
"Try signing in to OneClient again",
"If the official launcher also fails, follow the error shown there or contact Xbox Support",
],
),
MinecraftAuthStep::MinecraftProfile => AuthErrorGuidance::new(
"Minecraft services could not return a Java Edition profile for this account. This usually means the game was purchased recently, the Java profile is not finished being created, or the wrong Microsoft account is being used.",
&[
"Sign in with the official Minecraft Launcher and launch Java Edition once (https://www.minecraft.net/en-us/download)",
"Wait up to an hour if the purchase or profile setup was recent",
"Make sure you are using the Microsoft account that owns Minecraft",
"Try signing in to OneClient again",
],
),
_ => return None,
})
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct AuthErrorSample {
pub label: &'static str,
pub message: String,
pub guidance: Option<AuthErrorGuidance>,
}
#[must_use]
pub fn preview_samples() -> Vec<AuthErrorSample> {
use MinecraftAuthStep::{
MinecraftProfile, MinecraftToken, RefreshToken, XblAuthenticate, XstsAuthorize,
};
let errors: Vec<(&'static str, MinecraftAuthError)> = vec![
(
"Xbox 403 (RUST-4)",
MinecraftAuthError::ServiceError {
step: XblAuthenticate,
status_code: StatusCode::FORBIDDEN,
},
),
("No Xbox profile", xerr(2_148_916_233)),
("Region blocked", xerr(2_148_916_235)),
("Child account", xerr(2_148_916_238)),
("Account suspended", xerr(2_148_916_227)),
("Restricted / no online play", xerr(2_148_916_229)),
("ToS not accepted", xerr(2_148_916_234)),
("UK age verification", xerr(2_148_916_222)),
("KR adult verification", xerr(2_148_916_236)),
(
"Rate limited (429)",
MinecraftAuthError::ServiceError {
step: MinecraftToken,
status_code: StatusCode::TOO_MANY_REQUESTS,
},
),
(
"MC service error (503)",
MinecraftAuthError::ServiceError {
step: MinecraftToken,
status_code: StatusCode::SERVICE_UNAVAILABLE,
},
),
(
"Profile fetch failed",
MinecraftAuthError::ServiceError {
step: MinecraftProfile,
status_code: StatusCode::NOT_FOUND,
},
),
(
"Session expired",
MinecraftAuthError::ServiceError {
step: RefreshToken,
status_code: StatusCode::BAD_REQUEST,
},
),
(
"Generic Xbox reject",
MinecraftAuthError::ServiceError {
step: XstsAuthorize,
status_code: StatusCode::UNAUTHORIZED,
},
),
];
let mut out: Vec<AuthErrorSample> = errors
.into_iter()
.map(|(label, err)| AuthErrorSample {
label,
message: err.to_string(),
guidance: diagnose_auth_error(&err),
})
.collect();
out.push(AuthErrorSample {
label: "Network unreachable",
message: "error sending request for url (https://user.auth.xboxlive.com/user/authenticate): connection closed".to_string(),
guidance: Some(network_guidance(NetworkFailure::Generic)),
});
out.push(AuthErrorSample {
label: "TLS certificate rejected",
message: "error sending request for url (https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode): invalid peer certificate: UnknownIssuer".to_string(),
guidance: Some(network_guidance(NetworkFailure::Certificate)),
});
out.push(AuthErrorSample {
label: "TLS handshake failed",
message: "error sending request for url (https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode): received fatal alert: HandshakeFailure".to_string(),
guidance: Some(network_guidance(NetworkFailure::Handshake)),
});
out.push(AuthErrorSample {
label: "DNS lookup failed",
message: "error sending request for url (https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode): dns error: failed to lookup address information".to_string(),
guidance: Some(network_guidance(NetworkFailure::Dns)),
});
out
}
fn xerr(code: u64) -> MinecraftAuthError {
MinecraftAuthError::XboxError {
step: MinecraftAuthStep::XstsAuthorize,
error_code: code,
message: friendly_xbox_error(code).unwrap_or("Xbox rejected sign-in.").to_string(),
redirect: None,
}
}
/// Which layer of the connection failed.
///
/// reqwest reports every one of these as a connect error, so the text of the
/// source chain is the only thing that separates "your antivirus is
/// intercepting TLS" from "your DNS is broken" — and they need different
/// instructions. A wrong guess here costs the user a wasted support round, so
/// anything unrecognised falls back to the generic advice.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NetworkFailure {
Certificate,
Handshake,
Dns,
Generic,
}
fn classify_network_failure(err: &reqwest::Error) -> NetworkFailure {
let chain = oneclient_net::error_chain(err).to_ascii_lowercase();
// Certificate before handshake: a rejected certificate also aborts the
// handshake, and the certificate wording is the more specific of the two.
const CERTIFICATE: &[&str] = &[
"invalid peer certificate",
"unknownissuer",
"certificate",
"certexpired",
"notvalidforname",
];
const HANDSHAKE: &[&str] = &[
"handshake",
"received fatal alert",
"peer misbehaved",
"no cipher suites in common",
"protocol version",
"unsupported protocol",
];
const DNS: &[&str] = &[
"dns error",
"failed to lookup address",
"no record found",
"nodename nor servname",
"name or service not known",
"no such host",
];
let matches = |markers: &[&str]| markers.iter().any(|marker| chain.contains(marker));
if matches(CERTIFICATE) {
NetworkFailure::Certificate
} else if matches(HANDSHAKE) {
NetworkFailure::Handshake
} else if matches(DNS) {
NetworkFailure::Dns
} else {
NetworkFailure::Generic
}
}
fn network_guidance(failure: NetworkFailure) -> AuthErrorGuidance {
match failure {
NetworkFailure::Certificate => AuthErrorGuidance::new(
"OneClient reached the Microsoft sign-in service, but could not verify its security certificate. Something on this machine or network is intercepting the encrypted connection — usually antivirus HTTPS scanning, a school or workplace filter, or a VPN.",
&[
"Turn off HTTPS/SSL scanning in your antivirus (often called \"encrypted connection scanning\", \"web shield\", or \"SSL interception\")",
"Temporarily disable your VPN or proxy and try signing in again",
"If you are on a school or workplace network, try a home network or phone hotspot to confirm",
"Check that your system date, time, and time zone are correct",
],
),
NetworkFailure::Handshake => AuthErrorGuidance::new(
"OneClient reached the Microsoft sign-in service, but the two could not agree on a secure connection. This normally means a proxy, filter, or antivirus in between is using outdated encryption settings.",
&[
"Turn off HTTPS/SSL scanning in your antivirus and try signing in again",
"Temporarily disable your VPN or proxy software",
"If you are on a school or workplace network, try a home network or phone hotspot to confirm",
"Make sure OneClient is up to date",
],
),
NetworkFailure::Dns => AuthErrorGuidance::new(
"OneClient could not look up the address of the Microsoft sign-in service. This is a DNS problem on this machine or network, not a problem with your account.",
&[
"Check that your internet connection is working",
"Temporarily disable your VPN or proxy and try signing in again",
"Check your hosts file for entries blocking Microsoft or Minecraft domains",
"Try a different DNS server, such as Cloudflare (1.1.1.1) or Google (8.8.8.8)",
"Restart OneClient and try signing in again",
],
),
NetworkFailure::Generic => AuthErrorGuidance::new(
"OneClient could not connect to a Microsoft, Xbox, or Minecraft service needed for sign-in. This is usually a local network, DNS, proxy, firewall, hosts file, VPN, or antivirus issue.",
&[
"Restart OneClient and try signing in again",
"Check that your internet connection is working",
"Allow OneClient through your firewall, antivirus, proxy, VPN, and hosts file rules",
"Try a different network, or temporarily disable VPN/proxy software if you use one",
"If routing or DNS is the issue, a service like Cloudflare WARP can sometimes help",
],
),
}
}