✨ feature(core): improve logging system, fix minecraft auth timestamps

This commit is contained in:
Pauline 2024-05-11 05:39:45 +02:00
parent 7b3a89e055
commit a67796bd6d
No known key found for this signature in database
GPG key ID: 722BB181973296AE
5 changed files with 344 additions and 147 deletions

View file

@ -4,93 +4,116 @@ use crate::data::{Credentials, Directories, MinecraftCredentials};
use crate::prelude::ClusterPath;
use crate::utils::io::{self, IOError};
use futures::TryFutureExt;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::io::Read;
use std::time::SystemTime;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
// TODO: put this in the global store
/// Core logging state and reader for OneLauncher.
#[derive(Serialize, Debug)]
pub struct LogManager {
/// The log file to read as a [`String`]
/// Type log type associated with this log file.
pub log_type: LogType,
/// The age of this log as a [`u64`] in seconds.
pub age: u64,
/// The log file to read as a [`String`].
pub log_file: String,
/// The parsed and censored output of the logfile.
pub output: Option<LogOutput>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
pub enum LogType {
Info,
Crash,
}
impl LogManager {
/// Initialize a new [`LogManager`].
async fn initialize(
log_type: LogType,
age: SystemTime,
cluster: &ClusterPath,
log_file: String,
clear: Option<bool>,
) -> crate::Result<Self> {
Ok(Self {
log_type,
age: age
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_else(|_| std::time::Duration::from_secs(0))
.as_secs(),
output: if clear.unwrap_or(false) {
None
} else {
Some(get_output_by_file(cluster, &log_file).await?)
Some(get_output_by_file(cluster, log_type, &log_file).await?)
},
log_file,
})
}
}
/// Get a [`Vec<LogManager>`] for a specific [`ClusterPath`].
/// Verify all [`LogManager`]s of a certain [`LogType`]
#[tracing::instrument]
pub async fn get_logs(
cluster_path: ClusterPath,
pub async fn get_logs_by_type(
cluster_path: &ClusterPath,
log_type: LogType,
clear: Option<bool>,
) -> crate::Result<Vec<LogManager>> {
let cluster_path = if let Some(c) = crate::cluster::get(&cluster_path, None).await? {
c.cluster_path()
} else {
return Err(anyhow::anyhow!(
"cluster {} isn't managed by onelauncher!",
cluster_path.to_string()
)
.into());
logs: &mut Vec<crate::Result<LogManager>>,
) -> crate::Result<()> {
let logs_folder = match log_type {
LogType::Info => Directories::cluster_logs_dir(cluster_path).await?,
LogType::Crash => Directories::crash_reports_dir(cluster_path).await?,
};
let logs_folder = Directories::cluster_logs_dir(&cluster_path).await?;
let mut logs = Vec::new();
if logs_folder.exists() {
for entry in
std::fs::read_dir(&logs_folder).map_err(|e| IOError::with_path(e, &logs_folder))?
{
let entry: std::fs::DirEntry =
entry.map_err(|e| IOError::with_path(e, &logs_folder))?;
let age = entry
.metadata()?
.created()
.unwrap_or_else(|_| SystemTime::UNIX_EPOCH);
let path = entry.path();
if !path.is_file() {
continue;
}
if let Some(name) = path.file_name() {
let name = name.to_string_lossy().to_string();
logs.push(LogManager::initialize(&cluster_path, name, clear).await);
logs.push(LogManager::initialize(log_type, age, &cluster_path, name, clear).await);
}
}
}
Ok(())
}
/// Get all [`LogManager`]s in the global [`State`].
pub async fn get_logs(
cluster_path: ClusterPath,
clear: Option<bool>,
) -> crate::Result<Vec<LogManager>> {
let cluster_path = cluster_path.cluster_path().await?;
let mut logs = Vec::new();
get_logs_by_type(&cluster_path, LogType::Info, clear, &mut logs).await?;
get_logs_by_type(&cluster_path, LogType::Crash, clear, &mut logs).await?;
let mut logs = logs
.into_iter()
.collect::<crate::Result<Vec<LogManager>>>()?;
logs.sort_by_key(|x| x.log_file.clone());
logs.sort_by(|a, b| b.age.cmp(&a.age).then(b.log_file.cmp(&a.log_file)));
Ok(logs)
}
/// Delete all stored logs from a specific [`ClusterPath`].
#[tracing::instrument]
pub async fn delete_logs(cluster_path: ClusterPath) -> crate::Result<()> {
let cluster_path = if let Some(c) = crate::cluster::get(&cluster_path, None).await? {
c.cluster_path()
} else {
return Err(anyhow::anyhow!(
"cluster {} isn't managed by onelauncher!",
cluster_path.to_string()
)
.into());
};
let cluster_path = cluster_path.cluster_path().await?;
let logs_folder = Directories::cluster_logs_dir(&cluster_path).await?;
for entry in std::fs::read_dir(&logs_folder).map_err(|e| IOError::with_path(e, &logs_folder))? {
let entry = entry.map_err(|e| IOError::with_path(e, &logs_folder))?;
@ -107,22 +130,23 @@ pub async fn delete_logs(cluster_path: ClusterPath) -> crate::Result<()> {
#[tracing::instrument]
pub async fn get_logs_by_file(
cluster_path: ClusterPath,
log_type: LogType,
log_file: String,
) -> crate::Result<LogManager> {
let cluster_path = if let Some(c) = crate::cluster::get(&cluster_path, None).await? {
c.cluster_path()
} else {
return Err(anyhow::anyhow!(
"cluster {} isn't managed by onelauncher!",
cluster_path.to_string()
)
.into());
};
let cluster_path = cluster_path.cluster_path().await?;
Ok(LogManager {
output: Some(get_output_by_file(&cluster_path, &log_file).await?),
log_file,
})
let path = match log_type {
LogType::Info => Directories::cluster_logs_dir(&cluster_path).await,
LogType::Crash => Directories::crash_reports_dir(&cluster_path).await,
}?
.join(&log_file);
let metadata = std::fs::metadata(&path)?;
let age = metadata
.created()
.unwrap_or_else(|_| SystemTime::UNIX_EPOCH);
LogManager::initialize(log_type, age, &cluster_path, log_file, Some(true)).await
}
/// Get the default [`LogCursor`] for a [`ClusterPath`].
@ -138,16 +162,7 @@ pub async fn get_live_log_cursor(
log_file: &str,
mut cursor: u64,
) -> crate::Result<LogCursor> {
let cluster_path = if let Some(c) = crate::cluster::get(&cluster_path, None).await? {
c.cluster_path()
} else {
return Err(anyhow::anyhow!(
"cluster {} isn't managed by onelauncher!",
cluster_path.to_string()
)
.into());
};
let cluster_path = cluster_path.cluster_path().await?;
let state = crate::State::get().await?;
let logs_folder = Directories::cluster_logs_dir(&cluster_path).await?;
let path = logs_folder.join(log_file);
@ -201,18 +216,17 @@ pub async fn get_live_log_cursor(
/// Delete a specific minecraft log file.
#[tracing::instrument]
pub async fn delete_logs_by_file(cluster_path: ClusterPath, log_file: &str) -> crate::Result<()> {
let cluster_path = if let Some(c) = crate::cluster::get(&cluster_path, None).await? {
c.cluster_path()
} else {
return Err(anyhow::anyhow!(
"cluster {} isn't managed by onelauncher!",
cluster_path.to_string()
)
.into());
pub async fn delete_logs_by_file(
cluster_path: ClusterPath,
log_type: LogType,
log_file: &str,
) -> crate::Result<()> {
let cluster_path = cluster_path.cluster_path().await?;
let logs_folder = match log_type {
LogType::Info => Directories::cluster_logs_dir(&cluster_path).await?,
LogType::Crash => Directories::crash_reports_dir(&cluster_path).await?,
};
let logs_folder = Directories::cluster_logs_dir(&cluster_path).await?;
let path = logs_folder.join(log_file);
io::remove_dir_all(&path).await?;
Ok(())
@ -222,10 +236,14 @@ pub async fn delete_logs_by_file(cluster_path: ClusterPath, log_file: &str) -> c
#[tracing::instrument]
pub async fn get_output_by_file(
cluster_path: &ClusterPath,
log_type: LogType,
log_file: &str,
) -> crate::Result<LogOutput> {
let state = crate::State::get().await?;
let logs_folder = Directories::cluster_logs_dir(cluster_path).await?;
let logs_folder = match log_type {
LogType::Info => Directories::cluster_logs_dir(cluster_path).await?,
LogType::Crash => Directories::crash_reports_dir(cluster_path).await?,
};
let path = logs_folder.join(log_file);
let credentials: Vec<MinecraftCredentials> = state
.users
@ -255,7 +273,7 @@ pub async fn get_output_by_file(
}
return Ok(LogOutput::censor_secrets(result, &credentials, None));
} else if ext == "log" {
} else if ext == "log" || ext == "txt" {
while file
.read(&mut contents)
.map_err(|e| IOError::with_path(e, &path))?

View file

@ -123,7 +123,8 @@ pub enum ErrorKind {
#[derive(Debug)]
pub struct Error {
source: tracing_error::TracedError<ErrorKind>,
pub raw: std::sync::Arc<ErrorKind>,
pub source: tracing_error::TracedError<std::sync::Arc<ErrorKind>>,
}
impl From<Error> for String {
@ -146,8 +147,12 @@ impl std::fmt::Display for Error {
impl<E: Into<ErrorKind>> From<E> for Error {
fn from(source: E) -> Self {
let error = Into::<ErrorKind>::into(source);
let boxed_error = std::sync::Arc::new(error);
Self {
source: Into::<ErrorKind>::into(source).in_current_span(),
raw: boxed_error.clone(),
source: boxed_error.in_current_span(),
}
}
}

View file

@ -67,6 +67,19 @@ impl ClusterPath {
Ok(self)
}
/// Validate the cluster and clone the current [`ClusterPath`].
pub async fn cluster_path(&self) -> crate::Result<ClusterPath> {
if let Some(c) = crate::cluster::get(&self, None).await? {
Ok(c.cluster_path())
} else {
Err(anyhow::anyhow!(
"failed to get path of unmanaged or corrupted cluster {}",
self.to_string()
)
.into())
}
}
/// Create a [`ClusterPath`] from a full [`PathBuf`].
pub async fn from_path(path: PathBuf) -> crate::Result<Self> {
let path: PathBuf = io::canonicalize(path)?;

View file

@ -140,6 +140,12 @@ impl Directories {
Ok(cluster_path.full_path().await?.join("logs"))
}
/// Get the `{cluster_path}/crash_reports` directory.
#[inline]
pub async fn crash_reports_dir(cluster_path: &super::ClusterPath) -> crate::Result<PathBuf> {
Ok(cluster_path.full_path().await?.join("crash-reports"))
}
/// Get the `config_dir/metadata/resources` directory.
#[inline]
pub async fn legacy_assets_dir(&self) -> PathBuf {

View file

@ -59,11 +59,15 @@ impl MinecraftState {
/// Refresh all tokens in the global state.
#[tracing::instrument(skip(self))]
async fn refresh(&mut self) -> crate::Result<(DeviceKey, DeviceToken)> {
async fn refresh(
&mut self,
current_date: DateTime<Utc>,
force: bool,
) -> crate::Result<(DeviceKey, DeviceToken, DateTime<Utc>, bool)> {
macro_rules! device_key {
($self:ident, $device_key:expr, $device_token:expr, $SaveDeviceToken:path) => {{
let key = device_key()?;
let token = device_token(&key).await?;
let res = device_token(&key, current_date).await?;
self.token = Some(SaveDeviceToken {
id: key.id.clone(),
@ -74,18 +78,17 @@ impl MinecraftState {
.to_string(),
x: key.x.clone(),
y: key.y.clone(),
token: token.clone(),
modern: true,
token: res.value.clone(),
});
self.save().await?;
(key, token)
(key, res.value, res.date, true)
}};
}
let (key, token) = if let Some(ref token) = self.token {
if token.token.not_after > Utc::now() {
if let Ok(private_key) = SigningKey::from_pkcs8_pem(&token.private_key) {
let (key, token, date, valid_date) = if let Some(ref token) = self.token {
if let Ok(private_key) = SigningKey::from_pkcs8_pem(&token.private_key) {
if token.token.not_after > Utc::now() && !force {
(
DeviceKey {
id: token.id.clone(),
@ -94,9 +97,20 @@ impl MinecraftState {
y: token.y.clone(),
},
token.token.clone(),
current_date,
false,
)
} else {
device_key!(self, device_key, device_token, SaveDeviceToken)
let key = DeviceKey {
id: token.id.clone(),
key: private_key,
x: token.x.clone(),
y: token.y.clone(),
};
let res = device_token(&key, current_date).await?;
(key, res.value, res.date, true)
}
} else {
device_key!(self, device_key, device_token, SaveDeviceToken)
@ -105,25 +119,48 @@ impl MinecraftState {
device_key!(self, device_key, device_token, SaveDeviceToken)
};
Ok((key, token))
Ok((key, token, date, valid_date))
}
/// Begin a Microsoft authentication flow.
#[tracing::instrument(skip(self))]
pub async fn begin(&mut self) -> crate::Result<MinecraftLogin> {
let (key, token) = self.refresh().await?;
let (key, token, current_date, valid_date) = self.refresh(Utc::now(), false).await?;
let verify = generate_oauth_challenge();
let mut hash = sha2::Sha256::new();
hash.update(&verify);
let hash_result = hash.finalize();
let challenge = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(&hash_result);
let (sid, ruri) = sisu_authenticate(&token.token, &challenge, &key).await?;
Ok(MinecraftLogin {
verify,
challenge,
session_id: sid,
redirect_uri: ruri.msa_oauth_redirect,
})
let challenge = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(hash_result);
match sisu_authenticate(&token.token, &challenge, &key, current_date).await {
Ok((session_id, redirect_uri)) => Ok(MinecraftLogin {
verify,
challenge,
session_id,
redirect_uri: redirect_uri.value.msa_oauth_redirect,
}),
Err(err) => {
if !valid_date {
let (key, token, current_date, _) = self.refresh(Utc::now(), false).await?;
let verify = generate_oauth_challenge();
let mut hash = sha2::Sha256::new();
hash.update(&verify);
let hash_result = hash.finalize();
let challenge = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(hash_result);
let (session_id, redirect_uri) =
sisu_authenticate(&token.token, &challenge, &key, current_date).await?;
Ok(MinecraftLogin {
verify,
challenge,
session_id,
redirect_uri: redirect_uri.value.msa_oauth_redirect,
})
} else {
Err(crate::ErrorKind::from(err).into())
}
}
}
}
/// Finish a Microsoft authentication flow.
@ -133,17 +170,24 @@ impl MinecraftState {
code: &str,
flow: MinecraftLogin,
) -> crate::Result<MinecraftCredentials> {
let (key, token) = self.refresh().await?;
let (key, token, _, _) = self.refresh(Utc::now(), false).await?;
let oauth_token = oauth_token(code, &flow.verify).await?;
let sisu_authorize = sisu_authorize(
Some(&flow.session_id),
&oauth_token.access_token,
&oauth_token.value.access_token,
&token.token,
&key,
oauth_token.date,
)
.await?;
let xbox_token = xsts_authorize(sisu_authorize, &token.token, &key).await?;
let minecraft_token = minecraft_token(xbox_token).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?;
minecraft_entitlements(&minecraft_token.access_token).await?;
let profile = minecraft_profile(&minecraft_token.access_token).await?;
let profile_id = profile.id.unwrap_or_default();
@ -151,9 +195,10 @@ impl MinecraftState {
id: profile_id,
username: profile.name,
access_token: minecraft_token.access_token,
refresh_token: oauth_token.refresh_token,
refresh_token: oauth_token.value.refresh_token,
#[allow(deprecated)]
expires: Utc::now() + chrono::TimeDelta::seconds(oauth_token.expires_in as i64),
expires: oauth_token.date
+ chrono::TimeDelta::seconds(oauth_token.value.expires_in as i64),
};
self.users.insert(profile_id, credentials.clone());
@ -167,6 +212,47 @@ impl MinecraftState {
Ok(credentials)
}
/// Refresh the current stored [`MinecraftCredentials`].
async fn refresh_token(
&mut self,
creds: &MinecraftCredentials,
) -> crate::Result<Option<MinecraftCredentials>> {
let cred_id = creds.id;
let cred_name = creds.username.clone();
let oauth_token = oauth_refresh(&creds.refresh_token).await?;
let (key, token, current_date, _) = self.refresh(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 = MinecraftCredentials {
id: cred_id,
username: cred_name,
access_token: minecraft_token.access_token,
refresh_token: oauth_token.value.refresh_token,
#[allow(deprecated)]
expires: oauth_token.date
+ chrono::TimeDelta::seconds(oauth_token.value.expires_in as i64),
};
self.users.insert(val.id, val.clone());
self.save().await?;
Ok(Some(val))
}
/// Get the default user account as a set of optional [`MinecraftCredentials`].
#[tracing::instrument(skip(self))]
pub async fn get_default(&mut self) -> crate::Result<Option<MinecraftCredentials>> {
@ -187,29 +273,25 @@ impl MinecraftState {
}
if creds.expires < Utc::now() {
let cred_id = creds.id;
let profile_name = creds.username.clone();
let old_creds = creds.clone();
let res = self.refresh_token(&old_creds).await;
let oauth_token = oauth_refresh(&creds.refresh_token).await?;
let (key, token) = self.refresh().await?;
let sisu_authorize =
sisu_authorize(None, &oauth_token.access_token, &token.token, &key).await?;
match res {
Ok(val) => Ok(val),
Err(err) => {
if let crate::ErrorKind::AuthError(MinecraftAuthError::RequestError {
ref source,
..
}) = *err.raw
{
if source.is_connect() || source.is_timeout() {
return Ok(Some(old_creds));
}
}
let xbox_token = xsts_authorize(sisu_authorize, &token.token, &key).await?;
let minecraft_token = minecraft_token(xbox_token).await?;
let val = MinecraftCredentials {
id: cred_id,
username: profile_name,
access_token: minecraft_token.access_token,
refresh_token: oauth_token.refresh_token,
#[allow(deprecated)]
expires: Utc::now() + chrono::TimeDelta::seconds(oauth_token.expires_in as i64),
};
self.users.insert(val.id, val.clone());
self.save().await?;
Ok(Some(val))
Err(err)
}
}
} else {
Ok(Some(creds.clone()))
}
@ -235,6 +317,12 @@ const REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf";
/// Microsoft login xboxlive scopes to get tokens.
const SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL";
/// A [`reqwest::Request`] with a [`DateTime<Utc>`] attached to it.
struct RequestWithDate<T> {
pub date: DateTime<Utc>,
pub value: T,
}
/// A device token used for Microsoft authentication.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "PascalCase")]
@ -275,9 +363,6 @@ pub struct SaveDeviceToken {
pub y: String,
/// The assocated [`DeviceToken`].
pub token: DeviceToken,
/// Whether or not we should patch the auth device token to use a modern auth flow
#[serde(default)]
modern: bool,
}
/// Core variables passed throughout the Minecraft login flow.
@ -308,8 +393,11 @@ pub struct MinecraftCredentials {
}
#[tracing::instrument(skip(key))]
pub async fn device_token(key: &DeviceKey) -> Result<DeviceToken, MinecraftAuthError> {
Ok(send_signed_request(
pub async fn device_token(
key: &DeviceKey,
current_date: DateTime<Utc>,
) -> Result<RequestWithDate<DeviceToken>, MinecraftAuthError> {
let res = send_signed_request(
None,
"https://device.auth.xboxlive.com/device/authenticate",
"/device/authenticate",
@ -333,9 +421,14 @@ pub async fn device_token(key: &DeviceKey) -> Result<DeviceToken, MinecraftAuthE
}),
key,
MinecraftAuthStep::DeviceToken,
current_date,
)
.await?
.1)
.await?;
Ok(RequestWithDate {
date: res.current_date,
value: res.body,
})
}
#[derive(Deserialize)]
@ -349,9 +442,10 @@ async fn sisu_authenticate(
token: &str,
challenge: &str,
key: &DeviceKey,
) -> Result<(String, RedirectUri), MinecraftAuthError> {
current_date: DateTime<Utc>,
) -> Result<(String, RequestWithDate<RedirectUri>), MinecraftAuthError> {
// what was microsoft on when writing this??????????
let (headers, res) = send_signed_request(
let res = send_signed_request::<RedirectUri>(
None,
"https://sisu.xboxlive.com/authenticate",
"/authenticate",
@ -372,16 +466,24 @@ async fn sisu_authenticate(
}),
key,
MinecraftAuthStep::SisuAuthenicate,
current_date,
)
.await?;
let session_id = headers
let session_id = res
.headers
.get("X-SessionId")
.and_then(|x| x.to_str().ok())
.ok_or_else(|| MinecraftAuthError::SessionIdError)?
.to_string();
Ok((session_id, res))
Ok((
session_id,
RequestWithDate {
date: res.current_date,
value: res.body,
},
))
}
#[derive(Deserialize)]
@ -396,11 +498,14 @@ struct OAuthToken {
}
#[tracing::instrument]
async fn oauth_token(code: &str, verify: &str) -> Result<OAuthToken, MinecraftAuthError> {
async fn oauth_token(
code: &str,
verify: &str,
) -> Result<RequestWithDate<OAuthToken>, MinecraftAuthError> {
let mut query = HashMap::new();
query.insert("client_id", MICROSOFT_CLIENT_ID);
query.insert("code", code);
query.insert("code_verifier", &*verify);
query.insert("code_verifier", verify);
query.insert("grant_type", "authorization_code");
query.insert("redirect_uri", REDIRECT_URL);
query.insert("scope", SCOPES);
@ -418,6 +523,7 @@ async fn oauth_token(code: &str, verify: &str) -> Result<OAuthToken, MinecraftAu
})?;
let status = res.status();
let current_date = get_date_header(res.headers());
let text = res
.text()
.await
@ -426,16 +532,24 @@ async fn oauth_token(code: &str, verify: &str) -> Result<OAuthToken, MinecraftAu
source,
})?;
serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError {
step: MinecraftAuthStep::OAuthToken,
raw: text,
source,
status_code: status,
let body =
serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError {
step: MinecraftAuthStep::OAuthToken,
raw: text,
source,
status_code: status,
})?;
Ok(RequestWithDate {
date: current_date,
value: body,
})
}
#[tracing::instrument]
async fn oauth_refresh(refresh_token: &str) -> Result<OAuthToken, MinecraftAuthError> {
async fn oauth_refresh(
refresh_token: &str,
) -> Result<RequestWithDate<OAuthToken>, MinecraftAuthError> {
let mut query = HashMap::new();
query.insert("client_id", MICROSOFT_CLIENT_ID);
query.insert("refresh_token", refresh_token);
@ -456,6 +570,7 @@ async fn oauth_refresh(refresh_token: &str) -> Result<OAuthToken, MinecraftAuthE
})?;
let status = res.status();
let current_date = get_date_header(res.headers());
let text = res
.text()
.await
@ -464,11 +579,17 @@ async fn oauth_refresh(refresh_token: &str) -> Result<OAuthToken, MinecraftAuthE
source,
})?;
serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError {
step: MinecraftAuthStep::RefreshOAuthToken,
raw: text,
source,
status_code: status,
let body =
serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError {
step: MinecraftAuthStep::RefreshOAuthToken,
raw: text,
source,
status_code: status,
})?;
Ok(RequestWithDate {
date: current_date,
value: body,
})
}
@ -489,8 +610,9 @@ async fn sisu_authorize(
access_token: &str,
device_token: &str,
key: &DeviceKey,
) -> Result<SisuAuthorize, MinecraftAuthError> {
Ok(send_signed_request(
current_date: DateTime<Utc>,
) -> Result<RequestWithDate<SisuAuthorize>, MinecraftAuthError> {
let res = send_signed_request(
None,
"https://sisu.xboxlive.com/authorize",
"/authorize",
@ -515,9 +637,14 @@ async fn sisu_authorize(
}),
key,
MinecraftAuthStep::SisuAuthorize,
current_date,
)
.await?
.1)
.await?;
Ok(RequestWithDate {
date: res.current_date,
value: res.body,
})
}
#[tracing::instrument(skip(key))]
@ -525,8 +652,9 @@ async fn xsts_authorize(
authorize: SisuAuthorize,
device_token: &str,
key: &DeviceKey,
) -> Result<DeviceToken, MinecraftAuthError> {
Ok(send_signed_request(
current_date: DateTime<Utc>,
) -> Result<RequestWithDate<DeviceToken>, MinecraftAuthError> {
let res = send_signed_request(
None,
"https://xsts.auth.xboxlive.com/xsts/authorize",
"/xsts/authorize",
@ -542,9 +670,14 @@ async fn xsts_authorize(
}),
key,
MinecraftAuthStep::XstsAuthorize,
current_date,
)
.await?
.1)
.await?;
Ok(RequestWithDate {
date: res.current_date,
value: res.body,
})
}
#[derive(Deserialize)]
@ -729,6 +862,12 @@ fn device_key() -> Result<DeviceKey, MinecraftAuthError> {
})
}
struct SignedRequestResponse<T> {
pub headers: reqwest::header::HeaderMap,
pub current_date: DateTime<Utc>,
pub body: T,
}
async fn send_signed_request<T: serde::de::DeserializeOwned>(
authorization: Option<&str>,
url: &str,
@ -736,13 +875,14 @@ async fn send_signed_request<T: serde::de::DeserializeOwned>(
raw_body: serde_json::Value,
key: &DeviceKey,
step: MinecraftAuthStep,
) -> Result<(reqwest::header::HeaderMap, T), MinecraftAuthError> {
current_date: DateTime<Utc>,
) -> Result<SignedRequestResponse<T>, MinecraftAuthError> {
let auth = authorization
.clone()
.map_or(Vec::new(), |v| v.as_bytes().to_vec());
let body = serde_json::to_vec(&raw_body)
.map_err(|source| MinecraftAuthError::SerializeError { step, source })?;
let time: u128 = { ((Utc::now().timestamp() as u128) + 11644473600) * 10000000 };
let time: u128 = { ((current_date.timestamp() as u128) + 11644473600) * 10000000 };
use byteorder::WriteBytesExt;
let mut buffer = Vec::new();
@ -811,6 +951,7 @@ async fn send_signed_request<T: serde::de::DeserializeOwned>(
let status = res.status();
let headers = res.headers().clone();
let current_date = get_date_header(&headers);
let body = res
.text()
.await
@ -823,7 +964,21 @@ async fn send_signed_request<T: serde::de::DeserializeOwned>(
source,
status_code: status,
})?;
Ok((headers, body))
Ok(SignedRequestResponse {
headers,
current_date,
body,
})
}
#[tracing::instrument]
fn get_date_header(headers: &reqwest::header::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]