Skip to main content

twapi_reqwest/
oauth.rs

1use base64::engine::general_purpose::STANDARD;
2use base64::Engine;
3use reqwest::{Error, Response};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::time::Duration;
7use twapi_oauth::calc_oauth_header;
8
9use crate::build_client;
10
11/// Base URL of the Twitter OAuth endpoints. Used when `base_url` is `None`.
12pub(crate) const DEFAULT_BASE_URL: &str = "https://api.twitter.com";
13
14pub async fn get_bearer_token_response(
15    consumer_key: &str,
16    consumer_secret: &str,
17    timeout_sec: Option<Duration>,
18    base_url: Option<&str>,
19) -> Result<Response, Error> {
20    let base_url = base_url.unwrap_or(DEFAULT_BASE_URL);
21    let key = STANDARD.encode(format!("{}:{}", consumer_key, consumer_secret));
22    let client = build_client(timeout_sec);
23    client
24        .post(format!("{}/oauth2/token", base_url))
25        .header(
26            "Content-Type",
27            "application/x-www-form-urlencoded;charset=UTF-8",
28        )
29        .header("Authorization", &format!("Basic {}", key))
30        .body("grant_type=client_credentials")
31        .send()
32        .await
33}
34
35pub async fn get_bearer_token(
36    consumer_key: &str,
37    consumer_secret: &str,
38    timeout_sec: Option<Duration>,
39    base_url: Option<&str>,
40) -> Result<Option<String>, Error> {
41    let json: Value =
42        get_bearer_token_response(consumer_key, consumer_secret, timeout_sec, base_url)
43            .await?
44            .json()
45            .await?;
46    match json["access_token"].as_str() {
47        Some(access_token) => Ok(Some(access_token.to_string())),
48        None => Ok(None),
49    }
50}
51
52pub async fn request_token_response(
53    consumer_key: &str,
54    consumer_secret: &str,
55    oauth_callback: &str,
56    x_auth_access_type: Option<&str>,
57    timeout_sec: Option<Duration>,
58    base_url: Option<&str>,
59) -> Result<Response, Error> {
60    let base_url = base_url.unwrap_or(DEFAULT_BASE_URL);
61    let uri = format!("{}/oauth/request_token", base_url);
62    let uri = uri.as_str();
63    let mut header_options = vec![("oauth_callback", oauth_callback)];
64    if let Some(x_auth_access_type) = x_auth_access_type {
65        header_options.push(("x_auth_access_type", x_auth_access_type));
66    }
67    let signed = calc_oauth_header(
68        &format!("{}&", consumer_secret),
69        consumer_key,
70        &header_options,
71        "POST",
72        uri,
73        &vec![],
74    );
75    let client = build_client(timeout_sec);
76    client
77        .post(uri)
78        .header("Authorization", &format!("OAuth {}", signed))
79        .send()
80        .await
81}
82
83pub async fn request_token(
84    consumer_key: &str,
85    consumer_secret: &str,
86    oauth_callback: &str,
87    x_auth_access_type: Option<&str>,
88    timeout_sec: Option<Duration>,
89    base_url: Option<&str>,
90) -> Result<HashMap<String, String>, Error> {
91    let response = request_token_response(
92        consumer_key,
93        consumer_secret,
94        oauth_callback,
95        x_auth_access_type,
96        timeout_sec,
97        base_url,
98    )
99    .await?;
100    Ok(parse_oauth_body(response).await)
101}
102
103pub async fn access_token_response(
104    consumer_key: &str,
105    consumer_secret: &str,
106    oauth_token: &str,
107    oauth_token_secret: &str,
108    oauth_verifier: &str,
109    timeout_sec: Option<Duration>,
110    base_url: Option<&str>,
111) -> Result<Response, Error> {
112    let base_url = base_url.unwrap_or(DEFAULT_BASE_URL);
113    let uri = format!("{}/oauth/access_token", base_url);
114    let uri = uri.as_str();
115    let signed = calc_oauth_header(
116        &format!("{}&{}", consumer_secret, oauth_token_secret),
117        consumer_key,
118        &vec![
119            ("oauth_token", oauth_token),
120            ("oauth_verifier", oauth_verifier),
121        ],
122        "POST",
123        uri,
124        &vec![],
125    );
126    let client = build_client(timeout_sec);
127    client
128        .post(uri)
129        .header("Authorization", &format!("OAuth {}", signed))
130        .send()
131        .await
132}
133
134pub async fn access_token(
135    consumer_key: &str,
136    consumer_secret: &str,
137    oauth_token: &str,
138    oauth_token_secret: &str,
139    oauth_verifier: &str,
140    timeout_sec: Option<Duration>,
141    base_url: Option<&str>,
142) -> Result<HashMap<String, String>, Error> {
143    let response = access_token_response(
144        consumer_key,
145        consumer_secret,
146        oauth_token,
147        oauth_token_secret,
148        oauth_verifier,
149        timeout_sec,
150        base_url,
151    )
152    .await?;
153    Ok(parse_oauth_body(response).await)
154}
155
156async fn parse_oauth_body(response: Response) -> HashMap<String, String> {
157    let status = response.status();
158    let mut result = HashMap::new();
159    match response.text().await {
160        Ok(body) => {
161            if status.is_success() {
162                for item in body.split("&") {
163                    let mut pair = item.split("=");
164                    if let Some(key) = pair.next() {
165                        result.insert(key.to_owned(), pair.next().unwrap_or("").to_owned());
166                    }
167                }
168            } else {
169                result.insert("twapi_request_body".to_owned(), body.clone());
170                result.insert(
171                    "twapi_request_status_code".to_owned(),
172                    status.as_str().to_owned(),
173                );
174            }
175        }
176        Err(_) => {}
177    }
178    result
179}