From 03be1055d5d3951f2d9a0706582f87d00e27e860 Mon Sep 17 00:00:00 2001 From: Kyler <59854022+KylerOlsen@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:47:48 -0600 Subject: [PATCH] Added multiplayer authentication end points --- Cargo.toml | 3 ++ README.md | 4 +- src/accounts.rs | 4 +- src/lib.rs | 1 + src/multiplayer_auth.rs | 116 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/multiplayer_auth.rs diff --git a/Cargo.toml b/Cargo.toml index 60128d1..74b126a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" base64 = "0.21.5" +crypto = { version = "0.5.1", features = ["digest"] } +sha1 = "0.10.6" +num-bigint = "0.4.5" diff --git a/README.md b/README.md index a0edabc..403db2a 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ Purple Cello interface for the Mojang API in rust - [ ] [Connect Xbox Live](https://wiki.vg/Mojang_API#Connect_Xbox_Live) ### Multiplayer Authentication -- [ ] [Client Authentication](https://wiki.vg/Protocol_Encryption#Client) -- [ ] [Server Authentication](https://wiki.vg/Protocol_Encryption#Server) +- [x] [Client Authentication](https://wiki.vg/Protocol_Encryption#Client) +- [x] [Server Authentication](https://wiki.vg/Protocol_Encryption#Server) ### Game Files - [ ] [Game Versions](https://wiki.vg/Game_files#Game) diff --git a/src/accounts.rs b/src/accounts.rs index dd36ba2..ad3cfcb 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -80,7 +80,7 @@ pub struct ProfileProperty { } #[derive(Serialize, Deserialize)] -struct ProfilePropertyPrivate { +pub(crate) struct ProfilePropertyPrivate { pub name: String, pub value: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -124,7 +124,7 @@ struct UUIDToProfilePrivate { pub errorMessage: Option, } -fn get_profile_value(properties: Vec) +pub(crate) fn get_profile_value(properties: Vec) -> Result, Box> { let mut output: Vec = Vec::new(); diff --git a/src/lib.rs b/src/lib.rs index 5756482..e60a215 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ // Yeahbut January 2024 pub mod accounts; +pub mod multiplayer_auth; diff --git a/src/multiplayer_auth.rs b/src/multiplayer_auth.rs new file mode 100644 index 0000000..d34f3b2 --- /dev/null +++ b/src/multiplayer_auth.rs @@ -0,0 +1,116 @@ +// Yeahbut June 2024 + +#![allow(non_snake_case)] + +use std::fmt; +use reqwest; +use serde::{Serialize, Deserialize}; +use crypto::digest::Digest; +use sha1::Sha1; +use num_bigint::BigInt; + +use crate::accounts::{ + ProfileProperty, + ProfilePropertyPrivate, + get_profile_value, +}; + +#[derive(Debug)] +pub enum AuthError { + AuthError, +} + +impl fmt::Display for AuthError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AuthError::AuthError => write!(f, "AuthError"), + } + } +} +impl std::error::Error for AuthError {} + +pub async fn server_id_hash( + server_id: &[u8], + shared_secret: &[u8], + public_key: &[u8], +) -> String { + let hash_data = [server_id,shared_secret,public_key].concat(); + let hash = BigInt::from_signed_bytes_be( + &Sha1::digest(hash_data)).to_str_radix(16); + hash +} + +#[derive(Serialize, Deserialize)] +struct PlayerJoining { + pub accessToken: String, + pub selectedProfile: String, + pub serverId: String, +} + +pub async fn join( + accessToken: String, + selectedProfile: String, + serverId: String, +) -> Result> { + let url = "https://sessionserver.mojang.com/session/minecraft/join"; + + let resp = reqwest::Client::new() + .post(url) + .header("Content-Type", "application/json") + .json(&PlayerJoining {accessToken,selectedProfile,serverId}) + .send() + .await?; + + Ok(resp) +} + +#[derive(Serialize, Deserialize)] +pub struct JoinedPlayer { + pub id: String, + pub name: String, + pub properties: Vec, +} + +#[derive(Serialize, Deserialize)] +struct JoinedPlayerPrivate { + pub id: String, + pub name: String, + pub properties: Vec, +} + +pub async fn joined( + username: &str, + server_id: &str, + ip: Option<&str>, +) -> Result> { + let url = match ip { + Some(ip) => format!( + "https://sessionserver.mojang.com/session/minecraft/hasJoined?\ + username={}&serverId={}&ip={}", + username, + server_id, + ip, + ), + None => format!( + "https://sessionserver.mojang.com/session/minecraft/hasJoined?\ + username={}&serverId={}", + username, + server_id, + ) + }; + + let resp = reqwest::get(url) + .await?; + + if resp.status() == 200 { + let data = resp.json::().await?; + Ok(JoinedPlayer { + id: data.id, + name: data.name, + properties: get_profile_value(data.properties)? + }) + } else { + Err(Box::new(AuthError::AuthError)) + } + +}