Skip to main content

pipedrive_rs/
auth.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use base64::Engine;
5use url::{ParseError, Url};
6
7#[derive(Debug, Clone, Default, Deserialize, Serialize)]
8/// Based on https://pipedrive.readme.io/docs/marketplace-oauth-authorization
9pub struct AuthorizationTokens {
10    pub access_token: String,
11    pub token_type: String,
12    pub refresh_token: String,
13    pub scope: String,
14    pub api_domain: String,
15    pub expires_in: usize,
16}
17
18/// Gets the correct url to the authorization portal
19pub fn get_request_auth_url(
20    client_id: &str,
21    redirect_url: &str,
22    state: Option<&str>,
23) -> Result<String, ParseError> {
24    let redirect_url = Url::parse(redirect_url)?;
25    let authorize_url = if let Some(state) = state {
26        Url::parse_with_params(
27            "https://oauth.pipedrive.com/oauth/authorize",
28            &[
29                ("client_id", client_id),
30                ("state", state),
31                ("redirect_uri", redirect_url.as_ref()),
32            ],
33        )?
34    } else {
35        Url::parse_with_params(
36            "https://oauth.pipedrive.com/oauth/authorize",
37            &[
38                ("client_id", client_id),
39                ("redirect_uri", redirect_url.as_ref()),
40            ],
41        )?
42    };
43
44    Ok(authorize_url.to_string())
45}
46
47/// Authorizes the api client
48pub async fn autorize_code(
49    client_id: &str,
50    client_secret: &str,
51    redirect_url: &str,
52    code: &str,
53) -> Result<AuthorizationTokens> {
54    use reqwest::header::{HeaderMap, HeaderValue};
55
56    let authorize_url = "https://oauth.pipedrive.com/oauth/token";
57
58    let mut base64_auth = String::new();
59    base64::engine::general_purpose::URL_SAFE_NO_PAD
60        .encode_string(format!("{client_id}:{client_secret}"), &mut base64_auth);
61
62    // headers
63    let mut auth_value = HeaderValue::from_str(&format!("Basic {}", base64_auth))?;
64    auth_value.set_sensitive(true);
65    let headers = HeaderMap::from_iter([(reqwest::header::AUTHORIZATION, auth_value)]);
66
67    // parameters
68    let form = HashMap::from([
69        ("grant_type", "authorization_code"),
70        ("redirect_uri", redirect_url),
71        ("code", code),
72    ]);
73
74    let client = reqwest::ClientBuilder::new()
75        .default_headers(headers)
76        .build()?;
77
78    let res = client.post(authorize_url).form(&form).send().await?;
79
80    if !res.status().is_success() {
81        let text = res.text().await;
82        match text {
83            Ok(text) => anyhow::bail!("Response was not successful: {}", text),
84            Err(err) => anyhow::bail!(
85                "Response was not successful and couldn't convert body to text: {:?}",
86                err
87            ),
88        }
89    }
90
91    let body: AuthorizationTokens = res.json().await?;
92
93    Ok(body)
94}
95
96// Refreshes the authorization token
97pub async fn refresh_token(
98    client_id: &str,
99    client_secret: &str,
100
101    refresh_token: &str,
102) -> Result<AuthorizationTokens> {
103    use reqwest::header::{HeaderMap, HeaderValue};
104
105    let authorize_url = "https://oauth.pipedrive.com/oauth/token";
106
107    let mut base64_auth = String::new();
108    base64::engine::general_purpose::URL_SAFE_NO_PAD
109        .encode_string(format!("{client_id}:{client_secret}"), &mut base64_auth);
110
111    // headers
112    let mut auth_value = HeaderValue::from_str(&format!("Basic {}", base64_auth))?;
113    auth_value.set_sensitive(true);
114    let headers = HeaderMap::from_iter([(reqwest::header::AUTHORIZATION, auth_value)]);
115
116    // parameters
117    let form = HashMap::from([
118        ("grant_type", "refresh_token"),
119        ("refresh_token", refresh_token),
120    ]);
121
122    let client = reqwest::ClientBuilder::new()
123        .default_headers(headers)
124        .build()?;
125
126    let res = client.post(authorize_url).form(&form).send().await?;
127
128    if !res.status().is_success() {
129        let text = res.text().await;
130        match text {
131            Ok(text) => anyhow::bail!("Response was not successful: {}", text),
132            Err(err) => anyhow::bail!(
133                "Response was not successful and couldn't convert body to text: {:?}",
134                err
135            ),
136        }
137    }
138
139    let body: AuthorizationTokens = res.json().await?;
140
141    Ok(body)
142}