diff --git a/examples/src/bin/auth_azure.rs b/examples/src/bin/auth_azure.rs index aaae60a..3484b0a 100644 --- a/examples/src/bin/auth_azure.rs +++ b/examples/src/bin/auth_azure.rs @@ -7,7 +7,7 @@ use tokio::{ }; use xal::{ client_params::CLIENT_ANDROID, - flows::{AuthPromptCallback, AuthPromptData}, + AuthPromptCallback, AuthPromptData, oauth2::{RedirectUrl, Scope}, url::Url, Error, XalAppParameters, diff --git a/examples/src/bin/auth_cli.rs b/examples/src/bin/auth_cli.rs index c5d4387..4e43566 100644 --- a/examples/src/bin/auth_cli.rs +++ b/examples/src/bin/auth_cli.rs @@ -1,9 +1,9 @@ -use xal::{flows, AccessTokenPrefix, Error}; +use xal::{CliCallbackHandler, AccessTokenPrefix, Error}; use xal_examples::auth_main_default; #[tokio::main] async fn main() -> Result<(), Error> { - auth_main_default(AccessTokenPrefix::None, flows::CliCallbackHandler) + auth_main_default(AccessTokenPrefix::None, CliCallbackHandler) .await .ok(); diff --git a/examples/src/bin/auth_minecraft.rs b/examples/src/bin/auth_minecraft.rs index afcd57a..ce98195 100644 --- a/examples/src/bin/auth_minecraft.rs +++ b/examples/src/bin/auth_minecraft.rs @@ -1,6 +1,6 @@ use serde_json::json; use xal::{ - extensions::JsonExDeserializeMiddleware, flows, oauth2::TokenResponse, AccessTokenPrefix, + extensions::JsonExDeserializeMiddleware, CliCallbackHandler, oauth2::TokenResponse, AccessTokenPrefix, Error, XalAuthenticator, }; use xal_examples::auth_main; @@ -15,7 +15,7 @@ async fn main() -> Result<(), Error> { client_params, "RETAIL".into(), AccessTokenPrefix::None, - flows::CliCallbackHandler, + CliCallbackHandler, ) .await?; diff --git a/examples/src/bin/auth_webview.rs b/examples/src/bin/auth_webview.rs index e82ea45..b2a4503 100644 --- a/examples/src/bin/auth_webview.rs +++ b/examples/src/bin/auth_webview.rs @@ -14,7 +14,7 @@ use wry::{ webview::WebViewBuilder, }; use xal::{ - flows::{AuthPromptCallback, AuthPromptData}, + AuthPromptCallback, AuthPromptData, url::Url, AccessTokenPrefix, Error, XalAuthenticator, }; diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 31374a8..3935d46 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -2,8 +2,8 @@ use clap::{Parser, ValueEnum}; use env_logger::Env; use log::info; use xal::{ - flows, tokenstore::TokenStore, AccessTokenPrefix, Constants, Error, XalAppParameters, - XalAuthenticator, XalClientParameters, + Flows, TokenStore, AccessTokenPrefix, Constants, Error, XalAppParameters, + XalAuthenticator, XalClientParameters, AuthPromptCallback }; /// Common cli arguments @@ -57,7 +57,7 @@ pub enum AuthFlow { pub async fn auth_main_default( access_token_prefix: AccessTokenPrefix, - auth_cb: impl flows::AuthPromptCallback, + auth_cb: impl AuthPromptCallback, ) -> Result { auth_main( XalAppParameters::default(), @@ -75,22 +75,22 @@ pub async fn auth_main( client_params: XalClientParameters, sandbox_id: String, access_token_prefix: AccessTokenPrefix, - auth_cb: impl flows::AuthPromptCallback, + auth_cb: impl AuthPromptCallback, ) -> Result { let args = handle_args(); - let mut ts = match flows::try_refresh_live_tokens_from_file(&args.token_filepath).await { + let mut ts = match Flows::try_refresh_live_tokens_from_file(&args.token_filepath).await { Ok((mut authenticator, ts)) => { info!("Tokens refreshed succesfully, proceeding with Xbox Live Authorization"); match args.flow { AuthFlow::Sisu => { info!("Authorize and gather rest of xbox live tokens via sisu"); - flows::xbox_live_sisu_authorization_flow(&mut authenticator, ts.live_token) + Flows::xbox_live_sisu_authorization_flow(&mut authenticator, ts.live_token) .await? } _ => { info!("Authorize Xbox Live the traditional way, via individual requests"); - flows::xbox_live_authorization_traditional_flow( + Flows::xbox_live_authorization_traditional_flow( &mut authenticator, ts.live_token, Constants::RELYING_PARTY_XBOXLIVE.into(), @@ -108,17 +108,17 @@ pub async fn auth_main( info!("Authentication via flow={:?}", args.flow); let ts = match args.flow { AuthFlow::Sisu => { - flows::xbox_live_sisu_full_flow(&mut authenticator, auth_cb).await? + Flows::xbox_live_sisu_full_flow(&mut authenticator, auth_cb).await? } AuthFlow::DeviceCode => { - flows::ms_device_code_flow(&mut authenticator, auth_cb, tokio::time::sleep) + Flows::ms_device_code_flow(&mut authenticator, auth_cb, tokio::time::sleep) .await? } AuthFlow::Implicit => { - flows::ms_authorization_flow(&mut authenticator, auth_cb, true).await? + Flows::ms_authorization_flow(&mut authenticator, auth_cb, true).await? } AuthFlow::AuthorizationCode => { - flows::ms_authorization_flow(&mut authenticator, auth_cb, false).await? + Flows::ms_authorization_flow(&mut authenticator, auth_cb, false).await? } }; @@ -128,7 +128,7 @@ pub async fn auth_main( info!("Continuing flow via traditional Xbox Live authorization"); // Only required for non-sisu authentication, as // sisu already gathers all the tokens at once - flows::xbox_live_authorization_traditional_flow( + Flows::xbox_live_authorization_traditional_flow( &mut authenticator, ts.live_token, Constants::RELYING_PARTY_XBOXLIVE.into(), diff --git a/src/flows.rs b/src/flows.rs index 68537c1..dc85368 100644 --- a/src/flows.rs +++ b/src/flows.rs @@ -123,7 +123,7 @@ impl AuthPromptData { /// Sisu Auth callback trait /// -/// Used as an argument to [`crate::flows::xbox_live_sisu_full_flow`] +/// Used as an argument to [`crate::Flows::xbox_live_sisu_full_flow`] /// /// /// # Examples @@ -131,8 +131,7 @@ impl AuthPromptData { /// ``` /// # use std::io; /// # use async_trait::async_trait; -/// # use xal::XalAuthenticator; -/// # use xal::flows::{AuthPromptCallback, AuthPromptData}; +/// # use xal::{XalAuthenticator, AuthPromptCallback, AuthPromptData}; /// # use xal::url::Url; /// // Define callback handler for OAuth2 flow /// struct CallbackHandler; @@ -212,516 +211,520 @@ impl AuthPromptCallback for CliCallbackHandler { } } -/// Try to deserialize a JSON TokenStore from filepath and refresh the Windows Live tokens if needed. -/// -/// # Errors -/// -/// This function may return an error if the file cannot be read, fails to deserialize or the -/// tokens cannot be refreshed. -/// -/// # Examples -/// -/// ``` -/// # use xal::{Error, tokenstore::TokenStore}; -/// use xal::flows; -/// -/// # async fn demo_code() -> Result<(), Error> { -/// // Refresh Windows Live tokens first -/// let (mut authenticator, token_store) = flows::try_refresh_live_tokens_from_file("tokens.json") -/// .await?; -/// -/// // Continue by requesting xbox live tokens -/// let token_store = flows::xbox_live_sisu_authorization_flow( -/// &mut authenticator, -/// token_store.live_token -/// ) -/// .await?; -/// -/// # Ok(()) -/// # } -/// ``` -/// -/// # Returns -/// -/// If successful, a tuple of [`crate::XalAuthenticator`] and [`crate::tokenstore::TokenStore`] -/// is returned. TokenStore will contain the refreshed `live_tokens`. -pub async fn try_refresh_live_tokens_from_file( - filepath: &str, -) -> Result<(XalAuthenticator, TokenStore), Error> { - let mut ts = TokenStore::load_from_file(filepath)?; - let authenticator = try_refresh_live_tokens_from_tokenstore(&mut ts).await?; - Ok((authenticator, ts)) -} +/// Higher-level, bundled functionality for common authentication tasks +pub struct Flows; -/// Try to read tokens from the token store and refresh the Windows Live tokens if needed. -/// -/// # Examples -/// -/// ``` -/// use std::fs::File; -/// use serde_json; -/// use xal::{flows, tokenstore::TokenStore}; -/// -/// # async fn demo_code() -> Result<(), xal::Error> { -/// let mut file = File::open("tokens.json") -/// .expect("Failed to open tokenfile"); -/// let mut ts: TokenStore = serde_json::from_reader(&mut file) -/// .expect("Failed to deserialize TokenStore"); -/// -/// let authenticator = flows::try_refresh_live_tokens_from_tokenstore(&mut ts) -/// .await -/// .expect("Failed refreshing Windows Live tokens"); -/// # Ok(()) -/// # } -/// ``` -/// -/// # Errors -/// -/// This function may return an error if the token store cannot be read or the tokens cannot be refreshed. -/// -/// # Returns -/// -/// If successful, a tuple of [`crate::XalAuthenticator`] and [`crate::tokenstore::TokenStore`] -/// is returned. TokenStore will contain the refreshed `live_tokens`. -pub async fn try_refresh_live_tokens_from_tokenstore( - ts: &mut TokenStore, -) -> Result { - let mut authenticator = Into::::into(ts.clone()); - - info!("Refreshing windows live tokens"); - let refreshed_wl_tokens = authenticator - .refresh_token(ts.live_token.refresh_token().unwrap()) - .await - .expect("Failed to exchange refresh token for fresh WL tokens"); - - debug!("Windows Live tokens: {:?}", refreshed_wl_tokens); - ts.live_token = refreshed_wl_tokens.clone(); - - Ok(authenticator) -} +impl Flows { + /// Try to deserialize a JSON TokenStore from filepath and refresh the Windows Live tokens if needed. + /// + /// # Errors + /// + /// This function may return an error if the file cannot be read, fails to deserialize or the + /// tokens cannot be refreshed. + /// + /// # Examples + /// + /// ``` + /// # use xal::{Error, Flows, TokenStore}; + /// + /// # async fn demo_code() -> Result<(), Error> { + /// // Refresh Windows Live tokens first + /// let (mut authenticator, token_store) = Flows::try_refresh_live_tokens_from_file("tokens.json") + /// .await?; + /// + /// // Continue by requesting xbox live tokens + /// let token_store = Flows::xbox_live_sisu_authorization_flow( + /// &mut authenticator, + /// token_store.live_token + /// ) + /// .await?; + /// + /// # Ok(()) + /// # } + /// ``` + /// + /// # Returns + /// + /// If successful, a tuple of [`crate::XalAuthenticator`] and [`crate::tokenstore::TokenStore`] + /// is returned. TokenStore will contain the refreshed `live_tokens`. + pub async fn try_refresh_live_tokens_from_file( + filepath: &str, + ) -> Result<(XalAuthenticator, TokenStore), Error> { + let mut ts = TokenStore::load_from_file(filepath)?; + let authenticator = Self::try_refresh_live_tokens_from_tokenstore(&mut ts).await?; + Ok((authenticator, ts)) + } -/// Shorthand for Windows Live device code flow -/// -/// # Examples -/// -/// ``` -/// use xal::{XalAuthenticator, flows, Error, AccessTokenPrefix}; -/// use xal::response::WindowsLiveTokens; -/// -/// # async fn async_sleep_fn(_: std::time::Duration) {} -/// -/// # async fn example() -> Result<(), Error> { -/// let do_implicit_flow = true; -/// let mut authenticator = XalAuthenticator::default(); -/// -/// let token_store = flows::ms_device_code_flow( -/// &mut authenticator, -/// flows::CliCallbackHandler, -/// async_sleep_fn -/// ) -/// .await?; -/// -/// // TokenStore will only contain live tokens -/// assert!(token_store.user_token.is_none()); -/// assert!(token_store.title_token.is_none()); -/// assert!(token_store.device_token.is_none()); -/// assert!(token_store.authorization_token.is_none()); -/// # Ok(()) -/// # } -/// ``` -pub async fn ms_device_code_flow( - authenticator: &mut XalAuthenticator, - cb: impl AuthPromptCallback, - sleep_fn: S, -) -> Result -where - S: Fn(std::time::Duration) -> SF, - SF: std::future::Future, -{ - trace!("Initiating device code flow"); - let device_code_flow = authenticator.initiate_device_code_auth().await?; - debug!("Device code={:?}", device_code_flow); - - trace!("Reaching into callback to notify caller about device code url"); - cb.call(device_code_flow.clone().into()) - .await - .map_err(|e| Error::GeneralError(format!("Failed getting redirect URL err={e}")))?; - - trace!("Polling for device code"); - let live_tokens = authenticator - .poll_device_code_auth(&device_code_flow, sleep_fn) - .await?; - - let ts = TokenStore { - app_params: authenticator.app_params(), - client_params: authenticator.client_params(), - sandbox_id: authenticator.sandbox_id(), - live_token: live_tokens, - user_token: None, - title_token: None, - device_token: None, - authorization_token: None, - updated: None, - }; - - Ok(ts) -} + /// Try to read tokens from the token store and refresh the Windows Live tokens if needed. + /// + /// # Examples + /// + /// ``` + /// use std::fs::File; + /// use serde_json; + /// use xal::{Flows, TokenStore}; + /// + /// # async fn demo_code() -> Result<(), xal::Error> { + /// let mut file = File::open("tokens.json") + /// .expect("Failed to open tokenfile"); + /// let mut ts: TokenStore = serde_json::from_reader(&mut file) + /// .expect("Failed to deserialize TokenStore"); + /// + /// let authenticator = Flows::try_refresh_live_tokens_from_tokenstore(&mut ts) + /// .await + /// .expect("Failed refreshing Windows Live tokens"); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// This function may return an error if the token store cannot be read or the tokens cannot be refreshed. + /// + /// # Returns + /// + /// If successful, a tuple of [`crate::XalAuthenticator`] and [`crate::TokenStore`] + /// is returned. TokenStore will contain the refreshed `live_tokens`. + pub async fn try_refresh_live_tokens_from_tokenstore( + ts: &mut TokenStore, + ) -> Result { + let mut authenticator = Into::::into(ts.clone()); + + info!("Refreshing windows live tokens"); + let refreshed_wl_tokens = authenticator + .refresh_token(ts.live_token.refresh_token().unwrap()) + .await + .expect("Failed to exchange refresh token for fresh WL tokens"); + + debug!("Windows Live tokens: {:?}", refreshed_wl_tokens); + ts.live_token = refreshed_wl_tokens.clone(); + + Ok(authenticator) + } -/// Shorthand for Windows Live authorization flow -/// - Depending on the argument `implicit` the -/// methods `implicit grant` or `authorization code` are chosen -/// -/// # Examples -/// -/// ``` -/// use xal::{XalAuthenticator, flows, Error, AccessTokenPrefix}; -/// use xal::response::WindowsLiveTokens; -/// -/// # async fn example() -> Result<(), Error> { -/// let do_implicit_flow = true; -/// let mut authenticator = XalAuthenticator::default(); -/// -/// let token_store = flows::ms_authorization_flow( -/// &mut authenticator, -/// flows::CliCallbackHandler, -/// do_implicit_flow, -/// ) -/// .await?; -/// -/// // TokenStore will only contain live tokens -/// assert!(token_store.user_token.is_none()); -/// assert!(token_store.title_token.is_none()); -/// assert!(token_store.device_token.is_none()); -/// assert!(token_store.authorization_token.is_none()); -/// # Ok(()) -/// # } -/// ``` -pub async fn ms_authorization_flow( - authenticator: &mut XalAuthenticator, - cb: impl AuthPromptCallback, - implicit: bool, -) -> Result { - trace!("Starting implicit authorization flow"); - - let (url, state) = authenticator.get_authorization_url(implicit)?; - - trace!("Reaching into callback to receive authentication redirect URL"); - let redirect_url = cb - .call(url.into()) - .await - .map_err(|e| Error::GeneralError(format!("Failed getting redirect URL err={e}")))? - .ok_or(Error::GeneralError( - "Failed receiving redirect URL".to_string(), - ))?; - - debug!("From callback: Redirect URL={:?}", redirect_url); - - let live_tokens = if implicit { - trace!("Parsing (implicit grant) redirect URI"); - XalAuthenticator::parse_implicit_grant_url(&redirect_url, Some(&state))? - } else { - trace!("Parsing (authorization code) redirect URI"); + /// Shorthand for Windows Live device code flow + /// + /// # Examples + /// + /// ``` + /// use xal::{XalAuthenticator, Flows, Error, AccessTokenPrefix, CliCallbackHandler}; + /// use xal::response::WindowsLiveTokens; + /// + /// # async fn async_sleep_fn(_: std::time::Duration) {} + /// + /// # async fn example() -> Result<(), Error> { + /// let do_implicit_flow = true; + /// let mut authenticator = XalAuthenticator::default(); + /// + /// let token_store = Flows::ms_device_code_flow( + /// &mut authenticator, + /// CliCallbackHandler, + /// async_sleep_fn + /// ) + /// .await?; + /// + /// // TokenStore will only contain live tokens + /// assert!(token_store.user_token.is_none()); + /// assert!(token_store.title_token.is_none()); + /// assert!(token_store.device_token.is_none()); + /// assert!(token_store.authorization_token.is_none()); + /// # Ok(()) + /// # } + /// ``` + pub async fn ms_device_code_flow( + authenticator: &mut XalAuthenticator, + cb: impl AuthPromptCallback, + sleep_fn: S, + ) -> Result + where + S: Fn(std::time::Duration) -> SF, + SF: std::future::Future, + { + trace!("Initiating device code flow"); + let device_code_flow = authenticator.initiate_device_code_auth().await?; + debug!("Device code={:?}", device_code_flow); + + trace!("Reaching into callback to notify caller about device code url"); + cb.call(device_code_flow.clone().into()) + .await + .map_err(|e| Error::GeneralError(format!("Failed getting redirect URL err={e}")))?; + + trace!("Polling for device code"); + let live_tokens = authenticator + .poll_device_code_auth(&device_code_flow, sleep_fn) + .await?; + + let ts = TokenStore { + app_params: authenticator.app_params(), + client_params: authenticator.client_params(), + sandbox_id: authenticator.sandbox_id(), + live_token: live_tokens, + user_token: None, + title_token: None, + device_token: None, + authorization_token: None, + updated: None, + }; + + Ok(ts) + } + + /// Shorthand for Windows Live authorization flow + /// - Depending on the argument `implicit` the + /// methods `implicit grant` or `authorization code` are chosen + /// + /// # Examples + /// + /// ``` + /// use xal::{XalAuthenticator, Flows, Error, AccessTokenPrefix, CliCallbackHandler}; + /// use xal::response::WindowsLiveTokens; + /// + /// # async fn example() -> Result<(), Error> { + /// let do_implicit_flow = true; + /// let mut authenticator = XalAuthenticator::default(); + /// + /// let token_store = Flows::ms_authorization_flow( + /// &mut authenticator, + /// CliCallbackHandler, + /// do_implicit_flow, + /// ) + /// .await?; + /// + /// // TokenStore will only contain live tokens + /// assert!(token_store.user_token.is_none()); + /// assert!(token_store.title_token.is_none()); + /// assert!(token_store.device_token.is_none()); + /// assert!(token_store.authorization_token.is_none()); + /// # Ok(()) + /// # } + /// ``` + pub async fn ms_authorization_flow( + authenticator: &mut XalAuthenticator, + cb: impl AuthPromptCallback, + implicit: bool, + ) -> Result { + trace!("Starting implicit authorization flow"); + + let (url, state) = authenticator.get_authorization_url(implicit)?; + + trace!("Reaching into callback to receive authentication redirect URL"); + let redirect_url = cb + .call(url.into()) + .await + .map_err(|e| Error::GeneralError(format!("Failed getting redirect URL err={e}")))? + .ok_or(Error::GeneralError( + "Failed receiving redirect URL".to_string(), + ))?; + + debug!("From callback: Redirect URL={:?}", redirect_url); + + let live_tokens = if implicit { + trace!("Parsing (implicit grant) redirect URI"); + XalAuthenticator::parse_implicit_grant_url(&redirect_url, Some(&state))? + } else { + trace!("Parsing (authorization code) redirect URI"); + let authorization_code = + XalAuthenticator::parse_authorization_code_response(&redirect_url, Some(&state))?; + debug!("Authorization Code: {:?}", &authorization_code); + + trace!("Getting Windows Live tokens (exchange code)"); + authenticator + .exchange_code_for_token(authorization_code, None) + .await? + }; + + let ts = TokenStore { + app_params: authenticator.app_params(), + client_params: authenticator.client_params(), + sandbox_id: authenticator.sandbox_id(), + live_token: live_tokens, + user_token: None, + title_token: None, + device_token: None, + authorization_token: None, + updated: None, + }; + + Ok(ts) + } + + /// Shorthand for sisu authentication flow + /// + /// # Examples + /// + /// ``` + /// use xal::{XalAuthenticator, Flows, Error, AccessTokenPrefix, CliCallbackHandler}; + /// use xal::response::WindowsLiveTokens; + /// + /// # async fn example() -> Result<(), Error> { + /// let mut authenticator = XalAuthenticator::default(); + /// + /// let token_store = Flows::xbox_live_sisu_full_flow( + /// &mut authenticator, + /// CliCallbackHandler, + /// ) + /// .await?; + /// + /// // TokenStore will contain user/title/device/xsts tokens + /// assert!(token_store.user_token.is_some()); + /// assert!(token_store.title_token.is_some()); + /// assert!(token_store.device_token.is_some()); + /// assert!(token_store.authorization_token.is_some()); + /// # Ok(()) + /// # } + /// ``` + pub async fn xbox_live_sisu_full_flow( + authenticator: &mut XalAuthenticator, + callback: impl AuthPromptCallback, + ) -> Result { + trace!("Getting device token"); + let device_token = authenticator.get_device_token().await?; + debug!("Device token={:?}", device_token); + let (code_challenge, code_verifier) = XalAuthenticator::generate_code_verifier(); + trace!("Generated Code verifier={:?}", code_verifier); + trace!("Generated Code challenge={:?}", code_challenge); + let state = XalAuthenticator::generate_random_state(); + trace!("Generated random state={:?}", state); + + trace!("Fetching SISU authentication URL and Session Id"); + let (auth_resp, session_id) = authenticator + .sisu_authenticate(&device_token, &code_challenge, &state) + .await?; + debug!( + "SISU Authenticate response={:?} Session Id={:?}", + auth_resp, session_id + ); + + // Passing redirect URL to callback and expecting redirect url + authorization token back + trace!("Reaching into callback to receive authentication redirect URL"); + let redirect_url = callback + .call(auth_resp.into()) + .await + .map_err(|e| Error::GeneralError(format!("Failed getting redirect URL err={e}")))? + .ok_or(Error::GeneralError( + "Did not receive any Redirect URL from RedirectUrl callback".to_string(), + ))?; + + debug!("From callback: Redirect URL={:?}", redirect_url); + + trace!("Parsing redirect URI"); let authorization_code = XalAuthenticator::parse_authorization_code_response(&redirect_url, Some(&state))?; - debug!("Authorization Code: {:?}", &authorization_code); + debug!("Authorization Code: {:?}", &authorization_code); trace!("Getting Windows Live tokens (exchange code)"); - authenticator - .exchange_code_for_token(authorization_code, None) - .await? - }; - - let ts = TokenStore { - app_params: authenticator.app_params(), - client_params: authenticator.client_params(), - sandbox_id: authenticator.sandbox_id(), - live_token: live_tokens, - user_token: None, - title_token: None, - device_token: None, - authorization_token: None, - updated: None, - }; - - Ok(ts) -} + let live_tokens = authenticator + .exchange_code_for_token(authorization_code, Some(code_verifier)) + .await?; + debug!("Windows live tokens={:?}", &live_tokens); -/// Shorthand for sisu authentication flow -/// -/// # Examples -/// -/// ``` -/// use xal::{XalAuthenticator, flows, Error, AccessTokenPrefix}; -/// use xal::response::WindowsLiveTokens; -/// -/// # async fn example() -> Result<(), Error> { -/// let mut authenticator = XalAuthenticator::default(); -/// -/// let token_store = flows::xbox_live_sisu_full_flow( -/// &mut authenticator, -/// flows::CliCallbackHandler, -/// ) -/// .await?; -/// -/// // TokenStore will contain user/title/device/xsts tokens -/// assert!(token_store.user_token.is_some()); -/// assert!(token_store.title_token.is_some()); -/// assert!(token_store.device_token.is_some()); -/// assert!(token_store.authorization_token.is_some()); -/// # Ok(()) -/// # } -/// ``` -pub async fn xbox_live_sisu_full_flow( - authenticator: &mut XalAuthenticator, - callback: impl AuthPromptCallback, -) -> Result { - trace!("Getting device token"); - let device_token = authenticator.get_device_token().await?; - debug!("Device token={:?}", device_token); - let (code_challenge, code_verifier) = XalAuthenticator::generate_code_verifier(); - trace!("Generated Code verifier={:?}", code_verifier); - trace!("Generated Code challenge={:?}", code_challenge); - let state = XalAuthenticator::generate_random_state(); - trace!("Generated random state={:?}", state); - - trace!("Fetching SISU authentication URL and Session Id"); - let (auth_resp, session_id) = authenticator - .sisu_authenticate(&device_token, &code_challenge, &state) - .await?; - debug!( - "SISU Authenticate response={:?} Session Id={:?}", - auth_resp, session_id - ); - - // Passing redirect URL to callback and expecting redirect url + authorization token back - trace!("Reaching into callback to receive authentication redirect URL"); - let redirect_url = callback - .call(auth_resp.into()) - .await - .map_err(|e| Error::GeneralError(format!("Failed getting redirect URL err={e}")))? - .ok_or(Error::GeneralError( - "Did not receive any Redirect URL from RedirectUrl callback".to_string(), - ))?; - - debug!("From callback: Redirect URL={:?}", redirect_url); - - trace!("Parsing redirect URI"); - let authorization_code = - XalAuthenticator::parse_authorization_code_response(&redirect_url, Some(&state))?; - - debug!("Authorization Code: {:?}", &authorization_code); - trace!("Getting Windows Live tokens (exchange code)"); - let live_tokens = authenticator - .exchange_code_for_token(authorization_code, Some(code_verifier)) - .await?; - debug!("Windows live tokens={:?}", &live_tokens); - - trace!("Getting Sisu authorization response"); - let sisu_resp = authenticator - .sisu_authorize(&live_tokens, &device_token, Some(session_id)) - .await?; - debug!("Sisu authorizatione response={:?}", sisu_resp); - - let ts = TokenStore { - app_params: authenticator.app_params(), - client_params: authenticator.client_params(), - sandbox_id: authenticator.sandbox_id(), - live_token: live_tokens, - device_token: Some(device_token), - user_token: Some(sisu_resp.user_token), - title_token: Some(sisu_resp.title_token), - authorization_token: Some(sisu_resp.authorization_token), - - updated: None, - }; - - Ok(ts) -} + trace!("Getting Sisu authorization response"); + let sisu_resp = authenticator + .sisu_authorize(&live_tokens, &device_token, Some(session_id)) + .await?; + debug!("Sisu authorizatione response={:?}", sisu_resp); + + let ts = TokenStore { + app_params: authenticator.app_params(), + client_params: authenticator.client_params(), + sandbox_id: authenticator.sandbox_id(), + live_token: live_tokens, + device_token: Some(device_token), + user_token: Some(sisu_resp.user_token), + title_token: Some(sisu_resp.title_token), + authorization_token: Some(sisu_resp.authorization_token), + + updated: None, + }; -/// Implements the traditional Xbox Live authorization flow. -/// -/// The method serves as a shorthand for executing the Xbox Live authorization flow by exchanging -/// [`crate::models::response::WindowsLiveTokens`] to ultimately acquire an authorized Xbox Live session. -/// -/// The authorization flow is designed to be highly modular, allowing for extensive customization -/// based on the specific needs of your application. -/// -/// # Arguments -/// -/// - `xsts_relying_party` XSTS Relying Party URL (see #Notes) -/// - `access_token_prefix` Whether AccessToken needs to be prefixed for the Xbox UserToken (XASU) Request (see #Notes). -/// - `request_title_token` Whether to request a Title Token (see #Notes) -/// -/// # Errors -/// -/// This method may return an error if any of the intermediate token requests fail. -/// For a more detailed explanation of the error, refer to the documentation of the -/// [`crate::XalAuthenticator`] methods. -/// -/// # Returns -/// -/// This method returns a `Result` containing a tuple with two elements: -/// -/// - The updated `XalAuthenticator` instance, with an incremented [`crate::cvlib::CorrelationVector`] -/// - A `TokenStore` struct, with all the tokens necessary exchanged during the authorization flow. -/// -/// # Examples -/// -/// ``` -/// use xal::{XalAuthenticator, flows, Error, AccessTokenPrefix}; -/// use xal::response::WindowsLiveTokens; -/// -/// # async fn async_sleep_fn(_: std::time::Duration) {} -/// -/// # async fn example() -> Result<(), Error> { -/// let mut authenticator = XalAuthenticator::default(); -/// -/// let token_store = flows::ms_device_code_flow( -/// &mut authenticator, -/// flows::CliCallbackHandler, -/// async_sleep_fn -/// ) -/// .await?; -/// -/// // Execute the Xbox Live authorization flow.. -/// let token_store = flows::xbox_live_authorization_traditional_flow( -/// &mut authenticator, -/// token_store.live_token, -/// "rp://api.minecraftservices.com/".to_string(), -/// AccessTokenPrefix::D, -/// true, -/// ) -/// .await?; -/// # Ok(()) -/// # } -/// ``` -/// -/// # Notes -/// -/// - Requesting a Title Token *standalone* aka. without sisu-flow only works for very few clients, -/// currently only "Minecraft" is known. -/// - Depending on the client an AccessToken prefix is necessary to have the User Token (XASU) request succeed -/// - Success of authorizing (device, user, ?title?) tokens for XSTS relies on the target relying party -pub async fn xbox_live_authorization_traditional_flow( - authenticator: &mut XalAuthenticator, - live_tokens: WindowsLiveTokens, - xsts_relying_party: String, - access_token_prefix: AccessTokenPrefix, - request_title_token: bool, -) -> Result { - debug!("Windows live tokens={:?}", &live_tokens); - trace!("Getting device token"); - let device_token = authenticator.get_device_token().await?; - debug!("Device token={:?}", device_token); - - trace!("Getting user token"); - let user_token = authenticator - .get_user_token(&live_tokens, access_token_prefix) - .await?; - - debug!("User token={:?}", user_token); - - let maybe_title_token = if request_title_token { - trace!("Getting title token"); - let title_token = authenticator - .get_title_token(&live_tokens, &device_token) + Ok(ts) + } + + /// Implements the traditional Xbox Live authorization flow. + /// + /// The method serves as a shorthand for executing the Xbox Live authorization flow by exchanging + /// [`crate::models::response::WindowsLiveTokens`] to ultimately acquire an authorized Xbox Live session. + /// + /// The authorization flow is designed to be highly modular, allowing for extensive customization + /// based on the specific needs of your application. + /// + /// # Arguments + /// + /// - `xsts_relying_party` XSTS Relying Party URL (see #Notes) + /// - `access_token_prefix` Whether AccessToken needs to be prefixed for the Xbox UserToken (XASU) Request (see #Notes). + /// - `request_title_token` Whether to request a Title Token (see #Notes) + /// + /// # Errors + /// + /// This method may return an error if any of the intermediate token requests fail. + /// For a more detailed explanation of the error, refer to the documentation of the + /// [`crate::XalAuthenticator`] methods. + /// + /// # Returns + /// + /// This method returns a `Result` containing a tuple with two elements: + /// + /// - The updated `XalAuthenticator` instance, with an incremented [`crate::cvlib::CorrelationVector`] + /// - A `TokenStore` struct, with all the tokens necessary exchanged during the authorization flow. + /// + /// # Examples + /// + /// ``` + /// use xal::{XalAuthenticator, Flows, CliCallbackHandler, Error, AccessTokenPrefix}; + /// use xal::response::WindowsLiveTokens; + /// + /// # async fn async_sleep_fn(_: std::time::Duration) {} + /// + /// # async fn example() -> Result<(), Error> { + /// let mut authenticator = XalAuthenticator::default(); + /// + /// let token_store = Flows::ms_device_code_flow( + /// &mut authenticator, + /// CliCallbackHandler, + /// async_sleep_fn + /// ) + /// .await?; + /// + /// // Execute the Xbox Live authorization flow.. + /// let token_store = Flows::xbox_live_authorization_traditional_flow( + /// &mut authenticator, + /// token_store.live_token, + /// "rp://api.minecraftservices.com/".to_string(), + /// AccessTokenPrefix::D, + /// true, + /// ) + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Notes + /// + /// - Requesting a Title Token *standalone* aka. without sisu-flow only works for very few clients, + /// currently only "Minecraft" is known. + /// - Depending on the client an AccessToken prefix is necessary to have the User Token (XASU) request succeed + /// - Success of authorizing (device, user, ?title?) tokens for XSTS relies on the target relying party + pub async fn xbox_live_authorization_traditional_flow( + authenticator: &mut XalAuthenticator, + live_tokens: WindowsLiveTokens, + xsts_relying_party: String, + access_token_prefix: AccessTokenPrefix, + request_title_token: bool, + ) -> Result { + debug!("Windows live tokens={:?}", &live_tokens); + trace!("Getting device token"); + let device_token = authenticator.get_device_token().await?; + debug!("Device token={:?}", device_token); + + trace!("Getting user token"); + let user_token = authenticator + .get_user_token(&live_tokens, access_token_prefix) .await?; - debug!("Title token={:?}", title_token); - - Some(title_token) - } else { - debug!("Skipping title token request.."); - None - }; - - trace!("Getting XSTS token"); - let authorization_token = authenticator - .get_xsts_token( - Some(&device_token), - maybe_title_token.as_ref(), - Some(&user_token), - &xsts_relying_party, - ) - .await?; - debug!("XSTS token={:?}", authorization_token); - - let ts = TokenStore { - app_params: authenticator.app_params(), - client_params: authenticator.client_params(), - sandbox_id: authenticator.sandbox_id(), - live_token: live_tokens, - device_token: Some(device_token), - user_token: Some(user_token), - title_token: maybe_title_token, - authorization_token: Some(authorization_token), - - updated: None, - }; - - Ok(ts) -} -/// The authorization part of Sisu -/// -/// # Examples -/// -/// ``` -/// use xal::{XalAuthenticator, flows, Error, AccessTokenPrefix}; -/// use xal::response::WindowsLiveTokens; -/// -/// # async fn async_sleep_fn(_: std::time::Duration) {} -/// -/// # async fn example() -> Result<(), Error> { -/// let mut authenticator = XalAuthenticator::default(); -/// -/// let token_store = flows::ms_device_code_flow( -/// &mut authenticator, -/// flows::CliCallbackHandler, -/// async_sleep_fn -/// ) -/// .await?; -/// -/// let token_store = flows::xbox_live_sisu_authorization_flow( -/// &mut authenticator, -/// token_store.live_token, -/// ) -/// .await?; -/// -/// // TokenStore will contain user/title/device/xsts tokens -/// assert!(token_store.user_token.is_some()); -/// assert!(token_store.title_token.is_some()); -/// assert!(token_store.device_token.is_some()); -/// assert!(token_store.authorization_token.is_some()); -/// # Ok(()) -/// # } -/// ``` -pub async fn xbox_live_sisu_authorization_flow( - authenticator: &mut XalAuthenticator, - live_tokens: WindowsLiveTokens, -) -> Result { - debug!("Windows live tokens={:?}", &live_tokens); - trace!("Getting device token"); - let device_token = authenticator.get_device_token().await?; - debug!("Device token={:?}", device_token); - - trace!("Getting user token"); - let resp = authenticator - .sisu_authorize(&live_tokens, &device_token, None) - .await?; - debug!("Sisu authorization response"); - - let ts = TokenStore { - app_params: authenticator.app_params(), - client_params: authenticator.client_params(), - sandbox_id: authenticator.sandbox_id(), - live_token: live_tokens, - device_token: Some(device_token), - user_token: Some(resp.user_token), - title_token: Some(resp.title_token), - authorization_token: Some(resp.authorization_token), - - updated: None, - }; - - Ok(ts) -} + debug!("User token={:?}", user_token); + + let maybe_title_token = if request_title_token { + trace!("Getting title token"); + let title_token = authenticator + .get_title_token(&live_tokens, &device_token) + .await?; + debug!("Title token={:?}", title_token); + + Some(title_token) + } else { + debug!("Skipping title token request.."); + None + }; + + trace!("Getting XSTS token"); + let authorization_token = authenticator + .get_xsts_token( + Some(&device_token), + maybe_title_token.as_ref(), + Some(&user_token), + &xsts_relying_party, + ) + .await?; + debug!("XSTS token={:?}", authorization_token); + + let ts = TokenStore { + app_params: authenticator.app_params(), + client_params: authenticator.client_params(), + sandbox_id: authenticator.sandbox_id(), + live_token: live_tokens, + device_token: Some(device_token), + user_token: Some(user_token), + title_token: maybe_title_token, + authorization_token: Some(authorization_token), + + updated: None, + }; + + Ok(ts) + } + + /// The authorization part of Sisu + /// + /// # Examples + /// + /// ``` + /// use xal::{XalAuthenticator, Flows, CliCallbackHandler, Error, AccessTokenPrefix}; + /// use xal::response::WindowsLiveTokens; + /// + /// # async fn async_sleep_fn(_: std::time::Duration) {} + /// + /// # async fn example() -> Result<(), Error> { + /// let mut authenticator = XalAuthenticator::default(); + /// + /// let token_store = Flows::ms_device_code_flow( + /// &mut authenticator, + /// CliCallbackHandler, + /// async_sleep_fn + /// ) + /// .await?; + /// + /// let token_store = Flows::xbox_live_sisu_authorization_flow( + /// &mut authenticator, + /// token_store.live_token, + /// ) + /// .await?; + /// + /// // TokenStore will contain user/title/device/xsts tokens + /// assert!(token_store.user_token.is_some()); + /// assert!(token_store.title_token.is_some()); + /// assert!(token_store.device_token.is_some()); + /// assert!(token_store.authorization_token.is_some()); + /// # Ok(()) + /// # } + /// ``` + pub async fn xbox_live_sisu_authorization_flow( + authenticator: &mut XalAuthenticator, + live_tokens: WindowsLiveTokens, + ) -> Result { + debug!("Windows live tokens={:?}", &live_tokens); + trace!("Getting device token"); + let device_token = authenticator.get_device_token().await?; + debug!("Device token={:?}", device_token); + + trace!("Getting user token"); + let resp = authenticator + .sisu_authorize(&live_tokens, &device_token, None) + .await?; + debug!("Sisu authorization response"); + + let ts = TokenStore { + app_params: authenticator.app_params(), + client_params: authenticator.client_params(), + sandbox_id: authenticator.sandbox_id(), + live_token: live_tokens, + device_token: Some(device_token), + user_token: Some(resp.user_token), + title_token: Some(resp.title_token), + authorization_token: Some(resp.authorization_token), + + updated: None, + }; + + Ok(ts) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 36ee563..8f6eefa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,13 +17,15 @@ pub use url; mod authenticator; mod error; +mod flows; mod models; mod request_signer; +mod tokenstore; pub mod extensions; -pub mod flows; -pub mod tokenstore; pub use authenticator::*; pub use error::Error; +pub use flows::*; pub use models::*; pub use request_signer::*; +pub use tokenstore::*; \ No newline at end of file diff --git a/src/request_signer.rs b/src/request_signer.rs index b337656..ed97468 100644 --- a/src/request_signer.rs +++ b/src/request_signer.rs @@ -431,6 +431,8 @@ impl RequestSigner { /// Get Xbox Live endpoint descriptions required for dynamically signing HTTP requests /// based on target domain / endpoint +/// +/// Can be used to instantiate [`SignaturePolicyCache`]. pub async fn get_endpoints() -> Result { let resp = reqwest::Client::new() .get(Constants::XBOX_TITLE_ENDPOINTS_URL)