From ead933d2207825d4dd292e980aacefe6cb8bf504 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Thu, 30 Jan 2025 12:09:37 -0600 Subject: non-working: auth --- src/auth.rs | 99 +++++++++++++++++++++++++++++++++++++++---------------- src/auth/msa.rs | 33 +++++++++++++++++++ src/auth/types.rs | 2 +- 3 files changed, 104 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index 86776d4..0bae4e1 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -4,18 +4,21 @@ mod msa; use std::error::Error; use std::fmt::{Display, Formatter}; +use std::future::Future; use std::ops::Add; use std::time::{Duration, Instant, SystemTime}; use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc}; -use oauth2::{AccessToken, AuthType, AuthUrl, ClientId, DeviceAuthorizationUrl, HttpClientError, RefreshToken, RequestTokenError, Scope, StandardTokenResponse, TokenResponse, TokenUrl}; -use oauth2::basic::{BasicErrorResponse, BasicTokenResponse}; +use oauth2::{AccessToken, AuthType, AuthUrl, ClientId, DeviceAuthorizationResponse, DeviceAuthorizationUrl, DeviceCodeErrorResponse, EmptyExtraDeviceAuthorizationFields, EndpointNotSet, EndpointSet, HttpClientError, RefreshToken, RequestTokenError, Scope, StandardDeviceAuthorizationResponse, StandardRevocableToken, StandardTokenResponse, TokenResponse, TokenUrl}; +use oauth2::basic::{BasicErrorResponse, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse, BasicTokenResponse}; pub use types::*; #[derive(Debug)] pub enum AuthError { Request { what: &'static str, error: reqwest::Error }, OAuthRequestToken { what: &'static str, error: RequestTokenError, BasicErrorResponse> }, + OAuthRequestDeviceCode { what: &'static str, error: RequestTokenError, DeviceCodeErrorResponse> }, Internal(String), + Cancel(Option>), Timeout } @@ -24,7 +27,10 @@ impl Display for AuthError { match self { AuthError::Request { what, error } => write!(f, "auth request error ({}): {}", what, error), AuthError::OAuthRequestToken { what, error } => write!(f, "oauth error requesting token ({what}): {error}"), + AuthError::OAuthRequestDeviceCode { what, error } => write!(f, "oauth error with device code ({what}): {error}"), AuthError::Internal(msg) => write!(f, "internal auth error: {}", msg), + AuthError::Cancel(Some(error)) => write!(f, "operation cancelled: {error}"), + AuthError::Cancel(None) => f.write_str("operation cancelled"), AuthError::Timeout => f.write_str("interactive authentication timed out") } } @@ -35,6 +41,8 @@ impl Error for AuthError { match self { AuthError::Request { error, .. } => Some(error), AuthError::OAuthRequestToken { error, .. } => Some(error), + AuthError::OAuthRequestDeviceCode { error, .. } => Some(error), + AuthError::Cancel(Some(error)) => Some(error.as_ref()), _ => None } } @@ -47,26 +55,25 @@ impl Token { } macro_rules! create_oauth_client { - ($is_azure_client_id:expr) => { - if is_azure_client_id { - oauth2::Client::new(self.client_id.clone()) - .set_token_uri(TokenUrl::new("https://login.microsoftonline.com/consumers/oauth2/v2.0/token".into()).expect("hardcoded url")) - .set_device_authorization_url(DeviceAuthorizationUrl::new("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode".into()).expect("hardcoded url")) - as oauth2::Client - } else { - oauth2::Client::new(self.client_id.clone()) - .set_token_uri(TokenUrl::new("https://login.live.com/oauth20_token.srf".into()).expect("hardcoded url")) - .set_device_authorization_url(DeviceAuthorizationUrl::new("https://login.live.com/oauth20_connect.srf".into()).expect("hardcoded url")) - as oauth2::Client - } + ($is_azure_client_id:expr, $client_id:expr) => { + oauth2::Client::new($client_id) + .set_token_uri(TokenUrl::new(if $is_azure_client_id { AZURE_TOKEN_URL.into() } else { NON_AZURE_TOKEN_URL.into() }).expect("hardcoded url")) + .set_device_authorization_url(DeviceAuthorizationUrl::new(if $is_azure_client_id { AZURE_TOKEN_URL.into() } else { NON_AZURE_TOKEN_URL.into() }).expect("hardcoded url")) + as oauth2::Client } } +const AZURE_TOKEN_URL: &str = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; +const AZURE_DEVICE_CODE_URL: &str = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; +const NON_AZURE_TOKEN_URL: &str = "https://login.live.com/oauth20_token.srf"; +const NON_AZURE_DEVICE_CODE_URL: &str = "https://login.live.com/oauth20_connect.srf"; + const AZURE_LOGIN_SCOPES: &[&str] = ["XboxLive.signin", "offline_access"].as_slice(); const NON_AZURE_LOGIN_SCOPES: &[&str] = ["service::user.auth.xboxlive.com::MBI_SSL"].as_slice(); impl MsaUser { - pub fn create_client(&self) -> reqwest::Client { + pub fn create_client() -> reqwest::Client { reqwest::ClientBuilder::new() .redirect(reqwest::redirect::Policy::none()) .build().expect("building client should succeed") @@ -83,23 +90,47 @@ impl MsaUser { } // uses an access token from, for example, a device code grant logs into xbox live - async fn xbl_log_in(&mut self, token: &AccessToken) { + async fn xbl_login(&mut self, token: &AccessToken) -> Result<(), AuthError> { } - async fn refresh_access_token(&mut self, client: reqwest::Client) -> Result { - let oauth_client = create_oauth_client!(self.is_azure_client_id); + // logs into xbox live using a refresh token + // (panics if no refresh token present) + async fn xbl_login_refresh(&mut self, client: &reqwest::Client) -> Result<(), AuthError> { + let oauth_client = create_oauth_client!(self.is_azure_client_id, self.client_id.clone()); let refresh_token = self.refresh_token.as_ref().expect("refresh_access_token called with no refresh token"); let tokenres: BasicTokenResponse = oauth_client .exchange_refresh_token(refresh_token) .add_scopes(self.scopes_iter()) - .request_async(&client) + .request_async(client) .await.map_err(|e| AuthError::OAuthRequestToken { what: "refresh", error: e })?; self.refresh_token = tokenres.refresh_token().cloned(); - todo!() + self.xbl_login(tokenres.access_token()).await + } + + async fn xbl_login_device(&mut self, client: &reqwest::Client, handle_device: D) -> Result<(), AuthError> + where + D: FnOnce(&StandardDeviceAuthorizationResponse) -> DF, + DF: Future + { + let oauth_client = create_oauth_client!(self.is_azure_client_id, self.client_id.clone()); + let device_auth: StandardDeviceAuthorizationResponse = oauth_client.exchange_device_code().add_scopes(self.scopes_iter()) + .request_async(client) + .await.map_err(|e| AuthError::OAuthRequestToken { what: "device code", error: e })?; + + handle_device(&device_auth).await; + + let tokenres = oauth_client.exchange_device_access_token(&device_auth) + .set_max_backoff_interval(Duration::from_secs(20u64)) + .request_async(client, tokio::time::sleep, None) + .await.map_err(|e| AuthError::OAuthRequestDeviceCode { what: "device access code", error: e })?; + + self.refresh_token = tokenres.refresh_token().cloned(); + + self.xbl_login(tokenres.access_token()).await } // ensure we have an xbox live token for this person @@ -107,11 +138,11 @@ impl MsaUser { // - check if the XBL token is valid/not expired // - if it is expired, try to use refresh token to get a new one // - get rid of auth token if yeah - async fn ensure_xbl(&mut self, client: reqwest::Client) -> Result<(), AuthError> { + async fn ensure_xbl(&mut self, client: &reqwest::Client) -> Result<(), AuthError> { todo!() } - async fn log_in_silent(&mut self, client: reqwest::Client) -> Result<(), AuthError> { + async fn log_in_silent(&mut self, client: &reqwest::Client) -> Result<(), AuthError> { let now: DateTime = SystemTime::now().into(); if self.auth_token.as_ref().is_some_and(|tok| !tok.is_expired(now.add(TimeDelta::hours(12)))) { @@ -131,12 +162,22 @@ mod test { #[tokio::test] async fn abc() { simple_logger::SimpleLogger::new().with_colors(true).with_level(log::LevelFilter::Trace).init().unwrap(); - - _=device_code::DeviceCodeAuthBuilder::new() - .client_id("00000000402b5328") - .add_scope("service::user.auth.xboxlive.com::MBI_SSL", true) - .code_request_url("https://login.live.com/oauth20_connect.srf") - .check_url("https://login.live.com/oauth20_token.srf") - .begin(Client::new()).await.unwrap().drive().await; + + let mut user: MsaUser = MsaUser { + profile: None, + xuid: None, + client_id: ClientId::new("00000000402b5328".into()), + is_azure_client_id: false, + auth_token: None, + xbl_token: None, + refresh_token: None + }; + + let client = MsaUser::create_client(); + + user.xbl_login_device(&client, |d| { + dbg!(d); + futures::future::ready(()) + }).await.unwrap(); } } diff --git a/src/auth/msa.rs b/src/auth/msa.rs index e69de29..da9a376 100644 --- a/src/auth/msa.rs +++ b/src/auth/msa.rs @@ -0,0 +1,33 @@ +use chrono::{DateTime, Utc}; +use oauth2::AccessToken; +use serde::{Deserialize, Serialize}; +use crate::auth::AuthError; + +const XBOX_LIVE_AUTH: &str = "https://user.auth.xboxlive.com/user/authenticate"; + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +struct XboxLiveAuthRequestProperties<'a> { + auth_method: &'a str, + site_name: &'a str, + rps_ticket: &'a str +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +struct XboxLiveAuthRequest<'a> { + properties: XboxLiveAuthRequestProperties<'a>, + relying_party: &'a str, + token_type: &'a str +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct XboxLiveAuthResponse { + token: String, + not_after: DateTime +} + +pub fn xbox_live_login(client: &reqwest::Client, access_token: &AccessToken) -> Result<(), AuthError> { + +} \ No newline at end of file diff --git a/src/auth/types.rs b/src/auth/types.rs index 79cf84f..f455657 100644 --- a/src/auth/types.rs +++ b/src/auth/types.rs @@ -31,7 +31,7 @@ pub(super) struct Token { pub struct MsaUser { #[serde(skip_serializing_if = "Option::is_none")] pub profile: Option, - pub xuid: Uuid, + pub xuid: Option, pub(super) client_id: oauth2::ClientId, -- cgit v1.2.3-70-g09d2