From 35235e4a7ac90f4cdcdf753befb1aff516320eb4 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Wed, 29 Jan 2025 23:16:45 -0600 Subject: wip: use an actual oauth library --- src/auth.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) (limited to 'src/auth.rs') 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, 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 + } 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 + } + } +} + +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 { + 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 { + 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 = SystemTime::now().into(); + + if self.auth_token.as_ref().is_some_and(|tok| !tok.is_expired(now.add(TimeDelta::hours(12)))) { + + } + todo!() } } -- cgit v1.2.3-70-g09d2