summaryrefslogtreecommitdiffstats
path: root/src/auth/mcservices.rs
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-30 23:21:10 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-30 23:21:10 -0600
commiteb75f8512cddac27fb1e0a8aa678ba058862568d (patch)
tree9fcfee3089495f6eccf80bf288a14d4586eb6540 /src/auth/mcservices.rs
parentnon-working: auth (diff)
user auth
Diffstat (limited to 'src/auth/mcservices.rs')
-rw-r--r--src/auth/mcservices.rs92
1 files changed, 92 insertions, 0 deletions
diff --git a/src/auth/mcservices.rs b/src/auth/mcservices.rs
new file mode 100644
index 0000000..4305363
--- /dev/null
+++ b/src/auth/mcservices.rs
@@ -0,0 +1,92 @@
+use std::time::{Duration, SystemTime};
+use chrono::{DateTime, Utc};
+use reqwest::{IntoUrl, Method, RequestBuilder};
+use serde::{Deserialize, Serialize};
+use uuid::Uuid;
+use super::{AuthError, MinecraftPlayerInfo, PlayerProfile};
+use super::types::Token;
+
+const MINECRAFT_LOGIN: &str = "https://api.minecraftservices.com/authentication/login_with_xbox";
+const MINECRAFT_ENTITLEMENTS: &str = "https://api.minecraftservices.com/entitlements";
+const MINECRAFT_PROFILE: &str = "https://api.minecraftservices.com/minecraft/profile";
+
+const MINECRAFT_SESSION_PROFILE: &str = "https://sessionserver.mojang.com/session/minecraft/profile/";
+
+fn build_authenticated(client: &reqwest::Client, url: impl IntoUrl, method: Method, mc_token: &str) -> RequestBuilder {
+ super::build_json_request(client, url, method)
+ .bearer_auth(mc_token)
+}
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct MinecraftXboxLoginRequest<'a> {
+ identity_token: &'a str,
+ ensure_legacy_enabled: bool
+}
+
+#[derive(Deserialize, Debug)]
+struct MinecraftXboxLoginResponse {
+ access_token: String,
+ expires_in: u64
+}
+
+pub async fn login_with_xbox(client: &reqwest::Client, xsts_token: &str, user_hash: &str) -> Result<Token, AuthError> {
+ let tok = format!("XBL3.0 x={user_hash};{xsts_token}");
+ let req = MinecraftXboxLoginRequest {
+ identity_token: tok.as_str(),
+ ensure_legacy_enabled: true
+ };
+
+ let res: MinecraftXboxLoginResponse = super::build_json_request(client, MINECRAFT_LOGIN, Method::POST)
+ .json(&req).send().await
+ .and_then(|r| r.error_for_status())
+ .map_err(|e| AuthError::Request { what: "minecraft xbox login", error: e })?
+ .json().await
+ .map_err(|e| AuthError::Request { what: "minecraft xbox login (decode)", error: e })?;
+
+ let now: DateTime<Utc> = SystemTime::now().into();
+
+ Ok(Token {
+ value: res.access_token,
+ expire: Some(now + Duration::from_secs(res.expires_in))
+ })
+}
+
+#[derive(Deserialize, Debug)]
+struct EntitlementItem {
+ name: String
+ // we don't care about the signature
+}
+
+#[derive(Deserialize, Debug)]
+struct EntitlementResponse {
+ #[serde(default)]
+ items: Vec<EntitlementItem>
+}
+
+pub async fn owns_the_game(client: &reqwest::Client, token: &str) -> Result<bool, AuthError> {
+ let res: EntitlementResponse = build_authenticated(client, MINECRAFT_ENTITLEMENTS, Method::GET, token)
+ .send().await
+ .and_then(|r| r.error_for_status())
+ .map_err(|e| AuthError::Request { what: "entitlements", error: e })?
+ .json().await
+ .map_err(|e| AuthError::Request { what: "entitlements (receive)", error: e})?;
+
+ Ok(res.items.iter().filter(|i| i.name == "game_minecraft" || i.name == "product_minecraft").next().is_some())
+}
+
+pub async fn get_player_info(client: &reqwest::Client, token: &str) -> Result<MinecraftPlayerInfo, AuthError> {
+ build_authenticated(client, MINECRAFT_PROFILE, Method::GET, token)
+ .send().await
+ .and_then(|r| r.error_for_status())
+ .map_err(|e| AuthError::Request { what: "player info", error: e })?
+ .json().await
+ .map_err(|e| AuthError::Request { what: "player info (receive)", error: e })
+}
+
+pub async fn get_player_profile(client: &reqwest::Client, uuid: Uuid) -> Result<PlayerProfile, reqwest::Error> {
+ super::build_json_request(client, format!("{}{}?unsigned=false", MINECRAFT_SESSION_PROFILE, uuid.as_simple()), Method::GET)
+ .send().await
+ .and_then(|r| r.error_for_status())?
+ .json().await
+}