1use serde::{Deserialize, Serialize};
2use std::time::{Duration, SystemTime, UNIX_EPOCH};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum OAuthMode {
7 Max,
9 Console,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct TokenSet {
16 pub access_token: String,
18 pub refresh_token: String,
20 pub expires_at: u64,
22}
23
24impl TokenSet {
25 pub fn is_expired(&self) -> bool {
30 self.expires_in() <= Duration::from_secs(300)
31 }
32
33 pub fn expires_in(&self) -> Duration {
37 let now = SystemTime::now()
38 .duration_since(UNIX_EPOCH)
39 .unwrap()
40 .as_secs();
41
42 if self.expires_at > now {
43 Duration::from_secs(self.expires_at - now)
44 } else {
45 Duration::ZERO
46 }
47 }
48
49 pub fn validate(&self) -> Result<(), &'static str> {
53 if self.access_token.is_empty() {
54 return Err("access_token is empty");
55 }
56 if self.refresh_token.is_empty() {
57 return Err("refresh_token is empty");
58 }
59 if self.expires_at == 0 {
60 return Err("expires_at is invalid");
61 }
62 let now = SystemTime::now()
64 .duration_since(UNIX_EPOCH)
65 .unwrap()
66 .as_secs();
67 if self.expires_at > now + 31536000 {
69 return Err("expires_at is too far in the future");
70 }
71 Ok(())
72 }
73}
74
75#[derive(Debug, Clone)]
80pub struct OAuthFlow {
81 pub authorization_url: String,
83 pub verifier: String,
85 pub state: String,
87 pub mode: OAuthMode,
89}
90
91#[derive(Debug, Clone)]
93pub struct OAuthConfig {
94 pub client_id: String,
96 pub redirect_uri: String,
98}
99
100impl Default for OAuthConfig {
101 fn default() -> Self {
102 Self {
103 client_id: "9d1c250a-e61b-44d9-88ed-5944d1962f5e".to_string(),
104 redirect_uri: "http://localhost:1455/callback".to_string(),
105 }
106 }
107}
108
109impl OAuthConfig {
110 pub fn builder() -> OAuthConfigBuilder {
112 OAuthConfigBuilder::default()
113 }
114}
115
116#[derive(Debug, Clone, Default)]
118pub struct OAuthConfigBuilder {
119 client_id: Option<String>,
120 redirect_uri: Option<String>,
121}
122
123impl OAuthConfigBuilder {
124 pub fn client_id(mut self, client_id: impl Into<String>) -> Self {
126 self.client_id = Some(client_id.into());
127 self
128 }
129
130 pub fn redirect_uri(mut self, redirect_uri: impl Into<String>) -> Self {
132 self.redirect_uri = Some(redirect_uri.into());
133 self
134 }
135
136 pub fn redirect_port(mut self, port: u16) -> Self {
138 self.redirect_uri = Some(format!("http://localhost:{}/callback", port));
139 self
140 }
141
142 pub fn build(self) -> OAuthConfig {
144 let defaults = OAuthConfig::default();
145 OAuthConfig {
146 client_id: self.client_id.unwrap_or(defaults.client_id),
147 redirect_uri: self.redirect_uri.unwrap_or(defaults.redirect_uri),
148 }
149 }
150}
151
152#[derive(Debug, Deserialize)]
154pub(crate) struct TokenResponse {
155 pub access_token: String,
156 pub refresh_token: Option<String>,
157 pub expires_in: Option<u64>,
158}
159
160impl From<TokenResponse> for TokenSet {
161 fn from(response: TokenResponse) -> Self {
162 let expires_at = SystemTime::now()
163 .duration_since(UNIX_EPOCH)
164 .unwrap()
165 .as_secs()
166 + response.expires_in.unwrap_or(3600);
167
168 TokenSet {
169 access_token: response.access_token,
170 refresh_token: response.refresh_token.unwrap_or_default(),
171 expires_at,
172 }
173 }
174}
175
176#[derive(Debug, Deserialize)]
178pub(crate) struct ApiKeyResponse {
179 pub raw_key: String,
180}