1use std::path::PathBuf;
2
3use azalea_auth::{
4 AccessTokenResponse, AuthOpts,
5 certs::Certificates,
6 sessionserver::{self, ClientSessionServerError, SessionServerJoinOpts},
7};
8use parking_lot::Mutex;
9use uuid::Uuid;
10
11use crate::account::{Account, AccountTrait, BoxFuture};
12
13fn default_cache_file() -> PathBuf {
14 let minecraft_dir = minecraft_folder_path::minecraft_dir().unwrap_or_else(|| {
15 panic!(
16 "No {} environment variable found",
17 minecraft_folder_path::home_env_var()
18 )
19 });
20 minecraft_dir.join("azalea-auth.json")
21}
22
23#[derive(Clone, Debug, Default)]
27pub struct MicrosoftAccountOpts {
28 pub check_ownership: bool,
30 pub cache_file: Option<PathBuf>,
35 pub client_id: Option<String>,
37 pub scope: Option<String>,
39}
40
41impl MicrosoftAccountOpts {
42 fn to_auth_opts(&self) -> AuthOpts<'_> {
43 let cache_file = self
44 .cache_file
45 .clone()
46 .or_else(|| Some(default_cache_file()));
47
48 AuthOpts {
49 check_ownership: self.check_ownership,
50 cache_file,
51 client_id: self.client_id.as_deref(),
52 scope: self.scope.as_deref(),
53 }
54 }
55}
56
57fn default_account_opts(client_id: Option<&str>, scope: Option<&str>) -> MicrosoftAccountOpts {
58 MicrosoftAccountOpts {
59 check_ownership: false,
60 cache_file: Some(default_cache_file()),
61 client_id: client_id.map(str::to_owned),
62 scope: scope.map(str::to_owned),
63 }
64}
65
66#[derive(Debug)]
72pub struct MicrosoftAccount {
73 cache_key: String,
74 auth_opts: MicrosoftAccountOpts,
75
76 username: String,
77 uuid: Uuid,
78
79 access_token: Mutex<String>,
80 certs: Mutex<Option<Certificates>>,
81}
82impl MicrosoftAccount {
83 async fn new(
86 cache_key: &str,
87 auth_opts: MicrosoftAccountOpts,
88 ) -> Result<Self, azalea_auth::AuthError> {
89 let auth_result = azalea_auth::auth(cache_key, auth_opts.to_auth_opts()).await?;
90
91 Ok(Self {
92 cache_key: cache_key.to_owned(),
93 auth_opts,
94 username: auth_result.profile.name,
95 uuid: auth_result.profile.id,
96 access_token: Mutex::new(auth_result.access_token),
97 certs: Mutex::new(None),
98 })
99 }
100}
101impl AccountTrait for MicrosoftAccount {
102 fn username(&self) -> &str {
103 &self.username
104 }
105 fn uuid(&self) -> Uuid {
106 self.uuid
107 }
108 fn access_token(&self) -> Option<String> {
109 Some(self.access_token.lock().to_owned())
110 }
111 fn certs(&self) -> Option<azalea_auth::certs::Certificates> {
112 self.certs.lock().as_ref().cloned()
113 }
114 fn set_certs(&self, certs: azalea_auth::certs::Certificates) {
115 *self.certs.lock() = Some(certs);
116 }
117 fn refresh(&self) -> BoxFuture<'_, Result<(), azalea_auth::AuthError>> {
118 Box::pin(async {
119 let new_account =
120 MicrosoftAccount::new(&self.cache_key, self.auth_opts.clone()).await?;
121 let new_access_token = new_account.access_token().unwrap();
122 *self.access_token.lock() = new_access_token;
123 Ok(())
124 })
125 }
126 fn join<'a>(
127 &'a self,
128 public_key: &'a [u8],
129 private_key: &'a [u8; 16],
130 server_id: &'a str,
131 proxy: Option<reqwest::Proxy>,
132 ) -> BoxFuture<'a, Result<(), ClientSessionServerError>> {
133 Box::pin(async move {
134 let access_token = self.access_token.lock().clone();
135 sessionserver::join(SessionServerJoinOpts {
136 access_token: &access_token,
137 public_key,
138 private_key,
139 uuid: &self.uuid(),
140 server_id,
141 proxy,
142 })
143 .await
144 })
145 }
146}
147
148#[derive(Debug)]
157pub struct MicrosoftWithAccessTokenAccount {
158 msa: Mutex<azalea_auth::cache::ExpiringValue<AccessTokenResponse>>,
159
160 username: String,
161 uuid: Uuid,
162
163 access_token: Mutex<String>,
164 certs: Mutex<Option<Certificates>>,
165}
166impl MicrosoftWithAccessTokenAccount {
167 async fn new(
168 msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
169 client_id: Option<&str>,
170 scope: Option<&str>,
171 ) -> Result<Self, azalea_auth::AuthError> {
172 let client = reqwest::Client::new();
173
174 let mut msa = msa.clone();
175
176 if msa.is_expired() {
177 use tracing::trace;
178
179 trace!("refreshing Microsoft auth token");
180 msa = azalea_auth::refresh_ms_auth_token(
181 &client,
182 &msa.data.refresh_token,
183 client_id,
184 scope,
185 )
186 .await?;
187 }
188
189 let msa_token = &msa.data.access_token;
190 let res = azalea_auth::get_minecraft_token(&client, msa_token).await?;
191 let profile = azalea_auth::get_profile(&client, &res.minecraft_access_token).await?;
192
193 Ok(Self {
194 username: profile.name,
195 access_token: Mutex::new(res.minecraft_access_token),
196 uuid: profile.id,
197 msa: Mutex::new(msa),
198 certs: Mutex::new(None),
199 })
200 }
201}
202impl AccountTrait for MicrosoftWithAccessTokenAccount {
203 fn username(&self) -> &str {
204 &self.username
205 }
206 fn uuid(&self) -> Uuid {
207 self.uuid
208 }
209 fn access_token(&self) -> Option<String> {
210 Some(self.access_token.lock().to_owned())
211 }
212 fn certs(&self) -> Option<azalea_auth::certs::Certificates> {
213 self.certs.lock().as_ref().cloned()
214 }
215 fn set_certs(&self, certs: azalea_auth::certs::Certificates) {
216 *self.certs.lock() = Some(certs);
217 }
218 fn refresh(&self) -> BoxFuture<'_, Result<(), azalea_auth::AuthError>> {
219 Box::pin(async {
220 let msa_value = self.msa.lock().clone();
221 let new_account = MicrosoftWithAccessTokenAccount::new(msa_value, None, None).await?;
222
223 let new_access_token = new_account.access_token().unwrap();
224
225 *self.access_token.lock() = new_access_token;
226 *self.msa.lock() = new_account.msa.lock().clone();
227
228 Ok(())
229 })
230 }
231 fn join<'a>(
232 &'a self,
233 public_key: &'a [u8],
234 private_key: &'a [u8; 16],
235 server_id: &'a str,
236 proxy: Option<reqwest::Proxy>,
237 ) -> BoxFuture<'a, Result<(), ClientSessionServerError>> {
238 Box::pin(async move {
239 let access_token = self.access_token.lock().clone();
240 sessionserver::join(SessionServerJoinOpts {
241 access_token: &access_token,
242 public_key,
243 private_key,
244 uuid: &self.uuid(),
245 server_id,
246 proxy,
247 })
248 .await
249 })
250 }
251}
252
253impl Account {
254 #[cfg(feature = "online-mode")]
260 pub async fn microsoft(cache_key: &str) -> Result<Self, azalea_auth::AuthError> {
261 MicrosoftAccount::new(cache_key, default_account_opts(None, None))
262 .await
263 .map(Account::from)
264 }
265
266 #[cfg(feature = "online-mode")]
276 pub async fn microsoft_with_opts(
277 cache_key: &str,
278 auth_opts: MicrosoftAccountOpts,
279 ) -> Result<Self, azalea_auth::AuthError> {
280 MicrosoftAccount::new(cache_key, auth_opts)
281 .await
282 .map(Account::from)
283 }
284
285 #[cfg(feature = "online-mode")]
290 #[deprecated(note = "Use `Account::microsoft_with_opts` instead.")]
291 pub async fn microsoft_with_custom_client_id_and_scope(
292 cache_key: &str,
293 client_id: Option<&str>,
294 scope: Option<&str>,
295 ) -> Result<Self, azalea_auth::AuthError> {
296 MicrosoftAccount::new(cache_key, default_account_opts(client_id, scope))
297 .await
298 .map(Account::from)
299 }
300
301 #[cfg(feature = "online-mode")]
326 pub async fn with_microsoft_access_token(
327 msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
328 ) -> Result<Self, azalea_auth::AuthError> {
329 Self::with_microsoft_access_token_and_custom_client_id_and_scope(msa, None, None).await
330 }
331
332 #[cfg(feature = "online-mode")]
335 pub async fn with_microsoft_access_token_and_custom_client_id_and_scope(
336 msa: azalea_auth::cache::ExpiringValue<AccessTokenResponse>,
337 client_id: Option<&str>,
338 scope: Option<&str>,
339 ) -> Result<Self, azalea_auth::AuthError> {
340 MicrosoftWithAccessTokenAccount::new(msa, client_id, scope)
341 .await
342 .map(Account::from)
343 }
344}