scl_core/auth/
mod.rs

1/*!
2    此模块为登录验证模块,开发者可以调用此处的函数获取不同种类账户验证之后的登录令牌。
3*/
4
5use std::io::Cursor;
6
7use base64::prelude::*;
8use image::{GenericImageView, Pixel};
9use structs::mojang::{ProfileResponse, ProfileTexture};
10
11use self::structs::AuthMethod;
12use crate::{
13    http::{no_retry::*, RequestResult},
14    password::Password,
15    prelude::*,
16};
17
18pub mod authlib;
19pub mod microsoft;
20pub mod structs;
21
22/// 提取一个皮肤位图的正面头部部分,用于 GUI 展示头像
23///
24/// 传入的皮肤大小必须是 32x64 或 64x64
25pub fn parse_head_skin(result: Vec<u8>) -> DynResult<(Vec<u8>, Vec<u8>)> {
26    let cursor = Cursor::new(result);
27    let mut skin_data = Vec::with_capacity(2 * 4 * 64);
28    let mut skin_hat_data = Vec::with_capacity(2 * 4 * 64);
29    let skin = image::load(cursor, image::ImageFormat::Png)?;
30    for y in 8..16 {
31        for x in 8..16 {
32            let pixel = skin.get_pixel(x, y).to_rgba();
33            skin_data.push(pixel.0[0]);
34            skin_data.push(pixel.0[1]);
35            skin_data.push(pixel.0[2]);
36            skin_data.push(pixel.0[3]);
37        }
38    }
39    for y in 8..16 {
40        for x in 40..48 {
41            let pixel = skin.get_pixel(x, y).to_rgba();
42            skin_hat_data.push(pixel.0[0]);
43            skin_hat_data.push(pixel.0[1]);
44            skin_hat_data.push(pixel.0[2]);
45            skin_hat_data.push(pixel.0[3]);
46        }
47    }
48    Ok((skin_data, skin_hat_data))
49}
50
51async fn get_head_skin(uuid: &str) -> DynResult<(Vec<u8>, Vec<u8>)> {
52    // https://sessionserver.mojang.com/session/minecraft/profile/{uuid}
53    let uri = format!("https://sessionserver.mojang.com/session/minecraft/profile/{uuid}");
54    let result: ProfileResponse = crate::http::get(uri)
55        .await
56        .map_err(|e| anyhow::anyhow!(e))?
57        .body_json()
58        .await
59        .map_err(|e| anyhow::anyhow!(e))?;
60    if let Some(prop) = result
61        .properties
62        .iter()
63        .find(|a| a.name.as_str() == "textures")
64    {
65        let texture_raw = &prop.value;
66        let texture_raw = BASE64_STANDARD.decode(texture_raw)?;
67        let texture_data: ProfileTexture = serde_json::from_slice(&texture_raw)?;
68        if let Some(textures) = texture_data.textures {
69            if let Some(skin) = textures.skin {
70                let skin_url = skin.url;
71                parse_head_skin(
72                    crate::http::get(skin_url)
73                        .recv_bytes()
74                        .await
75                        .map_err(|e| anyhow::anyhow!(e))?,
76                )
77            } else {
78                Ok(Default::default())
79            }
80        } else {
81            Ok(Default::default())
82        }
83    } else {
84        Ok(Default::default())
85    }
86}
87
88/// 进行 Mojang 正版验证
89///
90/// **此验证方式已经弃用**,请开发者建议用户迁移到 Microsoft 账户后使用 [`crate::auth::microsoft::start_auth`] 进行 Microsoft 正版验证
91pub async fn auth_mojang(
92    _ctx: Option<impl Reporter>,
93    username: &str,
94    password: &Password,
95    client_token: &str,
96) -> DynResult<AuthMethod> {
97    // https://authserver.mojang.com/authenticate
98    let body = structs::mojang::AuthenticateBody {
99        username: username.into(),
100        password: password.to_owned(),
101        client_token: client_token.into(),
102        ..Default::default()
103    };
104    let result: RequestResult<structs::mojang::AuthenticateResponse> =
105        post_data("https://authserver.mojang.com/authenticate", &body).await?;
106    match result {
107        RequestResult::Ok(a) => {
108            let selected_profile = if let Some(selected_profile) = a.selected_profile {
109                selected_profile
110            } else if let Some(profile) = a.available_profiles.into_iter().next() {
111                profile // TODO: 选择所需要添加的多角色
112            } else {
113                anyhow::bail!("该账户没有可用的角色!")
114            };
115            let (head_skin, hat_skin) = get_head_skin(&selected_profile.id).await?;
116            Ok(AuthMethod::Mojang {
117                access_token: a.access_token,
118                uuid: selected_profile.id,
119                player_name: selected_profile.name,
120                head_skin,
121                hat_skin,
122            })
123        }
124        RequestResult::Err(_) => Ok(AuthMethod::Offline {
125            player_name: "".into(),
126            uuid: "".into(),
127        }),
128    }
129}
130
131/// 刷新/续期访问令牌
132pub async fn refresh_auth(am: &mut AuthMethod, client_token: &str) -> DynResult<bool> {
133    if let &mut AuthMethod::Microsoft { .. } = am {
134        return Ok(microsoft::leagcy::refresh_auth(am).await.is_ok());
135    }
136    match am {
137        AuthMethod::Mojang { access_token, .. } => {
138            let body = structs::mojang::ValidateResponse {
139                access_token: access_token.to_owned(),
140                client_token: client_token.to_owned(),
141            };
142            let result = crate::http::post("https://authserver.mojang.com/validate")
143                .body(serde_json::to_vec(&body)?)
144                .header("Content-Type", "application/json")
145                .await
146                .map_err(|e| anyhow::anyhow!("发送用户信息请求失败,可能是网络问题:{:?}", e))?;
147            if result.status().is_success() {
148                Ok(true)
149            } else {
150                Ok(false)
151            }
152        }
153        AuthMethod::Microsoft { access_token, .. } => {
154            // TODO: 增加正确的检测方式
155            let profile_resp =
156                crate::http::get("https://api.minecraftservices.com/minecraft/profile")
157                    .header("Authorization", &format!("Bearer {}", &access_token))
158                    .await
159                    .map_err(|e| {
160                        anyhow::anyhow!("发送用户信息请求失败,有可能是网络问题:{:?}", e)
161                    })?;
162            Ok(profile_resp.status().is_success())
163        }
164        AuthMethod::AuthlibInjector { .. } => {
165            if let Ok(new_am) =
166                crate::auth::authlib::refresh_token(am.to_owned(), client_token, false).await
167            {
168                *am = new_am;
169                Ok(true)
170            } else {
171                Ok(false)
172            }
173        }
174        _ => Ok(true),
175    }
176}