1use std::str::FromStr;
4
5use anyhow::Context;
6use base64::prelude::*;
7use surf::StatusCode;
8
9use crate::{
10 auth::structs::{mojang::*, AuthMethod},
11 http::RequestResult,
12 password::Password,
13 prelude::*,
14};
15
16#[derive(Debug, Default, Deserialize)]
17#[serde(default)]
18struct ServerMetaLinks {
19 pub homepage: String,
20}
21
22#[derive(Debug, Default, Deserialize)]
23#[serde(default)]
24#[serde(rename_all = "camelCase")]
25struct ServerMeta {
26 pub server_name: String,
27 pub links: Option<ServerMetaLinks>,
28}
29
30#[derive(Debug, Default, Deserialize)]
31struct APIMetaData {
32 pub meta: ServerMeta,
33}
34
35#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
36#[serde(rename_all = "camelCase")]
37pub(crate) struct RefreshBody {
38 pub access_token: Password,
39 #[serde(skip_serializing_if = "String::is_empty")]
40 pub client_token: String,
41 pub request_user: bool,
42 pub selected_profile: Option<AvaliableProfile>,
43}
44
45async fn get_head_skin(api_location: &str, uuid: &str) -> DynResult<(Vec<u8>, Vec<u8>)> {
46 let uri = format!("{api_location}sessionserver/session/minecraft/profile/{uuid}");
47 let result: ProfileResponse = crate::http::no_retry::get(&uri)
48 .await
49 .map_err(|e| anyhow::anyhow!("发送获取皮肤请求到 {} 时发生错误:{:?}", uri, e))?
50 .body_json()
51 .await
52 .map_err(|e| anyhow::anyhow!("接收获取皮肤响应到 {} 时发生错误:{:?}", uri, e))?;
53 if let Some(prop) = result
54 .properties
55 .iter()
56 .find(|a| a.name.as_str() == "textures")
57 {
58 let texture_raw = &prop.value;
59 let texture_raw = BASE64_STANDARD.decode(texture_raw)?;
60 let texture_data: ProfileTexture = serde_json::from_slice(&texture_raw)?;
61 if let Some(textures) = texture_data.textures {
62 if let Some(skin) = textures.skin {
63 let skin_url = skin.url;
64 crate::auth::parse_head_skin(
65 crate::http::no_retry::get(skin_url)
66 .recv_bytes()
67 .await
68 .map_err(|e| anyhow::anyhow!(e))?,
69 )
70 } else {
71 Ok(Default::default())
72 }
73 } else {
74 Ok(Default::default())
75 }
76 } else {
77 Ok(Default::default())
78 }
79}
80
81pub async fn refresh_token(
85 auth_method: AuthMethod,
86 client_token: &str,
87 provide_selected_profile: bool,
88) -> DynResult<AuthMethod> {
89 if let AuthMethod::AuthlibInjector {
90 api_location,
91 server_name,
92 server_homepage,
93 server_meta,
94 access_token,
95 uuid,
96 player_name,
97 ..
98 } = auth_method
99 {
100 let res: RequestResult<AuthenticateResponse> = dbg!(crate::http::no_retry::post_data(
101 dbg!(&format!("{api_location}authserver/refresh")),
102 dbg!(&RefreshBody {
103 access_token: access_token.to_owned(),
104 client_token: client_token.to_owned(),
105 request_user: provide_selected_profile,
106 selected_profile: if provide_selected_profile {
107 Some(AvaliableProfile {
108 name: player_name.to_owned(),
109 id: uuid.to_owned(),
110 })
111 } else {
112 None
113 },
114 }),
115 )
116 .await
117 .context("无法请求刷新令牌接口")?);
118
119 match res {
120 RequestResult::Ok(res) => {
121 let selected_profile = res.selected_profile.unwrap_or_else(|| AvaliableProfile {
122 name: player_name.to_owned(),
123 id: uuid.to_owned(),
124 });
125
126 let (head_skin, hat_skin) =
127 get_head_skin(&api_location, &selected_profile.id).await?;
128
129 let refreshed_method = AuthMethod::AuthlibInjector {
130 api_location: api_location.to_owned(),
131 server_name: server_name.to_owned(),
132 server_homepage,
133 server_meta,
134 access_token: res.access_token,
135 uuid: selected_profile.id,
136 player_name: selected_profile.name,
137 head_skin,
138 hat_skin,
139 };
140
141 Ok(refreshed_method)
142 }
143 RequestResult::Err(a) => {
144 if a.error_message.is_empty() {
145 match a.error.as_str() {
146 "ForbiddenOperationException" => anyhow::bail!("未授权的访问"),
147 "IllegalArgumentException" => anyhow::bail!("非法令牌绑定"),
148 _ => anyhow::bail!("未知原因:{}", a.error),
149 }
150 } else {
151 anyhow::bail!("{}:{}", a.error, a.error_message)
152 }
153 }
154 }
155 } else {
156 anyhow::bail!("此函数只支持 Authlib Injector 第三方登录")
157 }
158}
159
160pub async fn start_auth(
167 _ctx: Option<impl Reporter>,
168 authlib_host: &str,
169 username: String,
170 password: Password,
171 client_token: &str,
172) -> DynResult<Vec<AuthMethod>> {
173 let api_location = {
175 let a = crate::http::get(authlib_host)
176 .await
177 .map_err(|_| anyhow::anyhow!("无法请求 Authlib API 服务器:{}", authlib_host))?;
178 if let Some(h) = a.header("X-Authlib-Injector-API-Location") {
179 h.last().to_string()
180 } else {
181 authlib_host.to_owned()
182 }
183 };
184
185 let api_location = {
187 if api_location.starts_with("http") {
188 url::Url::parse(&api_location)?
189 } else {
190 url::Url::parse(authlib_host)?.join(&api_location)?
191 }
192 };
193
194 let api_location = api_location.to_string();
195 let api_location = if api_location.ends_with('/') {
196 api_location
197 } else {
198 format!("{api_location}/")
199 };
200 let api_location_url = url::Url::from_str(&api_location)?;
201
202 let meta_res: RequestResult<APIMetaData> = crate::http::no_retry::get_data(&api_location)
203 .await
204 .map_err(|e| anyhow::anyhow!("无法接收 Authlib 服务器元数据响应:{:?}", e))?;
205
206 let (server_name, server_homepage) = if let RequestResult::Ok(meta) = meta_res {
207 let mut result = (String::new(), String::new());
208 if meta.meta.server_name.is_empty() {
209 result.0 = url::Url::from_str(&api_location)?
210 .host()
211 .ok_or_else(|| anyhow::anyhow!("无法取得 Authlib 服务器接口的 Host 部分"))?
212 .to_string();
213 } else {
214 result.0 = meta.meta.server_name;
215 }
216 if let Some(server_homepage) = meta.meta.links.map(|a| a.homepage) {
217 result.1 = server_homepage;
218 } else {
219 result.1 = api_location_url.origin().ascii_serialization();
220 }
221 result
222 } else {
223 (
224 api_location_url
225 .host()
226 .ok_or_else(|| anyhow::anyhow!("无法取得 Authlib 服务器接口的 Host 部分"))?
227 .to_string(),
228 api_location_url.origin().ascii_serialization(),
229 )
230 };
231
232 let server_meta = crate::http::no_retry::get(&api_location)
233 .recv_bytes()
234 .await
235 .map_err(|e| anyhow::anyhow!("无法接收登录接口元数据:{:?}", e))?;
236 let server_meta = BASE64_STANDARD.encode(server_meta);
237
238 let auth_url = format!("{api_location}authserver/authenticate");
240 let auth_body = AuthenticateBody {
241 username: username.to_owned(),
242 password,
243 client_token: client_token.to_owned(),
244 ..Default::default()
245 };
246 let resp: RequestResult<AuthenticateResponse> =
247 crate::http::no_retry::post_data(&auth_url, &auth_body)
248 .await
249 .map_err(|e| anyhow::anyhow!("无法解析登录接口回调:{} {:?}", auth_url, e))?;
250
251 match resp {
252 RequestResult::Ok(a) => {
253 if let Some(selected_profile) = a.selected_profile {
254 if selected_profile.name == username {
255 let (head_skin, hat_skin) =
256 get_head_skin(&api_location, &selected_profile.id).await?;
257 return Ok(vec![AuthMethod::AuthlibInjector {
258 api_location,
259 server_name,
260 server_homepage,
261 server_meta,
262 access_token: a.access_token,
263 uuid: selected_profile.id,
264 player_name: selected_profile.name,
265 head_skin,
266 hat_skin,
267 }]);
268 }
269 }
270 if !a.available_profiles.is_empty() {
271 if let Some(profile) = a.available_profiles.iter().find(|x| x.name == username) {
272 let (head_skin, hat_skin) = get_head_skin(&api_location, &profile.id).await?;
273 return Ok(vec![AuthMethod::AuthlibInjector {
274 api_location,
275 server_name,
276 server_homepage,
277 server_meta,
278 access_token: a.access_token,
279 uuid: profile.id.to_owned(),
280 player_name: profile.name.to_owned(),
281 head_skin,
282 hat_skin,
283 }]);
284 }
285
286 let skins_threads =
287 futures::future::join_all(a.available_profiles.into_iter().map(|x| async {
288 let (head_skin, hat_skin) = get_head_skin(&api_location, &x.id)
289 .await
290 .unwrap_or_else(|_| (vec![0; 2 * 4 * 64], vec![0; 2 * 4 * 64]));
291 AuthMethod::AuthlibInjector {
292 api_location: api_location.to_owned(),
293 server_name: server_name.to_owned(),
294 server_homepage: server_homepage.to_owned(),
295 server_meta: server_meta.to_owned(),
296 access_token: a.access_token.to_owned(),
297 uuid: x.id,
298 player_name: x.name,
299 head_skin,
300 hat_skin,
301 }
302 }))
303 .await;
304 Ok(skins_threads)
305 } else {
306 anyhow::bail!("该账户没有可用的角色!")
307 }
308 }
309 RequestResult::Err(a) => {
310 if a.error_message.is_empty() {
311 match a.error.as_str() {
312 "ForbiddenOperationException" => anyhow::bail!("未授权的访问"),
313 "IllegalArgumentException" => anyhow::bail!("非法令牌绑定"),
314 _ => anyhow::bail!("未知原因:{}", a.error),
315 }
316 } else {
317 anyhow::bail!("{}:{}", a.error, a.error_message)
318 }
319 }
320 }
321}
322
323pub async fn validate(
325 api_location: &str,
326 access_token: &str,
327 client_token: &str,
328) -> DynResult<bool> {
329 let post_url = url::Url::parse(api_location)?.join("authserver/validate")?;
330 let resp = crate::http::post(post_url)
331 .body_json(&ValidateResponse {
332 access_token: access_token.into(),
333 client_token: client_token.to_owned(),
334 })
335 .map_err(|_| anyhow::anyhow!("无法序列化请求"))?
336 .await
337 .map_err(|_| anyhow::anyhow!("无法请求 Authlib API 服务器:{}", api_location))?;
338 Ok(resp.status() == StatusCode::NoContent)
339}