mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 23:44:38 +00:00
add auth in azalea-protocol/client
This commit is contained in:
parent
9dd452b07c
commit
cb001fa341
10 changed files with 463 additions and 25 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -124,6 +124,7 @@ dependencies = [
|
|||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
|
|
@ -18,6 +18,7 @@ serde_json = "1.0.86"
|
|||
tokio = "1.21.2"
|
||||
uuid = "^1.1.2"
|
||||
azalea-crypto = { path = "../azalea-crypto", version = "^0.1.0" }
|
||||
thiserror = "1.0.37"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.21.2", features = ["full"] }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#[tokio::main]
|
||||
async fn main() {
|
||||
let auth_result = azalea_auth::auth(None).await.unwrap();
|
||||
let auth_result = azalea_auth::auth(azalea_auth::AuthOpts::default())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("{:?}", auth_result);
|
||||
}
|
||||
|
|
13
azalea-auth/src/cache.rs
Normal file
13
azalea-auth/src/cache.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
//! Cache auth information
|
||||
|
||||
// pub fn get_auth_token() -> Option<AccessTokenResponse> {
|
||||
// let mut cache = CACHE.lock().unwrap();
|
||||
// if cache.auth_token.is_none() {
|
||||
// return None;
|
||||
// }
|
||||
// let auth_token = cache.auth_token.as_ref().unwrap();
|
||||
// if auth_token.expires_in < SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() {
|
||||
// return None;
|
||||
// }
|
||||
// Some(auth_token.clone())
|
||||
// }
|
|
@ -1,3 +1,301 @@
|
|||
pub mod auth;
|
||||
pub mod game_profile;
|
||||
pub mod sessionserver;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AuthOpts {
|
||||
/// Whether we should check if the user actually owns the game. This will
|
||||
/// fail if the user has Xbox Game Pass! Note that this isn't really
|
||||
/// necessary, since getting the user profile will check this anyways.
|
||||
pub check_ownership: bool,
|
||||
// /// Whether we should get the Minecraft profile data (i.e. username, uuid,
|
||||
// /// skin, etc) for the player.
|
||||
// pub get_profile: bool,
|
||||
}
|
||||
|
||||
/// Ask the user to authenticate with Microsoft and return the user's Minecraft
|
||||
/// access token. There's caching, so if the user has already authenticated
|
||||
/// before then they won't have to log in manually again.
|
||||
pub async fn auth(opts: AuthOpts) -> anyhow::Result<String> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let auth_token_res = interactive_get_ms_auth_token(&client).await?;
|
||||
// TODO: cache this
|
||||
println!("Got access token: {}", auth_token_res.access_token);
|
||||
|
||||
let xbl_auth = auth_with_xbox_live(&client, &auth_token_res.access_token).await?;
|
||||
|
||||
let xsts_token = obtain_xsts_for_minecraft(&client, &xbl_auth).await?;
|
||||
|
||||
let minecraft_access_token =
|
||||
auth_with_minecraft(&client, &xbl_auth.user_hash, &xsts_token).await?;
|
||||
|
||||
if opts.check_ownership {
|
||||
let has_game = check_ownership(&client, &minecraft_access_token).await?;
|
||||
if !has_game {
|
||||
panic!(
|
||||
"The Minecraft API is indicating that you don't own the game. \
|
||||
If you're using Xbox Game Pass, set `check_ownership` to false in the auth options."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// let profile = get_profile(&client, &minecraft_access_token).await?;
|
||||
|
||||
Ok(minecraft_access_token)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DeviceCodeResponse {
|
||||
user_code: String,
|
||||
device_code: String,
|
||||
verification_uri: String,
|
||||
expires_in: u64,
|
||||
interval: u64,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AccessTokenResponse {
|
||||
token_type: String,
|
||||
expires_in: u64,
|
||||
scope: String,
|
||||
access_token: String,
|
||||
refresh_token: String,
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct XboxLiveAuthResponse {
|
||||
issue_instant: String,
|
||||
not_after: String,
|
||||
token: String,
|
||||
display_claims: HashMap<String, Vec<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
/// Just the important data
|
||||
pub struct XboxLiveAuth {
|
||||
token: String,
|
||||
user_hash: String,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MinecraftAuthResponse {
|
||||
username: String,
|
||||
roles: Vec<String>,
|
||||
access_token: String,
|
||||
token_type: String,
|
||||
expires_in: u64,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GameOwnershipResponse {
|
||||
items: Vec<GameOwnershipItem>,
|
||||
signature: String,
|
||||
key_id: String,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GameOwnershipItem {
|
||||
name: String,
|
||||
signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProfileResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub skins: Vec<serde_json::Value>,
|
||||
pub capes: Vec<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Asks the user to go to a webpage and log in with Microsoft.
|
||||
async fn interactive_get_ms_auth_token(
|
||||
client: &reqwest::Client,
|
||||
) -> anyhow::Result<AccessTokenResponse> {
|
||||
// nintendo switch (real)
|
||||
let client_id = "00000000441cc96b";
|
||||
|
||||
let res = client
|
||||
.post("https://login.live.com/oauth20_connect.srf")
|
||||
.form(&vec![
|
||||
("scope", "service::user.auth.xboxlive.com::MBI_SSL"),
|
||||
("client_id", client_id),
|
||||
("response_type", "device_code"),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.json::<DeviceCodeResponse>()
|
||||
.await?;
|
||||
println!("{:?}", res);
|
||||
println!(
|
||||
"Go to {} and enter the code {}",
|
||||
res.verification_uri, res.user_code
|
||||
);
|
||||
|
||||
let access_token_response: AccessTokenResponse;
|
||||
|
||||
let expire_time = Instant::now() + std::time::Duration::from_secs(res.expires_in);
|
||||
|
||||
while Instant::now() < expire_time {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(res.interval)).await;
|
||||
|
||||
println!("trying");
|
||||
if let Ok(res) = client
|
||||
.post(format!(
|
||||
"https://login.live.com/oauth20_token.srf?client_id={}",
|
||||
client_id
|
||||
))
|
||||
.form(&vec![
|
||||
("client_id", client_id),
|
||||
("device_code", &res.device_code),
|
||||
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
|
||||
])
|
||||
.send()
|
||||
.await?
|
||||
.json::<AccessTokenResponse>()
|
||||
.await
|
||||
{
|
||||
access_token_response = res;
|
||||
return Ok(access_token_response);
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("Authentication timed out"))
|
||||
}
|
||||
|
||||
async fn auth_with_xbox_live(
|
||||
client: &reqwest::Client,
|
||||
access_token: &str,
|
||||
) -> anyhow::Result<XboxLiveAuth> {
|
||||
let auth_json = json!({
|
||||
"Properties": {
|
||||
"AuthMethod": "RPS",
|
||||
"SiteName": "user.auth.xboxlive.com",
|
||||
// i thought this was supposed to be d={} but it doesn't work for
|
||||
// me when i add it ??????
|
||||
"RpsTicket": format!("{}", access_token)
|
||||
},
|
||||
"RelyingParty": "http://auth.xboxlive.com",
|
||||
"TokenType": "JWT"
|
||||
});
|
||||
let payload = auth_json.to_string();
|
||||
// let signature = sign(
|
||||
// "https://user.auth.xboxlive.com/user/authenticate",
|
||||
// "",
|
||||
// &payload,
|
||||
// )?;
|
||||
println!("auth_json: {:#?}", auth_json);
|
||||
let res = client
|
||||
.post("https://user.auth.xboxlive.com/user/authenticate")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.header("x-xbl-contract-version", "1")
|
||||
// .header("Cache-Control", "no-store, must-revalidate, no-cache")
|
||||
// .header("Signature", base64::encode(signature))
|
||||
.body(payload)
|
||||
.send()
|
||||
.await?
|
||||
.json::<XboxLiveAuthResponse>()
|
||||
.await?;
|
||||
println!("got res: {:?}", res);
|
||||
|
||||
Ok(XboxLiveAuth {
|
||||
token: res.token,
|
||||
user_hash: res.display_claims["xui"].get(0).unwrap()["uhs"].clone(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn obtain_xsts_for_minecraft(
|
||||
client: &reqwest::Client,
|
||||
xbl_auth: &XboxLiveAuth,
|
||||
) -> anyhow::Result<String> {
|
||||
let res = client
|
||||
.post("https://xsts.auth.xboxlive.com/xsts/authorize")
|
||||
.header("Accept", "application/json")
|
||||
.json(&json!({
|
||||
"Properties": {
|
||||
"SandboxId": "RETAIL",
|
||||
"UserTokens": [xbl_auth.token]
|
||||
},
|
||||
"RelyingParty": "rp://api.minecraftservices.com/",
|
||||
"TokenType": "JWT"
|
||||
}))
|
||||
.send()
|
||||
.await?
|
||||
.json::<XboxLiveAuthResponse>()
|
||||
.await?;
|
||||
println!("{:?}", res);
|
||||
|
||||
Ok(res.token)
|
||||
}
|
||||
|
||||
async fn auth_with_minecraft(
|
||||
client: &reqwest::Client,
|
||||
user_hash: &str,
|
||||
xsts_token: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
let res = client
|
||||
.post("https://api.minecraftservices.com/authentication/login_with_xbox")
|
||||
.header("Accept", "application/json")
|
||||
.json(&json!({
|
||||
"identityToken": format!("XBL3.0 x={};{}", user_hash, xsts_token)
|
||||
}))
|
||||
.send()
|
||||
.await?
|
||||
.json::<MinecraftAuthResponse>()
|
||||
.await?;
|
||||
println!("{:?}", res);
|
||||
|
||||
Ok(res.access_token)
|
||||
}
|
||||
|
||||
async fn check_ownership(
|
||||
client: &reqwest::Client,
|
||||
minecraft_access_token: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
let res = client
|
||||
.get("https://api.minecraftservices.com/entitlements/mcstore")
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Bearer {}", minecraft_access_token),
|
||||
)
|
||||
.send()
|
||||
.await?
|
||||
.json::<GameOwnershipResponse>()
|
||||
.await?;
|
||||
println!("{:?}", res);
|
||||
|
||||
// TODO: we *should* check with mojang's public key that the signatures are right
|
||||
|
||||
Ok(!res.items.is_empty())
|
||||
}
|
||||
|
||||
async fn get_profile(
|
||||
client: &reqwest::Client,
|
||||
minecraft_access_token: &str,
|
||||
) -> anyhow::Result<ProfileResponse> {
|
||||
let res = client
|
||||
.get("https://api.minecraftservices.com/minecraft/profile")
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Bearer {}", minecraft_access_token),
|
||||
)
|
||||
.send()
|
||||
.await?
|
||||
.json::<ProfileResponse>()
|
||||
.await?;
|
||||
println!("{:?}", res);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
|
|
@ -1,29 +1,77 @@
|
|||
//! Tell Mojang you're joining a multiplayer server.
|
||||
//!
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Tell Mojang's servers that you are going to join a multiplayer server.
|
||||
/// The server ID is an empty string.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SessionServerError {
|
||||
#[error("Error sending HTTP request to sessionserver: {0}")]
|
||||
HttpError(#[from] reqwest::Error),
|
||||
#[error("Multiplayer is not enabled for this account")]
|
||||
MultiplayerDisabled,
|
||||
#[error("This account has been banned from multiplayer")]
|
||||
Banned,
|
||||
#[error("Unknown sessionserver error: {0}")]
|
||||
Unknown(String),
|
||||
#[error("Unexpected response from sessionserver (status code {status_code}): {body}")]
|
||||
UnexpectedResponse { status_code: u16, body: String },
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ForbiddenError {
|
||||
pub error: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
/// Tell Mojang's servers that you are going to join a multiplayer server,
|
||||
/// which is required to join online-mode servers. The server ID is an empty
|
||||
/// string.
|
||||
pub async fn join(
|
||||
access_token: &str,
|
||||
public_key: &str,
|
||||
private_key: &str,
|
||||
undashed_uuid: &str,
|
||||
public_key: &[u8],
|
||||
private_key: &[u8],
|
||||
uuid: &Uuid,
|
||||
server_id: &str,
|
||||
) {
|
||||
) -> Result<(), SessionServerError> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let server_hash = azalea_crypto::hex_digest(&azalea_crypto::digest_data(
|
||||
server_id,
|
||||
server_id.as_bytes(),
|
||||
public_key,
|
||||
private_key,
|
||||
));
|
||||
|
||||
client
|
||||
let mut encode_buffer = Uuid::encode_buffer();
|
||||
let undashed_uuid = uuid.simple().encode_lower(&mut encode_buffer);
|
||||
|
||||
let res = client
|
||||
.post("https://sessionserver.mojang.com/session/minecraft/join")
|
||||
.json(json! {
|
||||
accessToken: access_token,
|
||||
selectedProfile: undashed_uuid,
|
||||
serverId: server_hash
|
||||
})
|
||||
.json(&json!({
|
||||
"accessToken": access_token,
|
||||
"selectedProfile": undashed_uuid,
|
||||
"serverId": server_hash
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
match res.status() {
|
||||
reqwest::StatusCode::NO_CONTENT => Ok(()),
|
||||
reqwest::StatusCode::FORBIDDEN => {
|
||||
let forbidden = res.json::<ForbiddenError>().await?;
|
||||
match forbidden.error.as_str() {
|
||||
"InsufficientPrivilegesException" => Err(SessionServerError::MultiplayerDisabled),
|
||||
"UserBannedException" => Err(SessionServerError::Banned),
|
||||
_ => Err(SessionServerError::Unknown(forbidden.error)),
|
||||
}
|
||||
}
|
||||
status_code => {
|
||||
let body = res.text().await?;
|
||||
Err(SessionServerError::UnexpectedResponse {
|
||||
status_code: status_code.as_u16(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,18 @@ use tokio::sync::mpsc::UnboundedReceiver;
|
|||
/// Something that can join Minecraft servers.
|
||||
pub struct Account {
|
||||
pub username: String,
|
||||
/// The access token for authentication. You can obtain one of these
|
||||
/// manually from azalea-auth.
|
||||
pub access_token: Option<String>,
|
||||
/// Only required for online-mode accounts.
|
||||
pub uuid: Option<uuid::Uuid>,
|
||||
}
|
||||
impl Account {
|
||||
pub fn offline(username: &str) -> Self {
|
||||
Self {
|
||||
username: username.to_string(),
|
||||
access_token: None,
|
||||
uuid: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ use std::{
|
|||
};
|
||||
use thiserror::Error;
|
||||
use tokio::{
|
||||
io::AsyncWriteExt,
|
||||
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||
task::JoinHandle,
|
||||
time::{self},
|
||||
|
@ -105,6 +104,8 @@ pub enum JoinError {
|
|||
ReadPacket(#[from] azalea_protocol::read::ReadPacketError),
|
||||
#[error("{0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("{0}")]
|
||||
SessionServer(#[from] azalea_auth::sessionserver::SessionServerError),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -159,6 +160,17 @@ impl Client {
|
|||
debug!("Got encryption request");
|
||||
let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap();
|
||||
|
||||
if let Some(access_token) = &account.access_token {
|
||||
conn.authenticate(
|
||||
&access_token,
|
||||
&account
|
||||
.uuid
|
||||
.expect("Uuid must be present if access token is present."),
|
||||
p,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// TODO: authenticate with the server here (authenticateServer)
|
||||
|
||||
conn.write(
|
||||
|
@ -237,7 +249,7 @@ impl Client {
|
|||
|
||||
/// Disconnect from the server, ending all tasks.
|
||||
pub async fn shutdown(self) -> Result<(), std::io::Error> {
|
||||
self.write_conn.lock().await.write_stream.shutdown().await?;
|
||||
self.write_conn.lock().await.shutdown().await?;
|
||||
let tasks = self.tasks.lock();
|
||||
for task in tasks.iter() {
|
||||
task.abort();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Azalea Protocol
|
||||
|
||||
Send and receive Minecraft packets. You should probably use `azalea` or `azalea-client` instead.
|
||||
A low-level crate to send and receive Minecraft packets. You should probably use `azalea` or `azalea-client` instead.
|
||||
|
||||
The goal is to only support the latest Minecraft version in order to ease development.
|
||||
|
||||
|
|
|
@ -2,32 +2,38 @@
|
|||
|
||||
use crate::packets::game::{ClientboundGamePacket, ServerboundGamePacket};
|
||||
use crate::packets::handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket};
|
||||
use crate::packets::login::clientbound_hello_packet::ClientboundHelloPacket;
|
||||
use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket};
|
||||
use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
|
||||
use crate::packets::ProtocolPacket;
|
||||
use crate::read::{read_packet, ReadPacketError};
|
||||
use crate::write::write_packet;
|
||||
use crate::ServerIpAddress;
|
||||
use azalea_auth::sessionserver::SessionServerError;
|
||||
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
|
||||
use bytes::BytesMut;
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use thiserror::Error;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use tokio::net::TcpStream;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ReadConnection<R: ProtocolPacket> {
|
||||
pub read_stream: OwnedReadHalf,
|
||||
read_stream: OwnedReadHalf,
|
||||
buffer: BytesMut,
|
||||
pub compression_threshold: Option<u32>,
|
||||
pub dec_cipher: Option<Aes128CfbDec>,
|
||||
compression_threshold: Option<u32>,
|
||||
dec_cipher: Option<Aes128CfbDec>,
|
||||
private_key: Option<[u8; 16]>,
|
||||
_reading: PhantomData<R>,
|
||||
}
|
||||
|
||||
pub struct WriteConnection<W: ProtocolPacket> {
|
||||
pub write_stream: OwnedWriteHalf,
|
||||
pub compression_threshold: Option<u32>,
|
||||
pub enc_cipher: Option<Aes128CfbEnc>,
|
||||
write_stream: OwnedWriteHalf,
|
||||
compression_threshold: Option<u32>,
|
||||
enc_cipher: Option<Aes128CfbEnc>,
|
||||
private_key: Option<[u8; 16]>,
|
||||
_writing: PhantomData<W>,
|
||||
}
|
||||
|
||||
|
@ -64,6 +70,10 @@ where
|
|||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn shutdown(&mut self) -> std::io::Result<()> {
|
||||
self.write_stream.shutdown().await
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, W> Connection<R, W>
|
||||
|
@ -110,12 +120,14 @@ impl Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> {
|
|||
buffer: BytesMut::new(),
|
||||
compression_threshold: None,
|
||||
dec_cipher: None,
|
||||
private_key: None,
|
||||
_reading: PhantomData,
|
||||
},
|
||||
writer: WriteConnection {
|
||||
write_stream,
|
||||
compression_threshold: None,
|
||||
enc_cipher: None,
|
||||
private_key: None,
|
||||
_writing: PhantomData,
|
||||
},
|
||||
})
|
||||
|
@ -145,13 +157,55 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
|
|||
pub fn set_encryption_key(&mut self, key: [u8; 16]) {
|
||||
// minecraft has a cipher decoder and encoder, i don't think it matters though?
|
||||
let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key);
|
||||
self.writer.enc_cipher = Some(enc_cipher);
|
||||
self.reader.dec_cipher = Some(dec_cipher);
|
||||
self.writer.enc_cipher = Some(enc_cipher);
|
||||
self.reader.private_key = Some(key);
|
||||
self.writer.private_key = Some(key);
|
||||
}
|
||||
|
||||
pub fn game(self) -> Connection<ClientboundGamePacket, ServerboundGamePacket> {
|
||||
Connection::from(self)
|
||||
}
|
||||
|
||||
/// Authenticate with Minecraft's servers, which is required to join
|
||||
/// online-mode servers. This must happen when you get a
|
||||
/// `ClientboundLoginPacket::Hello` packet.
|
||||
///
|
||||
/// ```no_run
|
||||
/// let token = azalea_auth::auth(azalea_auth::AuthOpts {
|
||||
/// ..Default::default()
|
||||
/// })
|
||||
/// .await;
|
||||
/// let player_data = azalea_auth::get_profile(token).await;
|
||||
///
|
||||
/// let mut connection = azalea::Connection::new(&server_address).await?;
|
||||
///
|
||||
/// // transition to the login state, in a real program we would have done a handshake first
|
||||
/// connection.login();
|
||||
///
|
||||
/// match connection.read().await? {
|
||||
/// ClientboundLoginPacket::Hello(p) => {
|
||||
/// // tell Mojang we're joining the server
|
||||
/// connection.authenticate(&token, player_data.uuid, p).await?;
|
||||
/// }
|
||||
/// _ => {}
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn authenticate(
|
||||
&self,
|
||||
access_token: &str,
|
||||
uuid: &Uuid,
|
||||
packet: ClientboundHelloPacket,
|
||||
) -> Result<(), SessionServerError> {
|
||||
azalea_auth::sessionserver::join(
|
||||
access_token,
|
||||
&packet.public_key,
|
||||
&self.writer.private_key.expect("encryption key not set"),
|
||||
uuid,
|
||||
&packet.server_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// rust doesn't let us implement From because allegedly it conflicts with
|
||||
|
@ -172,12 +226,14 @@ where
|
|||
buffer: connection.reader.buffer,
|
||||
compression_threshold: connection.reader.compression_threshold,
|
||||
dec_cipher: connection.reader.dec_cipher,
|
||||
private_key: connection.reader.private_key,
|
||||
_reading: PhantomData,
|
||||
},
|
||||
writer: WriteConnection {
|
||||
compression_threshold: connection.writer.compression_threshold,
|
||||
write_stream: connection.writer.write_stream,
|
||||
enc_cipher: connection.writer.enc_cipher,
|
||||
private_key: connection.writer.private_key,
|
||||
_writing: PhantomData,
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue