async_dingtalk/
core.rs

1use std::collections::HashMap;
2
3use crate::DingTalk;
4use deadpool_redis::redis::cmd;
5use serde::{Deserialize, Serialize};
6use url::{form_urlencoded, Url};
7
8impl DingTalk {
9    /// Generate the redirect URL for DingTalk authorization.
10    ///
11    /// [Documents](https://open.dingtalk.com/document/isvapp/obtain-identity-credentials)
12    ///
13    /// # Arguments
14    ///
15    /// * `redirect_uri` - The redirect URI after authorization.
16    /// * `state` - An optional state string, which is used to prevent CSRF attacks.
17    ///
18    /// # Returns
19    ///
20    /// The redirect URL as a string.
21    pub fn get_redirect_url(&self, redirect_uri: String, state: Option<String>) -> String {
22        let mut url = Url::parse("https://login.dingtalk.com/oauth2/auth").unwrap();
23
24        let query = form_urlencoded::Serializer::new(String::new())
25            .append_pair("redirect_uri", &redirect_uri)
26            .append_pair("response_type", "code")
27            .append_pair("client_id", self.appid.as_ref())
28            .append_pair("scope", "openid corpid")
29            .append_pair("state", state.unwrap_or("".to_string()).as_ref())
30            .append_pair("prompt", "consent")
31            .finish();
32
33        url.set_query(Some(&query));
34
35        url.to_string()
36    }
37
38    /// Obtain the access token for the application.
39    ///
40    /// This asynchronous function sends a POST request to the DingTalk API to obtain the access token
41    /// for the application. The request includes the authorization code in the query parameters for
42    /// authentication.
43    ///
44    /// [Documents](https://open.dingtalk.com/document/isvapp/obtain-identity-credentials)
45    ///
46    /// # Arguments
47    ///
48    /// * `code` - The authorization code to obtain the access token.
49    ///
50    /// # Returns
51    ///
52    /// Returns a `Result` containing the access token as a string if the request is successful,
53    /// or an error if the request fails or if the response status is not successful.
54    ///
55    /// # Errors
56    ///
57    /// Returns an error if the response status is not successful, or if the request fails.
58    pub async fn set_app_access_token(
59        &self,
60        code: String,
61    ) -> Result<String, Box<dyn std::error::Error>> {
62        let mut params = HashMap::new();
63        params.insert("clientId", self.appid.clone());
64        params.insert("clientSecret", self.app_secret.clone());
65        params.insert("code", code.clone());
66        params.insert("refreshToken", "".to_string());
67        params.insert("grantType", "authorization_code".to_string());
68
69        let response = self
70            .client
71            .post("https://api.dingtalk.com/v1.0/oauth2/userAccessToken")
72            .json(&params)
73            .send()
74            .await?;
75
76        if !response.status().is_success() {
77            return Err(format!("Failed to get access token: {}", response.status()).into());
78        }
79
80        #[derive(Serialize, Deserialize, Debug)]
81        struct AccessToken {
82            #[serde(rename = "accessToken")]
83            pub access_token: String,
84            #[serde(rename = "refreshToken")]
85            pub refresh_token: String,
86            #[serde(rename = "corpId")]
87            pub corp_id: String,
88            #[serde(rename = "expireIn")]
89            pub expire_in: i32,
90        }
91        let at = response.json::<AccessToken>().await?;
92
93        let mut rdb = self.rdb.get().await.unwrap();
94        cmd("SET")
95            .arg(&self.appid)
96            .arg(serde_json::to_string(&at)?)
97            .query_async::<()>(&mut rdb)
98            .await
99            .unwrap();
100
101        Ok(at.corp_id) // 企业corpId
102    }
103
104    /// Get the access token for the application.
105    ///
106    /// The access token is stored in Redis by calling [set_app_access_token].
107    ///
108    /// # Returns
109    ///
110    /// A Result containing the access token as a string if the access token exists, otherwise an error string.
111    pub async fn get_app_access_token(&self) -> Result<String, Box<dyn std::error::Error>> {
112        let mut rdb = self.rdb.get().await.unwrap();
113        let value: Option<String> = cmd("GET")
114            .arg(&self.appid)
115            .query_async(&mut rdb)
116            .await
117            .unwrap_or(None);
118
119        #[derive(Serialize, Deserialize, Debug)]
120        struct AccessToken {
121            #[serde(rename = "accessToken")]
122            pub access_token: String,
123            #[serde(rename = "refreshToken")]
124            pub refresh_token: String,
125            #[serde(rename = "corpId")]
126            pub corp_id: String,
127            #[serde(rename = "expireIn")]
128            pub expire_in: i32,
129        }
130        if let Some(bytes) = value {
131            let value: AccessToken = serde_json::from_str(&bytes).unwrap();
132            return Ok(value.access_token);
133        }
134
135        Err("Failed to get access token".into())
136    }
137}