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:
parent
7d901e39bc
commit
431f9e90a7
5 changed files with 100 additions and 20 deletions
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue