1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

A couple useful things for servers

This commit is contained in:
EightFactorial 2023-01-17 15:04:06 -08:00
parent c6b5664972
commit 798cfc233e
4 changed files with 251 additions and 1 deletions

View file

@ -19,7 +19,7 @@ serde = {version = "1.0.145", features = ["derive"]}
serde_json = "1.0.86"
thiserror = "1.0.37"
tokio = {version = "1.23.1", features = ["fs"]}
uuid = "^1.1.2"
uuid = {version = "^1.1.2", features = ["serde"]}
[dev-dependencies]
env_logger = "0.9.1"

View file

@ -1,4 +1,5 @@
use azalea_buf::McBuf;
use serde::{de::Visitor, ser::SerializeStruct, Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
@ -24,3 +25,146 @@ pub struct ProfilePropertyValue {
pub value: String,
pub signature: Option<String>,
}
impl Serialize for GameProfile {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut serializer = serializer.serialize_struct("GameProfile", 3)?;
serializer.serialize_field("id", &self.uuid)?;
serializer.serialize_field("name", &self.name)?;
serializer.serialize_field(
"properties",
&SerializedProfilePropertyValue::from_map(self.properties.clone()),
)?;
serializer.end()
}
}
impl<'de> Deserialize<'de> for GameProfile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Id,
Name,
Properties,
}
struct GameProfileVisitor;
impl<'de> Visitor<'de> for GameProfileVisitor {
type Value = GameProfile;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("struct GameProfile")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let id = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let name = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
let properties = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;
Ok(GameProfile {
uuid: id,
name,
properties: SerializedProfilePropertyValue::into_map(properties),
})
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut id = None;
let mut name = None;
let mut properties = None;
while let Some(key) = map.next_key::<Field>()? {
match key {
Field::Id => {
if id.is_some() {
return Err(serde::de::Error::duplicate_field("id"));
}
id = Some(map.next_value()?);
}
Field::Name => {
if name.is_some() {
return Err(serde::de::Error::duplicate_field("name"));
}
name = Some(map.next_value()?);
}
Field::Properties => {
if properties.is_some() {
return Err(serde::de::Error::duplicate_field("properties"));
}
properties = Some(map.next_value()?);
}
}
}
let id = id.ok_or_else(|| serde::de::Error::missing_field("id"))?;
let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
let properties = SerializedProfilePropertyValue::into_map(
properties.ok_or_else(|| serde::de::Error::missing_field("properties"))?,
);
Ok(GameProfile {
uuid: id,
name,
properties,
})
}
}
const FIELDS: &'static [&'static str] = &["id", "name", "properties"];
deserializer.deserialize_struct("GameProfile", FIELDS, GameProfileVisitor)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SerializedProfilePropertyValue {
pub name: String,
pub value: String,
pub signature: Option<String>,
}
impl SerializedProfilePropertyValue {
pub fn from_map(
map: HashMap<String, ProfilePropertyValue>,
) -> Vec<SerializedProfilePropertyValue> {
let mut list: Vec<SerializedProfilePropertyValue> = Vec::new();
for (key, value) in map {
list.push(SerializedProfilePropertyValue {
name: key,
value: value.value,
signature: value.signature,
});
}
list
}
pub fn into_map(
list: Vec<SerializedProfilePropertyValue>,
) -> HashMap<String, ProfilePropertyValue> {
let mut map: HashMap<String, ProfilePropertyValue> = HashMap::new();
for value in list {
map.insert(
value.name,
ProfilePropertyValue {
value: value.value,
signature: value.signature,
},
);
}
map
}
}

View file

@ -4,6 +4,8 @@ use serde_json::json;
use thiserror::Error;
use uuid::Uuid;
use crate::game_profile::GameProfile;
#[derive(Debug, Error)]
pub enum SessionServerError {
#[error("Error sending HTTP request to sessionserver: {0}")]
@ -88,3 +90,49 @@ pub async fn join(
}
}
}
/// Ask Mojang's servers if the player joining is authenticated.
/// Included in the reply is the player's skin and cape.
/// The IP field is optional and equivalent to enabling
/// 'prevent-proxy-connections' in server.properties
pub async fn serverside_auth(
username: &String,
public_key: &[u8],
private_key: &[u8; 16],
ip: Option<&String>,
) -> Result<GameProfile, SessionServerError> {
let hash = azalea_crypto::hex_digest(&azalea_crypto::digest_data(
"".as_bytes(),
public_key,
private_key,
));
let mut url = format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={hash}");
if let Some(ip) = ip {
url = format!("{url}&ip={ip}");
}
let res = reqwest::get(url).await?;
match res.status() {
reqwest::StatusCode::OK => {}
reqwest::StatusCode::NO_CONTENT => {
return Err(SessionServerError::InvalidSession);
}
reqwest::StatusCode::FORBIDDEN => {
return Err(SessionServerError::Unknown(
res.json::<ForbiddenError>().await?.error,
))
}
status_code => {
// log the headers
log::debug!("Error headers: {:#?}", res.headers());
let body = res.text().await?;
return Err(SessionServerError::UnexpectedResponse {
status_code: status_code.as_u16(),
body,
});
}
};
Ok(res.json::<GameProfile>().await?)
}

View file

@ -8,6 +8,7 @@ use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
use crate::packets::ProtocolPacket;
use crate::read::{read_packet, ReadPacketError};
use crate::write::write_packet;
use azalea_auth::game_profile::GameProfile;
use azalea_auth::sessionserver::SessionServerError;
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
use bytes::BytesMut;
@ -339,6 +340,63 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
}
}
impl Connection<ServerboundHandshakePacket, ClientboundHandshakePacket> {
/// Change our state from handshake to login. This is the state that is used
/// for logging in.
pub fn login(self) -> Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
Connection::from(self)
}
/// Change our state from handshake to status. This is the state that is
/// used for pinging the server.
pub fn status(self) -> Connection<ServerboundStatusPacket, ClientboundStatusPacket> {
Connection::from(self)
}
}
impl Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
// /// Set our compression threshold, i.e. the maximum size that a packet is
// /// allowed to be without getting compressed. If you set it to less than 0
// /// then compression gets disabled.
// pub fn set_compression_threshold(&mut self, threshold: i32) {
// // if you pass a threshold of less than 0, compression is disabled
// if threshold >= 0 {
// self.reader.compression_threshold = Some(threshold as u32);
// self.writer.compression_threshold = Some(threshold as u32);
// } else {
// self.reader.compression_threshold = None;
// self.writer.compression_threshold = None;
// }
// }
/// Set the encryption key that is used to encrypt and decrypt packets. It's
/// the same for both reading and writing.
pub fn set_encryption_key(&mut self, key: [u8; 16]) {
let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key);
self.reader.dec_cipher = Some(dec_cipher);
self.writer.enc_cipher = Some(enc_cipher);
}
/// Change our state from login to game. This is the state that's used when
/// the client is actually in the game.
pub fn game(self) -> Connection<ServerboundGamePacket, ClientboundGamePacket> {
Connection::from(self)
}
/// Verify connecting clients have authenticated with Minecraft's servers.
/// This must happen after the client sends a `ServerboundLoginPacket::Key`
/// packet.
pub async fn authenticate(
&self,
username: &String,
public_key: &[u8],
private_key: &[u8; 16],
ip: Option<&String>,
) -> Result<GameProfile, SessionServerError> {
azalea_auth::sessionserver::serverside_auth(username, public_key, private_key, ip).await
}
}
// rust doesn't let us implement From because allegedly it conflicts with
// `core`'s "impl<T> From<T> for T" so we do this instead
impl<R1, W1> Connection<R1, W1>