diff options
Diffstat (limited to 'src/auth')
| -rw-r--r-- | src/auth/mcservices.rs | 92 | ||||
| -rw-r--r-- | src/auth/msa.rs | 170 | ||||
| -rw-r--r-- | src/auth/types.rs | 130 | ||||
| -rw-r--r-- | src/auth/types/property_map.rs | 61 |
4 files changed, 0 insertions, 453 deletions
diff --git a/src/auth/mcservices.rs b/src/auth/mcservices.rs deleted file mode 100644 index 45ef795..0000000 --- a/src/auth/mcservices.rs +++ /dev/null @@ -1,92 +0,0 @@ -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().any(|i| i.name == "game_minecraft" || i.name == "product_minecraft")) -} - -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 -} diff --git a/src/auth/msa.rs b/src/auth/msa.rs deleted file mode 100644 index add345c..0000000 --- a/src/auth/msa.rs +++ /dev/null @@ -1,170 +0,0 @@ -use std::borrow::Cow; -use std::collections::HashMap; -use chrono::{DateTime, Utc}; -use log::debug; -use oauth2::AccessToken; -use reqwest::{Method}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use crate::auth::AuthError; -use crate::auth::types::Token; - -const XBOX_LIVE_AUTH: &str = "https://user.auth.xboxlive.com/user/authenticate"; -const XBOX_LIVE_XSTS: &str = "https://xsts.auth.xboxlive.com/xsts/authorize"; - -#[derive(Debug, Serialize)] -#[serde(rename_all = "PascalCase")] -struct XboxLiveAuthRequestProperties<'a> { - auth_method: &'a str, - site_name: &'a str, - rps_ticket: &'a str -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "PascalCase")] -struct XboxLiveAuthRequest<'a> { - properties: XboxLiveAuthRequestProperties<'a>, - relying_party: &'a str, - token_type: &'a str -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "PascalCase")] -struct XboxLiveAuthResponse { - token: String, - not_after: DateTime<Utc> -} - -pub async fn xbox_live_login(client: &reqwest::Client, access_token: &AccessToken, azure: bool) -> Result<Token, AuthError> { - debug!("MSA performing xbox live login ({azure})"); - - let ticket = match azure { - true => Cow::Owned(format!("d={}", access_token.secret())), - _ => Cow::Borrowed(access_token.secret().as_str()) - }; - - let request = XboxLiveAuthRequest { - properties: XboxLiveAuthRequestProperties { - auth_method: "RPS", - site_name: "user.auth.xboxlive.com", - rps_ticket: ticket.as_ref() - }, - relying_party: "http://auth.xboxlive.com", - token_type: "JWT" - }; - - let res: XboxLiveAuthResponse = super::build_json_request(client, XBOX_LIVE_AUTH, Method::POST).json(&request).send().await - .and_then(|r| r.error_for_status()) - .map_err(|e| AuthError::Request { what: "xbox live auth", error: e })? - .json().await.map_err(|e| AuthError::Request { what: "xbox live auth (decode)", error: e })?; - - Ok(Token { - value: res.token, - expire: Some(res.not_after) - }) -} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "PascalCase")] -struct XSTSAuthRequest<'a> { - properties: XSTSAuthRequestProperties<'a>, - relying_party: &'a str, - token_type: &'a str -} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "PascalCase")] -struct XSTSAuthRequestProperties<'a> { - sandbox_id: &'a str, - user_tokens: &'a[&'a str] -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -pub(super) struct XSTSAuthSuccessResponse { - token: String, - #[serde(default)] - display_claims: XSTSAuthResponseDisplayClaims -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -pub(super) struct XSTSAuthErrorResponse { - x_err: u64, - message: Option<String> -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "PascalCase", untagged)] -pub(super) enum XSTSAuthResponse { - Success(XSTSAuthSuccessResponse), - Error(XSTSAuthErrorResponse) -} - -#[derive(Deserialize, Debug, Default)] -pub(super) struct XSTSAuthResponseDisplayClaims { - xui: Vec<HashMap<String, String>> -} - -impl XSTSAuthSuccessResponse { - pub(super) fn into_token(self) -> String { - self.token - } - - fn get_display_claim(&self, name: &str) -> Option<&str> { - self.display_claims.xui.iter().find(|m| m.contains_key(name)).and_then(|f| f.get(name).map(|s| s.as_str())) - } - - pub(super) fn get_user_hash(&self) -> Option<&str> { - self.get_display_claim("uhs") - } - - pub(super) fn get_xuid(&self) -> Option<&str> { - self.get_display_claim("xid") - } - - pub(super) fn get_gamertag(&self) -> Option<&str> { - self.get_display_claim("gtg") - } -} - -#[allow(clippy::from_over_into)] -impl Into<AuthError> for XSTSAuthErrorResponse { - fn into(self) -> AuthError { - AuthError::AuthXError { - // some well-known error values - what: match self.x_err { - 2148916238u64 => "Microsoft account held by a minor outside of a family.", - 2148916233u64 => "Account is not on Xbox.", - _ => "Unknown error." - }, - x_error: self.x_err, - message: self.message - } - } -} - -pub(super) const XSTS_RP_MINECRAFT_SERVICES: &str = "rp://api.minecraftservices.com/"; -pub(super) const XSTS_RP_XBOX_LIVE: &str = "http://xboxlive.com"; - -pub async fn xsts_request(client: &reqwest::Client, xbl_token: &str, relying_party: &str) -> Result<XSTSAuthResponse, AuthError> { - debug!("Performing XSTS auth {relying_party}"); - - let token_array = [xbl_token]; - let req = XSTSAuthRequest { - properties: XSTSAuthRequestProperties { - sandbox_id: "RETAIL", - user_tokens: token_array.as_slice() - }, - relying_party, - token_type: "JWT" - }; - - let res: XSTSAuthResponse = super::build_json_request(client, XBOX_LIVE_XSTS, Method::POST).json(&req).send().await - .and_then(|r| r.error_for_status()) - .map_err(|e| AuthError::Request { what: "xsts", error: e })? - .json().await - .map_err(|e| AuthError::Request { what: "xsts (decode)", error: e })?; - - Ok(res) -} diff --git a/src/auth/types.rs b/src/auth/types.rs deleted file mode 100644 index b9cdaad..0000000 --- a/src/auth/types.rs +++ /dev/null @@ -1,130 +0,0 @@ -pub mod property_map; -pub use property_map::PropertyMap; - -use std::fmt::{Debug, Formatter}; -use chrono::{DateTime, Utc}; -use multimap::MultiMap; -use oauth2::RefreshToken; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Property { - pub name: String, - pub value: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub signature: Option<String> -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct PlayerProfile { - #[serde(with = "uuid::serde::simple")] - pub id: Uuid, - pub name: String, - - #[serde(default, skip_serializing_if = "MultiMap::is_empty", with = "property_map")] - pub properties: PropertyMap -} - -#[derive(Serialize, Deserialize)] -pub(super) struct Token { - pub value: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub expire: Option<DateTime<Utc>> -} - -struct RedactedValue; -impl Debug for RedactedValue { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("[redacted]") - } -} - -impl Debug for Token { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Token") - .field("value", &RedactedValue) - .field("expire", &self.expire) - .finish() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum SkinState { - Active, - Inactive -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum SkinVariant { - Classic, - Slim -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SkinInfo { - pub id: Uuid, - pub state: SkinState, - pub url: String, - pub texture_key: Option<String>, - pub variant: Option<SkinVariant>, - pub alias: Option<String> -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MinecraftPlayerInfo { - #[serde(with = "uuid::serde::simple")] - pub id: Uuid, - pub name: String, - - #[serde(default)] - pub skins: Vec<SkinInfo>, - #[serde(default)] - pub capes: Vec<SkinInfo>, - - #[serde(default)] - pub demo: bool, - - #[serde(default)] - pub legacy: bool, - - // todo: profile actions (idk the format) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct MsaUser { - #[serde(skip_serializing_if = "Option::is_none")] - pub player_profile: Option<PlayerProfile>, - pub xuid: Option<String>, - pub gamertag: Option<String>, - - #[serde(skip)] // this information is transient - pub player_info: Option<MinecraftPlayerInfo>, - - pub(super) client_id: oauth2::ClientId, - - #[serde(default, skip_serializing_if = "std::ops::Not::not")] - pub(super) is_azure_client_id: bool, - - pub(super) mc_token: Option<Token>, - pub(super) xbl_token: Option<Token>, - pub(super) refresh_token: Option<RefreshToken> -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum User { - Dummy(PlayerProfile), - MSA(Box<MsaUser>) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AuthenticationDatabase { - pub users: Vec<User> -} diff --git a/src/auth/types/property_map.rs b/src/auth/types/property_map.rs deleted file mode 100644 index ddfc9ce..0000000 --- a/src/auth/types/property_map.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::fmt::Formatter; -use multimap::MultiMap; -use serde::de::{SeqAccess, Visitor}; -use serde::{Deserializer, Serializer}; -use crate::auth::Property; - -pub type PropertyMap = MultiMap<String, Property>; - -pub mod legacy { - use serde::Serializer; - use super::PropertyMap; - - pub fn serialize<S>(value: &PropertyMap, serializer: S) -> Result<S::Ok, S::Error> - where S: Serializer - { - serializer.collect_map(value.iter_all() - .filter_map(|(k, v)| { - if v.is_empty() { - None - } else { - Some((k, v.iter().map(|p| &p.value).collect::<Vec<_>>())) - } - })) - } -} - -pub fn serialize<S>(value: &PropertyMap, serializer: S) -> Result<S::Ok, S::Error> -where - S: Serializer -{ - serializer.collect_seq(value.flat_iter().map(|(_, v)| v)) -} - -pub fn deserialize<'de, D>(deserializer: D) -> Result<PropertyMap, D::Error> -where - D: Deserializer<'de> -{ - struct PropertyMapVisitor; - - impl<'de> Visitor<'de> for PropertyMapVisitor { - type Value = PropertyMap; - - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - formatter.write_str("a property map") - } - - fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> - where - A: SeqAccess<'de>, - { - let mut map = MultiMap::new() as PropertyMap; - while let Some(prop) = seq.next_element::<Property>()? { - map.insert(prop.name.clone(), prop); - } - - Ok(map) - } - } - - deserializer.deserialize_seq(PropertyMapVisitor) -} |
