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