1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 23:44:38 +00:00

Reauth on invalid session (#50)

* Reauth on invalid session

* fix to actually use new token and retry auth

* fix unused vars
This commit is contained in:
mat 2022-12-07 21:58:42 -06:00 committed by GitHub
commit 431f9e90a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 20 deletions

View file

@ -13,6 +13,10 @@ pub enum SessionServerError {
MultiplayerDisabled, MultiplayerDisabled,
#[error("This account has been banned from multiplayer")] #[error("This account has been banned from multiplayer")]
Banned, Banned,
#[error("The authentication servers are currently not reachable")]
AuthServersUnreachable,
#[error("Invalid or expired session")]
InvalidSession,
#[error("Unknown sessionserver error: {0}")] #[error("Unknown sessionserver error: {0}")]
Unknown(String), Unknown(String),
#[error("Unexpected response from sessionserver (status code {status_code}): {body}")] #[error("Unexpected response from sessionserver (status code {status_code}): {body}")]
@ -64,6 +68,10 @@ pub async fn join(
match forbidden.error.as_str() { match forbidden.error.as_str() {
"InsufficientPrivilegesException" => Err(SessionServerError::MultiplayerDisabled), "InsufficientPrivilegesException" => Err(SessionServerError::MultiplayerDisabled),
"UserBannedException" => Err(SessionServerError::Banned), "UserBannedException" => Err(SessionServerError::Banned),
"AuthenticationUnavailableException" => {
Err(SessionServerError::AuthServersUnreachable)
}
"InvalidCredentialsException" => Err(SessionServerError::InvalidSession),
_ => Err(SessionServerError::Unknown(forbidden.error)), _ => Err(SessionServerError::Unknown(forbidden.error)),
} }
} }

View file

@ -1,10 +1,5 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{ use syn::{self, punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident};
self, parse_macro_input, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field,
FieldsNamed, Ident,
};
fn read_named_fields( fn read_named_fields(
named: &Punctuated<Field, Comma>, named: &Punctuated<Field, Comma>,

View file

@ -1,6 +1,9 @@
//! Connect to Minecraft servers. //! Connect to Minecraft servers.
use std::sync::Arc;
use crate::get_mc_dir; use crate::get_mc_dir;
use parking_lot::Mutex;
use uuid::Uuid; use uuid::Uuid;
/// Something that can join Minecraft servers. /// Something that can join Minecraft servers.
@ -24,9 +27,25 @@ pub struct Account {
pub username: String, pub username: String,
/// The access token for authentication. You can obtain one of these /// The access token for authentication. You can obtain one of these
/// manually from azalea-auth. /// manually from azalea-auth.
pub access_token: Option<String>, ///
/// This is an Arc<Mutex> so it can be modified by [`Self::refresh`].
pub access_token: Option<Arc<Mutex<String>>>,
/// Only required for online-mode accounts. /// Only required for online-mode accounts.
pub uuid: Option<uuid::Uuid>, pub uuid: Option<uuid::Uuid>,
/// The parameters (i.e. email) that were passed for creating this
/// [`Account`]. This is used to for automatic reauthentication when we get
/// "Invalid Session" errors. If you don't need that feature (like in
/// offline mode), then you can set this to `AuthOpts::default()`.
pub auth_opts: AuthOpts,
}
/// The parameters that were passed for creating the associated [`Account`].
#[derive(Clone, Debug)]
pub enum AuthOpts {
Offline { username: String },
// this is an enum so legacy Mojang auth can be added in the future
Microsoft { email: String },
} }
impl Account { impl Account {
@ -38,6 +57,9 @@ impl Account {
username: username.to_string(), username: username.to_string(),
access_token: None, access_token: None,
uuid: None, uuid: None,
auth_opts: AuthOpts::Offline {
username: username.to_string(),
},
} }
} }
@ -62,8 +84,36 @@ impl Account {
.await?; .await?;
Ok(Self { Ok(Self {
username: auth_result.profile.name, username: auth_result.profile.name,
access_token: Some(auth_result.access_token), access_token: Some(Arc::new(Mutex::new(auth_result.access_token))),
uuid: Some(Uuid::parse_str(&auth_result.profile.id).expect("Invalid UUID")), uuid: Some(Uuid::parse_str(&auth_result.profile.id).expect("Invalid UUID")),
auth_opts: AuthOpts::Microsoft {
email: email.to_string(),
},
}) })
} }
/// Refresh the access_token for this account to be valid again.
///
/// This requires the `auth_opts` field to be set correctly (which is done
/// by default if you used the constructor functions). Note that if the
/// Account is offline-mode, this function won't do anything.
pub async fn refresh(&self) -> Result<(), azalea_auth::AuthError> {
match &self.auth_opts {
// offline mode doesn't need to refresh so just don't do anything lol
AuthOpts::Offline { .. } => Ok(()),
AuthOpts::Microsoft { email } => {
let new_account = Account::microsoft(email).await?;
let access_token = self
.access_token.as_ref()
.expect("Access token should always be set for Microsoft accounts");
let new_access_token = new_account
.access_token
.expect("Access token should always be set for Microsoft accounts")
.lock()
.clone();
*access_token.lock() = new_access_token;
Ok(())
}
}
}
} }

