Skip to content
This repository was archived by the owner on Mar 14, 2026. It is now read-only.

Commit a13be82

Browse files
committed
feat: minecraft-auth partial support
fixup: form string
1 parent 9845ba8 commit a13be82

3 files changed

Lines changed: 154 additions & 72 deletions

File tree

crates/core/src/auth/microsoft.rs

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![forbid(unsafe_code)]
22
#![warn(clippy::pedantic)]
33

4+
use std::collections::HashMap;
5+
46
use crate::{errors::AuthErrors, trait_alias::*};
57
use reqwest::Client;
68
use serde::{Deserialize, Serialize};
@@ -154,10 +156,7 @@ fn parse_oauth(data: &[u8]) -> Result<OuathInfo, AuthErrors> {
154156
/// OAuth Auth Token Infomation.
155157
#[derive(Deserialize, Debug)]
156158
pub struct OuathToken {
157-
pub token_type: String,
158-
pub scope: String,
159159
pub expires_in: u16,
160-
pub ext_expires_in: u16,
161160
pub access_token: String,
162161
pub refresh_token: String,
163162
}
@@ -168,34 +167,38 @@ pub fn ouath_token(
168167
code: Option<&str>,
169168
client_id: &str,
170169
scope: &str,
171-
port: u16,
172-
client_secret: &str,
170+
redirect_uri: String,
171+
client_secret: Option<&str>,
173172
) -> impl AsyncSendSync<Result<OuathToken, AuthErrors>> {
174-
let url = format!("https://login.microsoftonline.com/consumers/oauth2/v2.0/token");
175-
let mut body = format!(
176-
"client_id={}&scope={}&client_secret={}",
177-
client_id, scope, client_secret
178-
);
173+
let url = if redirect_uri == "https://login.live.com/oauth20_desktop.srf" {
174+
"https://login.live.com/oauth20_token.srf"
175+
} else {
176+
"https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
177+
};
178+
179+
// Use owned Strings for both keys and values
180+
let mut form = HashMap::new();
181+
form.insert("client_id".to_string(), client_id.to_string());
182+
form.insert("scope".to_string(), scope.to_string());
183+
184+
if let Some(secret) = client_secret {
185+
form.insert("client_secret".to_string(), secret.to_string());
186+
}
179187

180188
if let Some(token) = refresh_token {
181-
body.push_str(&format!(
182-
"&grant_type=refresh_token&refresh_token={}",
183-
token
184-
));
189+
form.insert("grant_type".to_string(), "refresh_token".to_string());
190+
form.insert("refresh_token".to_string(), token); // token is already a String
185191
} else if let Some(auth_code) = code {
186-
let redirect_uri = format!("http://localhost:{}", port);
187-
body.push_str(&format!(
188-
"&grant_type=authorization_code&code={}&redirect_uri={}",
189-
auth_code, redirect_uri
190-
));
191-
};
192+
form.insert("grant_type".to_string(), "authorization_code".to_string());
193+
form.insert("code".to_string(), auth_code.to_string());
194+
form.insert("redirect_uri".to_string(), redirect_uri);
195+
}
192196

193197
async move {
194198
'out: {
195-
let result = client.post(url).body(body).send().await;
199+
let result = client.post(url).form(&form).send().await;
196200

197201
let std::result::Result::Ok(response) = result else {
198-
println!("Part 1");
199202
break 'out Err(AuthErrors::ResponseError(
200203
"Failed to send request".to_string(),
201204
));
@@ -205,7 +208,10 @@ pub fn ouath_token(
205208
.text()
206209
.await
207210
.map_err(|_| AuthErrors::ResponseError("Failed to send request".to_string()))?;
208-
let std::result::Result::Ok(token) = serde_json::from_str::<OuathToken>(&text) else {
211+
212+
println!("Form: {:?}, Text: {}", form, text);
213+
214+
let std::result::Result::Ok(token) = serde_json::from_str(&text) else {
209215
break 'out Err(AuthErrors::ResponseError(
210216
"Failed to send request, Check your Client Secret.".to_string(),
211217
));

crates/minecraft-essentails/src/lib.rs

Lines changed: 119 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub(crate) const EXPERIMENTAL_MESSAGE: &str =
5858
#[cfg(feature = "launch")]
5959
pub(crate) const MANIFEST_URL: &str =
6060
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
61+
#[cfg(feature = "auth")]
62+
pub(crate) const LAUNCHER_CLIENT_ID: &str = "00000000402B5328";
6163

6264
/// OAuth 2.0 Authentication
6365
///
@@ -112,7 +114,7 @@ impl Oauth {
112114
let mut builder = AuthenticationBuilder::builder();
113115
builder
114116
.port(self.port)
115-
.client_id(&self.client_id)
117+
.client_id(Some(&self.client_id))
116118
.of_type(AuthType::Oauth);
117119
let auth_info = builder
118120
.get_info()
@@ -144,10 +146,10 @@ impl Oauth {
144146
AuthenticationBuilder::builder()
145147
.bedrockrel(Some(bedrock_relm))
146148
.of_type(AuthType::Oauth)
147-
.client_secret(client_secret)
148-
.client_id(&self.client_id)
149+
.client_secret(Some(client_secret))
150+
.client_id(Some(&self.client_id))
149151
.port(Some(self.port))
150-
.launch()
152+
.launch(None, None)
151153
.await
152154
}
153155

@@ -216,6 +218,11 @@ pub enum AuthType {
216218
/// This is used for refreshing your token with Minecraft.
217219
#[cfg(feature = "auth")]
218220
Refresh,
221+
222+
/// Minecraft authentification method
223+
///
224+
/// Uses Minecraft Auth menthod (habdy if you want the minecraft background)
225+
Minecraft,
219226
}
220227

221228
/// Represents a builder for authentication configurations.
@@ -226,10 +233,10 @@ pub enum AuthType {
226233
#[cfg(feature = "auth")]
227234
pub struct AuthenticationBuilder {
228235
auth_type: AuthType,
229-
client_id: String,
236+
client_id: Option<String>,
230237
scope: Option<String>,
231-
port: u16,
232-
client_secrect: String,
238+
port: Option<u16>,
239+
client_secret: Option<String>,
233240
bedrockrel: bool,
234241
refresh_token: Option<String>,
235242
}
@@ -244,6 +251,8 @@ pub struct AuthInfo {
244251
pub device_code: Option<CodeResponse>,
245252
/// OAuth URL that you will recive based on AuthType::OAuth.
246253
pub ouath_url: Option<String>,
254+
/// Redirect URL: Only needed when using minecraft auth
255+
pub redirect_url: Option<String>,
247256
}
248257

249258
#[cfg(feature = "auth")]
@@ -252,10 +261,10 @@ impl AuthenticationBuilder {
252261
pub fn builder() -> Self {
253262
Self {
254263
auth_type: AuthType::Oauth,
255-
client_id: "".to_string(),
264+
client_id: None,
256265
scope: Some(SCOPE.to_string()),
257-
port: 8000,
258-
client_secrect: "".to_string(),
266+
port: Some(8000),
267+
client_secret: None,
259268
bedrockrel: false,
260269
refresh_token: None,
261270
}
@@ -270,24 +279,28 @@ impl AuthenticationBuilder {
270279
}
271280

272281
/// Client ID from your application Required for `OAuth` & `DeviceCode`.
273-
pub fn client_id(&mut self, client_id: &str) -> &mut Self {
274-
self.client_id = client_id.to_string();
282+
pub fn client_id(&mut self, client_id: Option<&str>) -> &mut Self {
283+
if client_id.is_some() {
284+
self.client_id = client_id.map(|id| id.to_string());
285+
}
275286
self
276287
}
277288

278289
/// Port for the Temporary https Required for `OAuth``.
279290
pub fn port<T: Optional<u16>>(&mut self, port: T) -> &mut Self {
280291
let port = match port.into() {
281-
Some(port) => port,
282-
None => 8000,
292+
Some(port) => Some(port),
293+
None => None,
283294
};
284295
self.port = port;
285296
self
286297
}
287298

288299
/// Client Secret from your application Required for `OAuth` & `DeviceCode`.
289-
pub fn client_secret(&mut self, client_secret: &str) -> &mut Self {
290-
self.client_secrect = client_secret.to_string();
300+
pub fn client_secret(&mut self, client_secret: Option<&str>) -> &mut Self {
301+
if client_secret.is_some() {
302+
self.client_secret = client_secret.map(|secret| secret.to_string());
303+
}
291304
self
292305
}
293306

@@ -324,36 +337,63 @@ impl AuthenticationBuilder {
324337
/// Gets the code for device code method
325338
pub async fn get_info(&mut self) -> AuthInfo {
326339
let client = Client::new();
327-
if self.auth_type == AuthType::DeviceCode {
328-
let code = device_authentication_code(client, &self.client_id)
329-
.await
330-
.unwrap();
331-
AuthInfo {
332-
device_code: Some(code),
333-
ouath_url: None,
340+
341+
match self.auth_type {
342+
AuthType::DeviceCode => {
343+
let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID");
344+
let code = device_authentication_code(client, client_id).await.unwrap();
345+
AuthInfo {
346+
device_code: Some(code),
347+
ouath_url: None,
348+
redirect_url: None,
349+
}
334350
}
335-
} else {
336-
let url = format!(
337-
"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345",
338-
self.client_id, self.port, SCOPE
339-
);
340-
AuthInfo {
341-
device_code: None,
342-
ouath_url: Some(url),
351+
AuthType::Oauth | AuthType::Refresh => {
352+
let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID");
353+
let url = format!(
354+
"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345",
355+
client_id,
356+
self.port.expect("EXPECTED PORT"),
357+
self.scope.clone().expect("Expected Scope")
358+
);
359+
AuthInfo {
360+
device_code: None,
361+
ouath_url: Some(url),
362+
redirect_url: None,
363+
}
364+
}
365+
AuthType::Minecraft => {
366+
let redirect_url = "https://login.live.com/oauth20_desktop.srf";
367+
let url = format!(
368+
"https://login.live.com/oauth20_authorize.srf?client_id={LAUNCHER_CLIENT_ID}&redirect_uri={}&response_type=code&scope=service::user.auth.xboxlive.com::MBI_SSL",
369+
redirect_url
370+
);
371+
372+
AuthInfo {
373+
device_code: None,
374+
ouath_url: Some(url),
375+
redirect_url: Some(redirect_url.to_string()),
376+
}
343377
}
344378
}
345379
}
346380

347381
/// Launchs the authentication process.
348-
pub async fn launch(&mut self) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
382+
pub async fn launch(
383+
&mut self,
384+
code: Option<&str>,
385+
redirect_url: Option<String>,
386+
) -> Result<CustomAuthData, Box<dyn std::error::Error>> {
349387
dbg!(&self.auth_type, &self.client_id);
350388
let client = Client::new();
351389

352390
match self.auth_type {
353391
AuthType::Oauth => {
354-
dbg!(&self.client_secrect, self.port);
355-
print!("{}", self.client_id);
356-
let server = ouath(self.port)?.await?;
392+
dbg!(&self.client_secret, self.port, &self.client_secret);
393+
let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID");
394+
let client_secret = &self.client_secret.clone().expect("EXPECTED CLIENT_ID");
395+
396+
let server = ouath(self.port.expect("EXPECTED PORT"))?.await?;
357397
let server_token = ouath_token(
358398
client.clone(),
359399
None,
@@ -363,10 +403,10 @@ impl AuthenticationBuilder {
363403
.expect("\x1b[31mXbox Expected code.\x1b[0m")
364404
.as_str(),
365405
),
366-
&self.client_id,
406+
client_id,
367407
self.scope.as_deref().unwrap_or_default(),
368-
self.port,
369-
&self.client_secrect,
408+
format!("https://localhost:{}", self.port.expect("EXPECTED PORT")),
409+
Some(client_secret),
370410
)
371411
.await?;
372412
let xbl = xbl(client.clone(), &server_token.access_token).await?;
@@ -384,10 +424,12 @@ impl AuthenticationBuilder {
384424
}
385425
}
386426
AuthType::DeviceCode => {
427+
let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID");
428+
387429
print!("{} \n Status: WIP (Work In Progress)", EXPERIMENTAL_MESSAGE);
388-
let code = device_authentication_code(client.clone(), &self.client_id).await?;
430+
let code = device_authentication_code(client.clone(), client_id).await?;
389431
let code_token =
390-
authenticate_device(client.clone(), &code.device_code, &self.client_id).await?;
432+
authenticate_device(client.clone(), &code.device_code, client_id).await?;
391433
let xbl = xbl(client.clone(), &code_token.token).await?;
392434
let xts = xsts(client.clone(), &xbl.token, self.bedrockrel).await?;
393435

@@ -403,6 +445,9 @@ impl AuthenticationBuilder {
403445
}
404446
}
405447
AuthType::Refresh => {
448+
let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID");
449+
let client_secret = &self.client_secret.clone().expect("EXPECTED CLIENT_ID");
450+
406451
if self.refresh_token == None {
407452
return Err(Box::new(errors::AuthErrors::AuthenticationFailure(
408453
"Missing Refresh Token".to_string(),
@@ -412,10 +457,41 @@ impl AuthenticationBuilder {
412457
client.clone(),
413458
self.refresh_token.clone(),
414459
None,
415-
&self.client_id,
460+
client_id,
416461
self.scope.as_deref().unwrap_or_default(),
417-
self.port,
418-
&self.client_secrect,
462+
if self.port.is_some() {
463+
format!("https://localhost:{}", self.port.expect("EXPECTED PORT"))
464+
} else {
465+
redirect_url.expect("EXPECTED REDIRECT URL")
466+
},
467+
Some(client_secret),
468+
)
469+
.await?;
470+
let xbl = xbl(client.clone(), &server_token.access_token).await?;
471+
let xts = xsts(client.clone(), &xbl.token, self.bedrockrel).await?;
472+
473+
if self.bedrockrel {
474+
Ok(CustomAuthData {
475+
access_token: None,
476+
uuid: None,
477+
expires_in: 0,
478+
xts_token: Some(xts.token),
479+
})
480+
} else {
481+
Ok(bearer_token(client, &xbl.display_claims.xui[0].uhs, &xts.token).await?)
482+
}
483+
}
484+
AuthType::Minecraft => {
485+
dbg!(self.port);
486+
487+
let server_token = ouath_token(
488+
client.clone(),
489+
None,
490+
code,
491+
LAUNCHER_CLIENT_ID,
492+
"service::user.auth.xboxlive.com::MBI_SSL",
493+
redirect_url.expect("EXPECTED REDIRECT URL"),
494+
None,
419495
)
420496
.await?;
421497
let xbl = xbl(client.clone(), &server_token.access_token).await?;

0 commit comments

Comments
 (0)