wechat_minapp/user/
credential.rs

1use super::User;
2use super::user_info::{UserBuilder, UserInfo};
3#[allow(deprecated)]
4use aes::{
5    Aes128,
6    cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7, generic_array::GenericArray},
7};
8use base64::{Engine, engine::general_purpose::STANDARD};
9use cbc::Decryptor;
10use hex::encode;
11use hmac::{Hmac, Mac};
12use http::{HeaderValue, Method, Request};
13use serde::{Deserialize, Serialize};
14use serde_json::from_slice;
15use sha2::Sha256;
16use std::collections::HashMap;
17use tracing::{debug, instrument};
18
19use crate::{Result, constants, error::Error::InternalServer, response::Response};
20
21type Aes128CbcDec = Decryptor<Aes128>;
22
23#[derive(Serialize, Deserialize, Clone)]
24pub struct Credential {
25    open_id: String,
26    session_key: String,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    union_id: Option<String>,
29}
30
31impl Credential {
32    pub fn open_id(&self) -> &str {
33        &self.open_id
34    }
35
36    pub fn session_key(&self) -> &str {
37        &self.session_key
38    }
39
40    pub fn union_id(&self) -> Option<&str> {
41        self.union_id.as_deref()
42    }
43
44    /// 解密用户数据,使用的是 AES-128-CBC 算法,数据采用PKCS#7填充。
45    /// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
46    /// ```no_run
47    /// use wechat_minapp::client::WechatMinappSDK;
48    /// use wechat_minapp::user::{User, Contact};
49    ///
50    ///  #[tokio::main]
51    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
52    ///     let client = WechatMinappSDK::new("app_id", "secret");
53    ///     let user = User::new(client);
54    ///     let code = "0816abc123def456";
55    ///     let credential = user.login(code).await?;
56    ///     let info = credential.decrypt(&encrypted_data, &iv)?;
57    ///     println!("昵称: {}", info.nickname());
58    ///     println!("性别: {}", info.gender());
59    ///     println!("地区: {}-{}-{}", info.country(), info.province(), info.city());
60    ///     println!("头像: {}", info.avatar());
61    ///     println!("AppID: {}", info.app_id());
62    ///     println!("时间戳: {}", info.timestamp());
63    ///     
64    ///     Ok(())
65    /// }
66    /// ```
67    #[instrument(skip(self, encrypted_data, iv))]
68    pub fn decrypt(&self, encrypted_data: &str, iv: &str) -> Result<UserInfo> {
69        debug!("encrypted_data: {}", encrypted_data);
70        debug!("iv: {}", iv);
71
72        let key = STANDARD.decode(self.session_key.as_bytes())?;
73        let iv = STANDARD.decode(iv.as_bytes())?;
74        #[allow(deprecated)]
75        let decryptor = Aes128CbcDec::new(
76            &GenericArray::clone_from_slice(&key),
77            &GenericArray::clone_from_slice(&iv),
78        );
79
80        let encrypted_data = STANDARD.decode(encrypted_data.as_bytes())?;
81
82        let buffer = decryptor.decrypt_padded_vec_mut::<Pkcs7>(&encrypted_data)?;
83
84        let builder = from_slice::<UserBuilder>(&buffer)?;
85
86        debug!("user builder: {:#?}", builder);
87
88        Ok(builder.build())
89    }
90}
91
92impl std::fmt::Debug for Credential {
93    // 为了安全,不打印 session_key
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("Credential")
96            .field("open_id", &self.open_id)
97            .field("session_key", &"********")
98            .field("union_id", &self.union_id)
99            .finish()
100    }
101}
102
103#[derive(Deserialize)]
104pub(crate) struct CredentialBuilder {
105    #[serde(rename = "openid")]
106    open_id: String,
107    session_key: String,
108    #[serde(rename = "unionid")]
109    union_id: Option<String>,
110}
111
112impl CredentialBuilder {
113    pub(crate) fn build(self) -> Credential {
114        Credential {
115            open_id: self.open_id,
116            session_key: self.session_key,
117            union_id: self.union_id,
118        }
119    }
120}
121
122impl std::fmt::Debug for CredentialBuilder {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        f.debug_struct("CredentialBuilder")
125            .field("open_id", &self.open_id)
126            .field("session_key", &"********")
127            .field("union_id", &self.union_id)
128            .finish()
129    }
130}
131
132type HmacSha256 = Hmac<Sha256>;
133
134impl User {
135    /// 检查登录态是否过期
136    /// [官方文档](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html)
137    #[instrument(skip(self, session_key, open_id))]
138    pub async fn check_session_key(&self, session_key: &str, open_id: &str) -> Result<()> {
139        let mut mac = HmacSha256::new_from_slice(session_key.as_bytes())?;
140        mac.update(b"");
141        let hasher = mac.finalize();
142        let signature = encode(hasher.into_bytes());
143
144        let mut map = HashMap::new();
145
146        map.insert("openid", open_id.to_string());
147        map.insert("signature", signature);
148        map.insert("sig_method", "hmac_sha256".into());
149        let mut url = url::Url::parse(constants::CHECK_SESSION_KEY_END_POINT)?;
150        url.query_pairs_mut().extend_pairs(&map);
151        let client = &self.client.client;
152        let query = serde_json::to_vec(&map)?;
153        let request = Request::builder()
154            .uri(url.as_str())
155            .method(Method::GET)
156            .header(
157                "User-Agent",
158                HeaderValue::from_static(constants::HTTP_CLIENT_USER_AGENT),
159            )
160            .body(query)?;
161
162        let response = client.execute(request).await?;
163
164        debug!("response: {:#?}", response);
165
166        if response.status().is_success() {
167            Ok(())
168        } else {
169            let (_parts, body) = response.into_parts();
170            let message = String::from_utf8_lossy(&body.to_vec()).to_string();
171            Err(crate::error::Error::InternalServer(message))
172        }
173    }
174
175    /// 重置用户的 session_key
176    /// [官方文档](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/ResetUserSessionKey.html)
177    #[instrument(skip(self, open_id))]
178    pub async fn reset_session_key(&self, session_key: &str, open_id: &str) -> Result<Credential> {
179        let mut mac = HmacSha256::new_from_slice(session_key.as_bytes())?;
180        mac.update(b"");
181        let hasher = mac.finalize();
182        let signature = encode(hasher.into_bytes());
183
184        let mut map = HashMap::new();
185        map.insert("access_token", self.client.token().await?);
186        map.insert("openid", open_id.to_string());
187        map.insert("signature", signature);
188        map.insert("sig_method", "hmac_sha256".into());
189
190        let mut url = url::Url::parse(constants::RESET_SESSION_KEY_END_POINT)?;
191        url.query_pairs_mut().extend_pairs(&map);
192        let client = &self.client.client;
193        let query = serde_json::to_vec(&map)?;
194        let request = Request::builder()
195            .uri(url.as_str())
196            .method(Method::GET)
197            .header(
198                "User-Agent",
199                HeaderValue::from_static(constants::HTTP_CLIENT_USER_AGENT),
200            )
201            .body(query)?;
202
203        let response = client.execute(request).await?;
204        debug!("response: {:#?}", &response);
205
206        if response.status().is_success() {
207            let (_parts, body) = response.into_parts();
208            let json = serde_json::from_slice::<Response<CredentialBuilder>>(&body.to_vec())?;
209
210            let credential = json.extract()?.build();
211
212            debug!("credential: {:#?}", credential);
213
214            Ok(credential)
215        } else {
216            let (_parts, body) = response.into_parts();
217            let message = String::from_utf8_lossy(&body.to_vec()).to_string();
218            Err(InternalServer(message))
219        }
220    }
221}