View file

@ -1,6 +1,6 @@
pub use crate::chat::ChatPacket; pub use crate::chat::ChatPacket;
use crate::{movement::WalkDirection, plugins::PluginStates, Account, PlayerInfo}; use crate::{movement::WalkDirection, plugins::PluginStates, Account, PlayerInfo};
use azalea_auth::game_profile::GameProfile; use azalea_auth::{game_profile::GameProfile, sessionserver::SessionServerError};
use azalea_core::{ChunkPos, ResourceLocation, Vec3}; use azalea_core::{ChunkPos, ResourceLocation, Vec3};
use azalea_protocol::{ use azalea_protocol::{
connect::{Connection, ConnectionError, ReadConnection, WriteConnection}, connect::{Connection, ConnectionError, ReadConnection, WriteConnection},
@ -142,6 +142,8 @@ pub enum JoinError {
SessionServer(#[from] azalea_auth::sessionserver::SessionServerError), SessionServer(#[from] azalea_auth::sessionserver::SessionServerError),
#[error("The given address could not be parsed into a ServerAddress")] #[error("The given address could not be parsed into a ServerAddress")]
InvalidAddress, InvalidAddress,
#[error("Couldn't refresh access token: {0}")]
Auth(#[from] azalea_auth::AuthError),
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -239,7 +241,11 @@ impl Client {
Ok((client, rx)) Ok((client, rx))
} }
/// Do a handshake with the server and get to the game state from the initial handshake state. /// Do a handshake with the server and get to the game state from the
/// initial handshake state.
///
/// This will also automatically refresh the account's access token if
/// it's expired.
pub async fn handshake( pub async fn handshake(
mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>, mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
account: &Account, account: &Account,
@ -282,15 +288,36 @@ impl Client {
let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap(); let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap();
if let Some(access_token) = &account.access_token { if let Some(access_token) = &account.access_token {
conn.authenticate( // keep track of the number of times we tried
access_token, // authenticating so we can give up after too many
&account let mut attempts: usize = 1;
.uuid
.expect("Uuid must be present if access token is present."), while let Err(e) = {
e.secret_key, let access_token = access_token.lock().clone();
p, conn.authenticate(
) &access_token,
.await?; &account
.uuid
.expect("Uuid must be present if access token is present."),
e.secret_key,
&p,
)
.await
} {
if attempts >= 2 {
// if this is the second attempt and we failed
// both times, give up
return Err(e.into());
}
if let SessionServerError::InvalidSession = e {
// uh oh, we got an invalid session and have
// to reauthenticate now
account.refresh().await?;
} else {
return Err(e.into());
}
attempts += 1;
}
} }
conn.write( conn.write(

View file

@ -322,7 +322,7 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
access_token: &str, access_token: &str,
uuid: &Uuid, uuid: &Uuid,
private_key: [u8; 16], private_key: [u8; 16],
packet: ClientboundHelloPacket, packet: &ClientboundHelloPacket,
) -> Result<(), SessionServerError> { ) -> Result<(), SessionServerError> {
azalea_auth::sessionserver::join( azalea_auth::sessionserver::join(
access_token, access_token,