1use std::sync::Arc;
2use tokio::sync::RwLock;
3
4use crate::error::WaveError;
5
6const TOKEN_URL: &str = "https://api.waveapps.com/oauth2/token/";
7
8pub type TokenRefreshCallback = Arc<dyn Fn(&str, &str) + Send + Sync>;
10
11#[derive(Clone)]
13pub struct OAuthConfig {
14 pub client_id: String,
15 pub client_secret: String,
16 pub access_token: String,
17 pub refresh_token: String,
18 pub redirect_uri: String,
19 pub on_token_refresh: Option<TokenRefreshCallback>,
21}
22
23#[derive(Clone)]
25pub(crate) struct AuthState {
26 pub(crate) config: OAuthConfig,
27 pub(crate) tokens: Arc<RwLock<TokenPair>>,
28}
29
30pub(crate) struct TokenPair {
32 pub access_token: String,
33 pub refresh_token: String,
34}
35
36impl AuthState {
37 pub fn new(config: OAuthConfig) -> Self {
38 let tokens = TokenPair {
39 access_token: config.access_token.clone(),
40 refresh_token: config.refresh_token.clone(),
41 };
42 Self {
43 config,
44 tokens: Arc::new(RwLock::new(tokens)),
45 }
46 }
47
48 pub async fn access_token(&self) -> String {
49 self.tokens.read().await.access_token.clone()
50 }
51
52 pub async fn refresh(&self, http: &reqwest::Client) -> Result<(), WaveError> {
54 let refresh_token = self.tokens.read().await.refresh_token.clone();
55
56 let params = [
57 ("client_id", self.config.client_id.as_str()),
58 ("client_secret", self.config.client_secret.as_str()),
59 ("refresh_token", refresh_token.as_str()),
60 ("grant_type", "refresh_token"),
61 ("redirect_uri", self.config.redirect_uri.as_str()),
62 ];
63
64 let resp = http
65 .post(TOKEN_URL)
66 .form(¶ms)
67 .send()
68 .await
69 .map_err(|e| WaveError::TokenRefresh(e.to_string()))?;
70
71 if !resp.status().is_success() {
72 let status = resp.status();
73 let body = resp.text().await.unwrap_or_default();
74 return Err(WaveError::TokenRefresh(format!(
75 "HTTP {status} — {body}"
76 )));
77 }
78
79 let body: serde_json::Value = resp
80 .json()
81 .await
82 .map_err(|e| WaveError::TokenRefresh(e.to_string()))?;
83
84 let new_access = body["access_token"]
85 .as_str()
86 .ok_or_else(|| WaveError::TokenRefresh("missing access_token in response".into()))?;
87 let new_refresh = body["refresh_token"]
88 .as_str()
89 .ok_or_else(|| WaveError::TokenRefresh("missing refresh_token in response".into()))?;
90
91 {
93 let mut tokens = self.tokens.write().await;
94 tokens.access_token = new_access.to_string();
95 tokens.refresh_token = new_refresh.to_string();
96 }
97
98 if let Some(cb) = &self.config.on_token_refresh {
100 cb(new_access, new_refresh);
101 }
102
103 Ok(())
104 }
105}