aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-12-07 21:58:42 -0600
committerGitHub <noreply@github.com>2022-12-07 21:58:42 -0600
commit431f9e90a782c42f9d28a85d1d18288e352d3e0c (patch)
tree2ef9ed32d214efb1f93164b8eb88f3a6b2674430
parent7d901e39bc10a855b545d7b6c167f45148a1fb0a (diff)
downloadazalea-drasl-431f9e90a782c42f9d28a85d1d18288e352d3e0c.tar.xz
Reauth on invalid session (#50)
* Reauth on invalid session * fix to actually use new token and retry auth * fix unused vars
-rwxr-xr-xazalea-auth/src/sessionserver.rs8
-rw-r--r--azalea-buf/azalea-buf-macros/src/read.rs7
-rwxr-xr-xazalea-client/src/account.rs54
-rw-r--r--azalea-client/src/client.rs49
-rwxr-xr-xazalea-protocol/src/connect.rs2
5 files changed, 100 insertions, 20 deletions
diff --git a/azalea-auth/src/sessionserver.rs b/azalea-auth/src/sessionserver.rs
index fe0b694a..800a9642 100755
--- a/azalea-auth/src/sessionserver.rs
+++ b/azalea-auth/src/sessionserver.rs
@@ -13,6 +13,10 @@ pub enum SessionServerError {
MultiplayerDisabled,
#[error("This account has been banned from multiplayer")]
Banned,
+ #[error("The authentication servers are currently not reachable")]
+ AuthServersUnreachable,
+ #[error("Invalid or expired session")]
+ InvalidSession,
#[error("Unknown sessionserver error: {0}")]
Unknown(String),
#[error("Unexpected response from sessionserver (status code {status_code}): {body}")]
@@ -64,6 +68,10 @@ pub async fn join(
match forbidden.error.as_str() {
"InsufficientPrivilegesException" => Err(SessionServerError::MultiplayerDisabled),
"UserBannedException" => Err(SessionServerError::Banned),
+ "AuthenticationUnavailableException" => {
+ Err(SessionServerError::AuthServersUnreachable)
+ }
+ "InvalidCredentialsException" => Err(SessionServerError::InvalidSession),
_ => Err(SessionServerError::Unknown(forbidden.error)),
}
}
diff --git a/azalea-buf/azalea-buf-macros/src/read.rs b/azalea-buf/azalea-buf-macros/src/read.rs
index 42050d6b..75c71f94 100644
--- a/azalea-buf/azalea-buf-macros/src/read.rs
+++ b/azalea-buf/azalea-buf-macros/src/read.rs
@@ -1,10 +1,5 @@
-use proc_macro::TokenStream;
-use proc_macro2::Span;
use quote::{quote, ToTokens};
-use syn::{
- self, parse_macro_input, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field,
- FieldsNamed, Ident,
-};
+use syn::{self, punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident};
fn read_named_fields(
named: &Punctuated<Field, Comma>,
diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs
index 42bfe6fc..0d758507 100755
--- a/azalea-client/src/account.rs
+++ b/azalea-client/src/account.rs
@@ -1,6 +1,9 @@
//! Connect to Minecraft servers.
+use std::sync::Arc;
+
use crate::get_mc_dir;
+use parking_lot::Mutex;
use uuid::Uuid;
/// Something that can join Minecraft servers.
@@ -24,9 +27,25 @@ 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>,
+ ///
+ /// 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.
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 {
@@ -38,6 +57,9 @@ impl Account {
username: username.to_string(),
access_token: None,
uuid: None,
+ auth_opts: AuthOpts::Offline {
+ username: username.to_string(),
+ },
}
}
@@ -62,8 +84,36 @@ impl Account {
.await?;
Ok(Self {
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")),
+ 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(())
+ }
+ }
+ }
}
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
index 17fb4840..4f0a2ed6 100644
--- a/azalea-client/src/client.rs
+++ b/azalea-client/src/client.rs
@@ -1,6 +1,6 @@
pub use crate::chat::ChatPacket;
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_protocol::{
connect::{Connection, ConnectionError, ReadConnection, WriteConnection},
@@ -142,6 +142,8 @@ pub enum JoinError {
SessionServer(#[from] azalea_auth::sessionserver::SessionServerError),
#[error("The given address could not be parsed into a ServerAddress")]
InvalidAddress,
+ #[error("Couldn't refresh access token: {0}")]
+ Auth(#[from] azalea_auth::AuthError),
}
#[derive(Error, Debug)]
@@ -239,7 +241,11 @@ impl Client {
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(
mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
account: &Account,
@@ -282,15 +288,36 @@ impl Client {
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."),
- e.secret_key,
- p,
- )
- .await?;
+ // keep track of the number of times we tried
+ // authenticating so we can give up after too many
+ let mut attempts: usize = 1;
+
+ while let Err(e) = {
+ let access_token = access_token.lock().clone();
+ conn.authenticate(
+ &access_token,
+ &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(
diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs
index fde24c40..ece7fd6f 100755
--- a/azalea-protocol/src/connect.rs
+++ b/azalea-protocol/src/connect.rs
@@ -322,7 +322,7 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
access_token: &str,
uuid: &Uuid,
private_key: [u8; 16],
- packet: ClientboundHelloPacket,
+ packet: &ClientboundHelloPacket,
) -> Result<(), SessionServerError> {
azalea_auth::sessionserver::join(
access_token,