1use std::collections::HashMap;
2
3use anyhow::Result;
4use base64::Engine;
5use url::{ParseError, Url};
6
7#[derive(Debug, Clone, Default, Deserialize, Serialize)]
8pub 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
18pub 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
47pub 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 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 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
96pub 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 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 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}