#![doc = include_str!("../README.md")]
#![forbid(unsafe_code, missing_docs)]
#![warn(clippy::pedantic)]
pub(crate) mod async_trait_alias;
mod code;
mod errors;
mod mojang;
mod oauth;
mod xbox;
pub use mojang::AuthInfo as AuthData;
pub(crate) const SCOPE: &str = "XboxLive.signin%20XboxLive.offline_access";
pub(crate) const EXPERIEMNTAL_MESSAGE: &str = "\x1b[33mNOTICE: You are using and Experiemntal Feature.\x1b[0m";
pub struct Oauth {
url: String,
port: u16,
client_id: String,
}
pub struct DeviceCode {
url: String,
message: String,
expires_in: u32,
user_code: String,
device_code: String,
client_id: String,
}
#[cfg(feature = "renew")]
pub struct RefreshBearer {
refresh_token: String,
client_id: String,
port: u16,
client_secret: String,
}
impl Oauth {
pub fn new(clientid: &str, port: Option<u16>) -> Self {
let port = port.unwrap_or(8000);
const REQUEST_MODE: &str = "query";
const REQUEST_TYPE: &str = "code";
let params = format!("client_id={}&response_type={}&redirect_uri=http://localhost:{}&response_mode={}&scope={}&state=12345", clientid, REQUEST_TYPE, port, REQUEST_MODE, SCOPE);
let url = format!(
"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?{}",
params
);
Self {
url,
port,
client_id: clientid.to_string(),
}
}
pub fn url(&self) -> &str {
&self.url
}
pub async fn launch(
&self,
bedrockrelm: bool,
client_secret: &str,
) -> Result<AuthData, Box<dyn std::error::Error>> {
let http_server = oauth::server(self.port)?.await?;
let token = oauth::token(
http_server
.code
.expect("\x1b[31mXbox Expected code.\x1b[0m")
.as_str(),
&self.client_id,
self.port,
&client_secret,
)
.await?;
let xbox = xbox::xbl(&token.access_token).await?;
let xts = xbox::xsts_token(
&xbox.token,
bedrockrelm,
)
.await?;
if bedrockrelm == true {
return Ok(AuthData {
access_token: "null".to_string(),
uuid: "null".to_string(),
expires_in: 0,
xts_token: Some(xts.token),
});
} else {
return Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?);
}
}
}
impl DeviceCode {
pub fn new(
client_id: &str,
) -> impl async_trait_alias::AsyncSendSync<Result<Self, reqwest::Error>> {
println!(
"{}",
EXPERIEMNTAL_MESSAGE
);
let client_id_str = client_id.to_string();
async move {
let response_data = code::device_authentication_code(&client_id_str).await?;
Ok(Self {
url: response_data.verification_uri,
message: response_data.message,
expires_in: response_data.expires_in,
user_code: response_data.user_code,
device_code: response_data.device_code,
client_id: client_id_str,
})
}
}
pub fn prelaunch(&self) -> (&str, &str, u32, &str) {
(&self.url, &self.message, self.expires_in, &self.user_code)
}
pub async fn launch(&self, bedrockrelm: bool) -> Result<AuthData, Box<dyn std::error::Error>> {
let token = code::authenticate_device(&self.device_code, &self.client_id).await?;
let xbox = xbox::xbl(&token.token).await?;
let xts = xbox::xsts_token(&xbox.token, bedrockrelm).await?;
if bedrockrelm == true {
return Ok(AuthData {
access_token: "null".to_string(),
uuid: "null".to_string(),
expires_in: 0,
xts_token: Some(xts.token),
});
} else {
return Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?);
}
}
}
#[cfg(feature = "renew")]
impl RefreshBearer {
pub fn new(refresh_token: &str, client_id: &str, port: Option<u16>, client_secret: &str) -> Self {
let port = port.unwrap_or(8000);
Self {
refresh_token: refresh_token.to_string(),
client_id: client_id.to_string(),
port: port,
client_secret: client_secret.to_string(),
}
}
pub async fn launch_oauth(&self) {
let token = oauth::token(&self.refresh_token, &self.client_id, self.port, &self.client_secret);
}
pub async fn launch_devicecode(&self) {
println!(
"{}",
EXPERIEMNTAL_MESSAGE
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use dotenv_vault::dotenv;
use std::env;
#[tokio::test]
async fn test_oauth_url() {
let _ = dotenv();
let client_id = env::var("Client_ID").expect("Expected Client ID");
let oauth = Oauth::new(&client_id, None);
let params = format!("client_id={}&response_type=code&redirect_uri=http://localhost:8000&response_mode=query&scope={}&state=12345", client_id, SCOPE);
let expected_url = format!(
"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?{}",
params
);
assert_eq!(oauth.url(), expected_url);
}
#[tokio::test]
async fn test_device_code_prelaunch() {
let _ = dotenv();
let client_id = env::var("Client_ID").expect("Expected Client ID.");
let device_code = DeviceCode::new(&client_id).await.unwrap();
let (url, message, expires_in, user_code) = device_code.prelaunch();
assert_eq!(url, device_code.url);
assert_eq!(message, device_code.message);
assert_eq!(expires_in, device_code.expires_in);
assert_eq!(user_code, device_code.user_code);
}
}