highlevel_api/auth/
oauth.rs1use reqwest::Client;
2use std::sync::Arc;
3use url::Url;
4use crate::{
5 config::HighLevelConfig,
6 error::{Error, Result},
7};
8use super::token::TokenData;
9
10const AUTH_BASE_URL: &str = "https://marketplace.gohighlevel.com";
11const TOKEN_URL: &str = "https://services.leadconnectorhq.com/oauth/token";
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AccessType {
15 Online,
16 Offline,
17}
18
19impl AccessType {
20 pub fn as_str(self) -> &'static str {
21 match self {
22 AccessType::Online => "online",
23 AccessType::Offline => "offline",
24 }
25 }
26}
27
28pub struct OAuthClient {
29 config: Arc<HighLevelConfig>,
30 inner: Client,
31}
32
33impl OAuthClient {
34 pub fn new(config: Arc<HighLevelConfig>, inner: Client) -> Self {
35 Self { config, inner }
36 }
37
38 pub fn authorization_url(&self, scopes: &[&str], access_type: AccessType) -> String {
40 let mut url = Url::parse(AUTH_BASE_URL).expect("static URL is valid");
41 url.set_path("/oauth/chooselocation");
42 {
43 let mut q = url.query_pairs_mut();
44 q.append_pair("response_type", "code");
45 if let Some(redirect_uri) = &self.config.redirect_uri {
46 q.append_pair("redirect_uri", redirect_uri);
47 }
48 q.append_pair("client_id", &self.config.client_id);
49 q.append_pair("scope", &scopes.join(" "));
50 q.append_pair("access_type", access_type.as_str());
51 }
52 url.to_string()
53 }
54
55 pub async fn exchange_code(&self, code: &str) -> Result<TokenData> {
57 let redirect_uri = self.config.redirect_uri.clone().unwrap_or_default();
58 let params = [
59 ("client_id", self.config.client_id.as_str()),
60 ("client_secret", self.config.client_secret.as_str()),
61 ("grant_type", "authorization_code"),
62 ("code", code),
63 ("redirect_uri", &redirect_uri),
64 ];
65 self.fetch_token(¶ms).await
66 }
67
68 pub async fn refresh_token(&self, refresh_token: &str) -> Result<TokenData> {
70 let params = [
71 ("client_id", self.config.client_id.as_str()),
72 ("client_secret", self.config.client_secret.as_str()),
73 ("grant_type", "refresh_token"),
74 ("refresh_token", refresh_token),
75 ];
76 self.fetch_token(¶ms).await
77 }
78
79 async fn fetch_token(&self, params: &[(&str, &str)]) -> Result<TokenData> {
80 let resp = self.inner.post(TOKEN_URL).form(params).send().await?;
81 if !resp.status().is_success() {
82 let status = resp.status().as_u16();
83 let message = resp.text().await.unwrap_or_default();
84 return Err(Error::Api { status, message });
85 }
86 let mut token: TokenData = resp.json().await?;
87 token.mark_issued_now();
88 Ok(token)
89 }
90}