1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::consts;
use crate::error::CobbleResult;
use crate::profile::error::ProfileError;
use crate::profile::microsoft::MicrosoftToken;
use crossbeam_channel::Receiver;
use oauth2::basic::BasicClient;
use oauth2::reqwest::http_client;
use oauth2::{AuthType, AuthUrl, ClientId, ClientSecret, RefreshToken, TokenUrl};
use serde::{Deserialize, Serialize};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::thread;
use time::OffsetDateTime;
use uuid::Uuid;
mod entitlements;
pub(crate) mod error;
mod microsoft;
mod minecraft;
mod minecraft_profile;
mod xbox_live;
mod xbox_live_security;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Profile {
pub uuid: Uuid,
pub username: String,
pub profile_id: String,
pub player_name: String,
pub microsoft_refresh_token: String,
pub minecraft_token: String,
#[serde(with = "time::serde::rfc3339")]
pub minecraft_token_exp: OffsetDateTime,
}
impl Profile {
pub fn authenticate(
client_id: String,
client_secret: String,
cancel: Arc<AtomicBool>,
) -> CobbleResult<(String, Receiver<CobbleResult<Self>>)> {
let (url, microsoft_token) = microsoft::authenticate(client_id, client_secret, cancel)?;
let (sender, receiver) = crossbeam_channel::unbounded();
thread::spawn(move || {
let result = perform_auth(microsoft_token);
sender.send(result).expect("Channel already closed");
});
Ok((url, receiver))
}
pub fn refresh(&mut self, client_id: String, client_secret: String) -> CobbleResult<()> {
let client_id = ClientId::new(client_id);
let client_secret = ClientSecret::new(client_secret);
let auth_url = AuthUrl::new(consts::MS_AUTH_CODE_URL.to_string()).unwrap();
let token_url = TokenUrl::new(consts::MS_AUTH_TOKEN_URL.to_string()).unwrap();
let refresh_token = RefreshToken::new(self.microsoft_refresh_token.clone());
let oauth_client =
BasicClient::new(client_id, Some(client_secret), auth_url, Some(token_url))
.set_auth_type(AuthType::RequestBody);
let microsoft_token = oauth_client
.exchange_refresh_token(&refresh_token)
.request(http_client)
.map(MicrosoftToken::from_token_response)
.map_err(|err| ProfileError::MicrosoftAuthenticate(err.to_string()))?;
let xbox_live_token = xbox_live::authenticate(µsoft_token.access_token)?;
let xbox_live_security_token =
xbox_live_security::authenticate(&xbox_live_token.access_token)?;
let user_hash = xbox_live_security_token
.user_hash
.ok_or(ProfileError::MissingUserHash)?;
let minecraft_token =
minecraft::authenticate(&xbox_live_security_token.access_token, &user_hash)?;
self.microsoft_refresh_token = microsoft_token.refresh_token;
self.minecraft_token = minecraft_token.access_token;
self.minecraft_token_exp = minecraft_token.expiration;
Ok(())
}
}
fn perform_auth(token_receiver: Receiver<CobbleResult<MicrosoftToken>>) -> CobbleResult<Profile> {
let microsoft_token = token_receiver
.recv()
.map_err(|err| ProfileError::MicrosoftAuthenticate(err.to_string()))??;
let xbox_live_token = xbox_live::authenticate(µsoft_token.access_token)?;
let xbox_live_security_token = xbox_live_security::authenticate(&xbox_live_token.access_token)?;
let user_hash = xbox_live_security_token
.user_hash
.ok_or(ProfileError::MissingUserHash)?;
let minecraft_token =
minecraft::authenticate(&xbox_live_security_token.access_token, &user_hash)?;
let entitlements = entitlements::get_entitlements(&minecraft_token.access_token)?;
if entitlements.entitlements.is_empty() {
return Err(ProfileError::Unauthorized.into());
}
let minecraft_profile = minecraft_profile::get_profile(&minecraft_token.access_token)?;
let profile = Profile {
uuid: Uuid::new_v4(),
username: minecraft_token.username,
profile_id: minecraft_profile.id,
player_name: minecraft_profile.name,
microsoft_refresh_token: microsoft_token.refresh_token,
minecraft_token: minecraft_token.access_token,
minecraft_token_exp: minecraft_token.expiration,
};
Ok(profile)
}