1use azalea_auth::{
2 AccessTokenResponse,
3 certs::Certificates,
4 sessionserver::{self, ClientSessionServerError, SessionServerJoinOpts},
5};
6use parking_lot::Mutex;
7use uuid::Uuid;
8
9use crate::account::{Account, AccountTrait, BoxFuture};
10
11#[derive(Debug)]
16pub struct MicrosoftAccount {
17 cache_key: String,
18
19 username: String,
20 uuid: Uuid,
21
22 access_token: Mutex<String>,
23 certs: Mutex<Option<Certificates>>,
24}
25impl MicrosoftAccount {
26 async fn new(
29 cache_key: &str,
30 client_id: Option<&str>,
31 scope: Option<&str>,
32 ) -> Result<Self, azalea_auth::AuthError> {
33 let minecraft_dir = minecraft_folder_path::minecraft_dir().unwrap_or_else(|| {
34 panic!(
35 "No {} environment variable found",
36 minecraft_folder_path::home_env_var()
37 )
38 });
39 let auth_result = azalea_auth::auth(
40 cache_key,
41 azalea_auth::AuthOpts {
42 cache_file: Some(minecraft_dir.join("azalea-auth.json")),
43 client_id,
44 scope,
45 ..Default::default()
46 },
47 )
48 .await?;
49
50 Ok(Self {
51 cache_key: cache_key.to_owned(),
52 username: auth_result.profile.name,
53 uuid: auth_result.profile.id,
54 access_token: Mutex::new(auth_result.access_token),
55 certs: Mutex::new(None),
56 })
57 }
58}
59impl AccountTrait for MicrosoftAccount {
60 fn username(&self) -> &str {
61 &self.username
62 }
63 fn uuid(&self) -> Uuid {
64 self.uuid
65 }
66 fn access_token(&self) -> Option<String> {
67 Some(self.access_token.lock().to_owned())
68 }
69 fn certs(&self) -> Option<azalea_auth::certs::Certificates> {
70 self.certs.lock().as_ref().cloned()
71 }
72 fn set_certs(&self, certs: azalea_auth::certs::Certificates) {
73 *self.certs.lock() = Some(certs);
74 }
75 fn refresh(&self) -> BoxFuture<'_, Result<(), azalea_auth::AuthError>> {
76 Box::pin(async {
77 let new_account = MicrosoftAccount::new(&self.cache_key, None, None).await?;
78 let new_access_token = new_account.access_token().unwrap();
79 *self.access_token.lock() = new_access_token;
80 Ok(())
81 })
82 }
83 fn join<'a>(
84 &'a self,
85 public_key: &'a [u8],
86 private_key: &'a [u8; 16],
87 server_id: &'a str,
88 proxy: Option<reqwest::Proxy>,
89 ) -> BoxFuture<'a, Result<(), ClientSessionServerError>> {
90 Box::pin(async move {
91 let access_token = self.access_token.lock().clone();
92 sessionserver::join(SessionServerJoinOpts {
93 access_token: &access_token,
94 public_key,
95 private_key,
96 uuid: &self.uuid(),
97 server_id,
98 proxy,
99 })
100 .await
101 })
102 }
103}
104
105#[derive(Debug)]
114pub struct MicrosoftWithAccessTokenAccount {
115 msa: Mutex<azalea_auth::cache::ExpiringValue<AccessTokenResponse>>,
116
117 username: String,
118 uuid: Uuid,
119
120 access_token: Mutex<String>,
121 certs: Mutex<Option<Certificates>>,
122}
123impl MicrosoftWithAccessTokenAccount {
124 async fn new(
125 msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
126 client_id: Option<&str>,
127 scope: Option<&str>,
128 ) -> Result<Self, azalea_auth::AuthError> {
129 let client = reqwest::Client::new();
130
131 let mut msa = msa.clone();
132
133 if msa.is_expired() {
134 use tracing::trace;
135
136 trace!("refreshing Microsoft auth token");
137 msa = azalea_auth::refresh_ms_auth_token(
138 &client,
139 &msa.data.refresh_token,
140 client_id,
141 scope,
142 )
143 .await?;
144 }
145
146 let msa_token = &msa.data.access_token;
147 let res = azalea_auth::get_minecraft_token(&client, msa_token).await?;
148 let profile = azalea_auth::get_profile(&client, &res.minecraft_access_token).await?;
149
150 Ok(Self {
151 username: profile.name,
152 access_token: Mutex::new(res.minecraft_access_token),
153 uuid: profile.id,
154 msa: Mutex::new(msa),
155 certs: Mutex::new(None),
156 })
157 }
158}
159impl AccountTrait for MicrosoftWithAccessTokenAccount {
160 fn username(&self) -> &str {
161 &self.username
162 }
163 fn uuid(&self) -> Uuid {
164 self.uuid
165 }
166 fn access_token(&self) -> Option<String> {
167 Some(self.access_token.lock().to_owned())
168 }
169 fn certs(&self) -> Option<azalea_auth::certs::Certificates> {
170 self.certs.lock().as_ref().cloned()
171 }
172 fn set_certs(&self, certs: azalea_auth::certs::Certificates) {
173 *self.certs.lock() = Some(certs);
174 }
175 fn refresh(&self) -> BoxFuture<'_, Result<(), azalea_auth::AuthError>> {
176 Box::pin(async {
177 let msa_value = self.msa.lock().clone();
178 let new_account = MicrosoftWithAccessTokenAccount::new(msa_value, None, None).await?;
179
180 let new_access_token = new_account.access_token().unwrap();
181
182 *self.access_token.lock() = new_access_token;
183 *self.msa.lock() = new_account.msa.lock().clone();
184
185 Ok(())
186 })
187 }
188 fn join<'a>(
189 &'a self,
190 public_key: &'a [u8],
191 private_key: &'a [u8; 16],
192 server_id: &'a str,
193 proxy: Option<reqwest::Proxy>,
194 ) -> BoxFuture<'a, Result<(), ClientSessionServerError>> {
195 Box::pin(async move {
196 let access_token = self.access_token.lock().clone();
197 sessionserver::join(SessionServerJoinOpts {
198 access_token: &access_token,
199 public_key,
200 private_key,
201 uuid: &self.uuid(),
202 server_id,
203 proxy,
204 })
205 .await
206 })
207 }
208}
209
210impl Account {
211 #[cfg(feature = "online-mode")]
217 pub async fn microsoft(cache_key: &str) -> Result<Self, azalea_auth::AuthError> {
218 Self::microsoft_with_custom_client_id_and_scope(cache_key, None, None).await
219 }
220
221 #[cfg(feature = "online-mode")]
226 pub async fn microsoft_with_custom_client_id_and_scope(
227 cache_key: &str,
228 client_id: Option<&str>,
229 scope: Option<&str>,
230 ) -> Result<Self, azalea_auth::AuthError> {
231 MicrosoftAccount::new(cache_key, client_id, scope)
232 .await
233 .map(Account::from)
234 }
235
236 #[cfg(feature = "online-mode")]
261 pub async fn with_microsoft_access_token(
262 msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
263 ) -> Result<Self, azalea_auth::AuthError> {
264 Self::with_microsoft_access_token_and_custom_client_id_and_scope(msa, None, None).await
265 }
266
267 #[cfg(feature = "online-mode")]
270 pub async fn with_microsoft_access_token_and_custom_client_id_and_scope(
271 msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
272 client_id: Option<&str>,
273 scope: Option<&str>,
274 ) -> Result<Self, azalea_auth::AuthError> {
275 MicrosoftWithAccessTokenAccount::new(msa, client_id, scope)
276 .await
277 .map(Account::from)
278 }
279}