aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAditya Kumar <117935160+AS1100K@users.noreply.github.com>2024-08-12 03:24:45 +0530
committerGitHub <noreply@github.com>2024-08-11 16:54:45 -0500
commit13afc1d6a42d28f01087fb59ca868696be4a2635 (patch)
treea3b77e5a7529c764a9f384407b580e273b0b3b55
parent92c90753eac774add5d033a567f9af115dad4ea0 (diff)
downloadazalea-drasl-13afc1d6a42d28f01087fb59ca868696be4a2635.tar.xz
Auth Customization Options (#159)
* Added Support for Custom Auth using `client_id` and `scope` * fix: `Account::microsoft` and added lifetime to `Account::microsoft_with_custom_client_id` * Added function `with_microsoft_access_token_and_custom_client_id` * Removed Custom Scope. * I got carried away, and made scope also customizable, later realized no customization is needed. * Better Documentation and Minor fixes * Added Custom Scope * Added RpsTicket format for custom `client_id` * Moved to non-static str * fix example Co-authored-by: mat <27899617+mat-1@users.noreply.github.com> * fix doc grammer * changed function signature * fmt * fixed example * removed dead code * Removed `d=` insertion in `client_id` * removed unnecessary `mut` --------- Co-authored-by: mat <27899617+mat-1@users.noreply.github.com>
-rwxr-xr-xazalea-auth/examples/auth_manual.rs5
-rwxr-xr-xazalea-auth/src/auth.rs73
-rwxr-xr-xazalea-client/src/account.rs38
3 files changed, 96 insertions, 20 deletions
diff --git a/azalea-auth/examples/auth_manual.rs b/azalea-auth/examples/auth_manual.rs
index 6a9510a8..05803c87 100755
--- a/azalea-auth/examples/auth_manual.rs
+++ b/azalea-auth/examples/auth_manual.rs
@@ -18,15 +18,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
Ok(())
}
+// We will be using default `client_id` and `scope`
async fn auth() -> Result<ProfileResponse, Box<dyn Error>> {
let client = reqwest::Client::new();
- let res = azalea_auth::get_ms_link_code(&client).await?;
+ let res = azalea_auth::get_ms_link_code(&client, None, None).await?;
println!(
"Go to {} and enter the code {}",
res.verification_uri, res.user_code
);
- let msa = azalea_auth::get_ms_auth_token(&client, res).await?;
+ let msa = azalea_auth::get_ms_auth_token(&client, res, None).await?;
let auth_result = azalea_auth::get_minecraft_token(&client, &msa.data.access_token).await?;
Ok(azalea_auth::get_profile(&client, &auth_result.minecraft_access_token).await?)
}
diff --git a/azalea-auth/src/auth.rs b/azalea-auth/src/auth.rs
index e0a75adf..08d2d3a8 100755
--- a/azalea-auth/src/auth.rs
+++ b/azalea-auth/src/auth.rs
@@ -13,7 +13,7 @@ use thiserror::Error;
use uuid::Uuid;
#[derive(Default)]
-pub struct AuthOpts {
+pub struct AuthOpts<'a> {
/// 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.
@@ -24,6 +24,12 @@ pub struct AuthOpts {
/// The directory to store the cache in. If this is not set, caching is not
/// done.
pub cache_file: Option<PathBuf>,
+ /// If you choose to use your own Microsoft authentication instead of using
+ /// Nintendo Switch, just put your client_id here.
+ pub client_id: Option<&'a str>,
+ /// If you want to use custom scope instead of default one, just put your
+ /// scope here.
+ pub scope: Option<&'a str>,
}
#[derive(Debug, Error)]
@@ -59,7 +65,7 @@ pub enum AuthError {
/// If you want to use your own code to cache or show the auth code to the user
/// in a different way, use [`get_ms_link_code`], [`get_ms_auth_token`],
/// [`get_minecraft_token`] and [`get_profile`] instead.
-pub async fn auth(email: &str, opts: AuthOpts) -> Result<AuthResult, AuthError> {
+pub async fn auth<'a>(email: &str, opts: AuthOpts<'a>) -> Result<AuthResult, AuthError> {
let cached_account = if let Some(cache_file) = &opts.cache_file {
cache::get_account_in_cache(cache_file, email).await
} else {
@@ -76,20 +82,32 @@ pub async fn auth(email: &str, opts: AuthOpts) -> Result<AuthResult, AuthError>
profile: account.profile.clone(),
})
} else {
+ let client_id = opts.client_id.unwrap_or(CLIENT_ID);
+ let scope = opts.scope.unwrap_or(SCOPE);
+
let client = reqwest::Client::new();
let mut msa = if let Some(account) = cached_account {
account.msa
} else {
- interactive_get_ms_auth_token(&client, email).await?
+ interactive_get_ms_auth_token(&client, email, Some(client_id), Some(scope)).await?
};
if msa.is_expired() {
tracing::trace!("refreshing Microsoft auth token");
- match refresh_ms_auth_token(&client, &msa.data.refresh_token).await {
+ match refresh_ms_auth_token(
+ &client,
+ &msa.data.refresh_token,
+ opts.client_id,
+ opts.scope,
+ )
+ .await
+ {
Ok(new_msa) => msa = new_msa,
Err(e) => {
// can't refresh, ask the user to auth again
tracing::error!("Error refreshing Microsoft auth token: {}", e);
- msa = interactive_get_ms_auth_token(&client, email).await?;
+ msa =
+ interactive_get_ms_auth_token(&client, email, Some(client_id), Some(scope))
+ .await?;
}
}
}
@@ -259,6 +277,7 @@ pub struct ProfileResponse {
// nintendo switch (so it works for accounts that are under 18 years old)
const CLIENT_ID: &str = "00000000441cc96b";
+const SCOPE: &str = "service::user.auth.xboxlive.com::MBI_SSL";
#[derive(Debug, Error)]
pub enum GetMicrosoftAuthTokenError {
@@ -280,12 +299,12 @@ pub enum GetMicrosoftAuthTokenError {
///
/// ```
/// # async fn example(client: &reqwest::Client) -> Result<(), Box<dyn std::error::Error>> {
-/// let res = azalea_auth::get_ms_link_code(&client).await?;
+/// let res = azalea_auth::get_ms_link_code(&client, None, None).await?;
/// println!(
/// "Go to {} and enter the code {}",
/// res.verification_uri, res.user_code
/// );
-/// let msa = azalea_auth::get_ms_auth_token(client, res).await?;
+/// let msa = azalea_auth::get_ms_auth_token(client, res, None).await?;
/// let minecraft = azalea_auth::get_minecraft_token(client, &msa.data.access_token).await?;
/// let profile = azalea_auth::get_profile(&client, &minecraft.minecraft_access_token).await?;
/// # Ok(())
@@ -293,12 +312,22 @@ pub enum GetMicrosoftAuthTokenError {
/// ```
pub async fn get_ms_link_code(
client: &reqwest::Client,
+ client_id: Option<&str>,
+ scope: Option<&str>,
) -> Result<DeviceCodeResponse, GetMicrosoftAuthTokenError> {
+ let client_id = if let Some(c) = client_id {
+ c
+ } else {
+ CLIENT_ID
+ };
+
+ let scope = if let Some(c) = scope { c } else { SCOPE };
+
Ok(client
.post("https://login.live.com/oauth20_connect.srf")
.form(&vec![
- ("scope", "service::user.auth.xboxlive.com::MBI_SSL"),
- ("client_id", CLIENT_ID),
+ ("scope", scope),
+ ("client_id", client_id),
("response_type", "device_code"),
])
.send()
@@ -314,7 +343,14 @@ pub async fn get_ms_link_code(
pub async fn get_ms_auth_token(
client: &reqwest::Client,
res: DeviceCodeResponse,
+ client_id: Option<&str>,
) -> Result<ExpiringValue<AccessTokenResponse>, GetMicrosoftAuthTokenError> {
+ let client_id = if let Some(c) = client_id {
+ c
+ } else {
+ CLIENT_ID
+ };
+
let login_expires_at = Instant::now() + std::time::Duration::from_secs(res.expires_in);
while Instant::now() < login_expires_at {
@@ -323,10 +359,10 @@ pub async fn get_ms_auth_token(
tracing::trace!("Polling to check if user has logged in...");
if let Ok(access_token_response) = client
.post(format!(
- "https://login.live.com/oauth20_token.srf?client_id={CLIENT_ID}"
+ "https://login.live.com/oauth20_token.srf?client_id={client_id}"
))
.form(&vec![
- ("client_id", CLIENT_ID),
+ ("client_id", client_id),
("device_code", &res.device_code),
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
])
@@ -357,15 +393,17 @@ pub async fn get_ms_auth_token(
pub async fn interactive_get_ms_auth_token(
client: &reqwest::Client,
email: &str,
+ client_id: Option<&str>,
+ scope: Option<&str>,
) -> Result<ExpiringValue<AccessTokenResponse>, GetMicrosoftAuthTokenError> {
- let res = get_ms_link_code(client).await?;
+ let res = get_ms_link_code(client, client_id, scope).await?;
tracing::trace!("Device code response: {:?}", res);
println!(
"Go to \x1b[1m{}\x1b[m and enter the code \x1b[1m{}\x1b[m for \x1b[1m{}\x1b[m",
res.verification_uri, res.user_code, email
);
- get_ms_auth_token(client, res).await
+ get_ms_auth_token(client, res, client_id).await
}
#[derive(Debug, Error)]
@@ -379,12 +417,17 @@ pub enum RefreshMicrosoftAuthTokenError {
pub async fn refresh_ms_auth_token(
client: &reqwest::Client,
refresh_token: &str,
+ client_id: Option<&str>,
+ scope: Option<&str>,
) -> Result<ExpiringValue<AccessTokenResponse>, RefreshMicrosoftAuthTokenError> {
+ let client_id = client_id.unwrap_or(CLIENT_ID);
+ let scope = scope.unwrap_or(SCOPE);
+
let access_token_response_text = client
.post("https://login.live.com/oauth20_token.srf")
.form(&vec![
- ("scope", "service::user.auth.xboxlive.com::MBI_SSL"),
- ("client_id", CLIENT_ID),
+ ("scope", scope),
+ ("client_id", client_id),
("grant_type", "refresh_token"),
("refresh_token", refresh_token),
])
diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs
index 741a07d4..c007e01a 100755
--- a/azalea-client/src/account.rs
+++ b/azalea-client/src/account.rs
@@ -90,6 +90,18 @@ impl Account {
/// a key for the cache, but it's recommended to use the real email to
/// avoid confusion.
pub async fn microsoft(email: &str) -> Result<Self, azalea_auth::AuthError> {
+ Self::microsoft_with_custom_client_id_and_scope(email, None, None).await
+ }
+
+ /// Similar to [`account.microsoft()`](Self::microsoft) but you can use your
+ /// own `client_id` and `scope`.
+ ///
+ /// Pass `None` if you want to use default ones.
+ pub async fn microsoft_with_custom_client_id_and_scope(
+ email: &str,
+ client_id: Option<&str>,
+ scope: Option<&str>,
+ ) -> Result<Self, azalea_auth::AuthError> {
let minecraft_dir = minecraft_folder_path::minecraft_dir().unwrap_or_else(|| {
panic!(
"No {} environment variable found",
@@ -100,6 +112,8 @@ impl Account {
email,
azalea_auth::AuthOpts {
cache_file: Some(minecraft_dir.join("azalea-auth.json")),
+ client_id,
+ scope,
..Default::default()
},
)
@@ -128,24 +142,42 @@ impl Account {
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let client = reqwest::Client::new();
///
- /// let res = azalea_auth::get_ms_link_code(&client).await?;
+ /// let res = azalea_auth::get_ms_link_code(&client, None, None).await?;
+ /// // Or, `azalea_auth::get_ms_link_code(&client, Some(client_id), None).await?`
+ /// // if you want to use your own client_id
/// println!(
/// "Go to {} and enter the code {}",
/// res.verification_uri, res.user_code
/// );
- /// let msa = azalea_auth::get_ms_auth_token(&client, res).await?;
+ /// let msa = azalea_auth::get_ms_auth_token(&client, res, None).await?;
/// Account::with_microsoft_access_token(msa).await?;
/// # Ok(())
/// # }
/// ```
pub async fn with_microsoft_access_token(
+ msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
+ ) -> Result<Self, azalea_auth::AuthError> {
+ Self::with_microsoft_access_token_and_custom_client_id_and_scope(msa, None, None).await
+ }
+
+ /// Similar to [`Account::with_microsoft_access_token`] but you can use
+ /// custom `client_id` and `scope`.
+ pub async fn with_microsoft_access_token_and_custom_client_id_and_scope(
mut msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
+ client_id: Option<&str>,
+ scope: Option<&str>,
) -> Result<Self, azalea_auth::AuthError> {
let client = reqwest::Client::new();
if msa.is_expired() {
tracing::trace!("refreshing Microsoft auth token");
- msa = azalea_auth::refresh_ms_auth_token(&client, &msa.data.refresh_token).await?;
+ msa = azalea_auth::refresh_ms_auth_token(
+ &client,
+ &msa.data.refresh_token,
+ client_id,
+ scope,
+ )
+ .await?;
}
let msa_token = &msa.data.access_token;