diff options
| author | 2025-01-29 23:16:45 -0600 | |
|---|---|---|
| committer | 2025-01-29 23:16:45 -0600 | |
| commit | 35235e4a7ac90f4cdcdf753befb1aff516320eb4 (patch) | |
| tree | af53499a61bb41a8705073d30acdf8dbde2c120a /src | |
| parent | do device code auth (diff) | |
wip: use an actual oauth library
Diffstat (limited to 'src')
| -rw-r--r-- | src/auth.rs | 82 | ||||
| -rw-r--r-- | src/auth/msa.rs | 0 | ||||
| -rw-r--r-- | src/auth/types.rs | 9 |
3 files changed, 88 insertions, 3 deletions
diff --git a/src/auth.rs b/src/auth.rs index e7d8061..86776d4 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,14 +1,20 @@ mod types;
mod oauth;
+mod msa;
use std::error::Error;
use std::fmt::{Display, Formatter};
-use chrono::{DateTime, Utc};
+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};
pub use types::*;
#[derive(Debug)]
pub enum AuthError {
Request { what: &'static str, error: reqwest::Error },
+ OAuthRequestToken { what: &'static str, error: RequestTokenError<HttpClientError<reqwest::Error>, BasicErrorResponse> },
Internal(String),
Timeout
}
@@ -17,6 +23,7 @@ impl Display for AuthError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
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::Internal(msg) => write!(f, "internal auth error: {}", msg),
AuthError::Timeout => f.write_str("interactive authentication timed out")
}
@@ -27,6 +34,7 @@ impl Error for AuthError { fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
AuthError::Request { error, .. } => Some(error),
+ AuthError::OAuthRequestToken { error, .. } => Some(error),
_ => None
}
}
@@ -38,8 +46,78 @@ 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<BasicErrorResponse, BasicTokenResponse, _, _, _, _, _, _, _, _>
+ } 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<BasicErrorResponse, BasicTokenResponse, _, _, _, _, _, _, _, _>
+ }
+ }
+}
+
+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 {
- async fn log_in(&mut self) -> Result<(), AuthError> {
+ pub fn create_client(&self) -> reqwest::Client {
+ reqwest::ClientBuilder::new()
+ .redirect(reqwest::redirect::Policy::none())
+ .build().expect("building client should succeed")
+ }
+
+ fn scopes_iter(&self) -> impl Iterator<Item = Scope> {
+ let to_scope = |f: &&str| Scope::new(String::from(*f));
+
+ if self.is_azure_client_id {
+ AZURE_LOGIN_SCOPES.iter().map(to_scope)
+ } else {
+ NON_AZURE_LOGIN_SCOPES.iter().map(to_scope)
+ }
+ }
+
+ // 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 refresh_access_token(&mut self, client: reqwest::Client) -> Result<BasicTokenResponse, AuthError> {
+ let oauth_client = create_oauth_client!(self.is_azure_client_id);
+ 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)
+ .await.map_err(|e| AuthError::OAuthRequestToken { what: "refresh", error: e })?;
+
+ self.refresh_token = tokenres.refresh_token().cloned();
+
+ todo!()
+ }
+
+ // ensure we have an xbox live token for this person
+ // tasks for this function:
+ // - 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> {
+ todo!()
+ }
+
+ async fn log_in_silent(&mut self, client: reqwest::Client) -> Result<(), AuthError> {
+ let now: DateTime<Utc> = SystemTime::now().into();
+
+ if self.auth_token.as_ref().is_some_and(|tok| !tok.is_expired(now.add(TimeDelta::hours(12)))) {
+
+ }
+
todo!()
}
}
diff --git a/src/auth/msa.rs b/src/auth/msa.rs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/auth/msa.rs diff --git a/src/auth/types.rs b/src/auth/types.rs index 8889b63..79cf84f 100644 --- a/src/auth/types.rs +++ b/src/auth/types.rs @@ -1,5 +1,6 @@ use chrono::{DateTime, Utc};
use multimap::MultiMap;
+use oauth2::RefreshToken;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@@ -31,9 +32,15 @@ pub struct MsaUser { #[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<UserProfile>,
pub xuid: Uuid,
+
+ pub(super) client_id: oauth2::ClientId,
+
+ #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+ pub(super) is_azure_client_id: bool,
+
pub(super) auth_token: Option<Token>,
pub(super) xbl_token: Option<Token>,
- pub(super) refresh_token: Option<Token>
+ pub(super) refresh_token: Option<RefreshToken>
}
#[derive(Debug, Serialize, Deserialize)]
|
