1use std::{sync::Arc, time::Duration};
2
3use time::{Duration as TimeDuration, OffsetDateTime};
4
5use crate::{CredentialStore, Error, Result, Token};
6
7#[async_trait::async_trait]
8pub trait TokenProvider: Send + Sync {
10 async fn authenticate(&self, timeout: Duration) -> Result<Token>;
12 async fn login(&self, timeout: Duration) -> Result<Token> {
14 self.authenticate(timeout).await
15 }
16 async fn refresh(&self, token: &Token) -> Result<Token>;
18 fn can_authenticate_silently(&self) -> bool {
20 false
21 }
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
26pub struct AuthOptions {
27 #[uniffi(default = 300)]
29 pub refresh_buffer_seconds: i64,
30 #[uniffi(default = 30)]
32 pub lock_timeout_seconds: u64,
33 #[uniffi(default = 3600)]
35 pub login_timeout_seconds: u64,
36 #[uniffi(default = None)]
38 pub callback_image_src: Option<String>,
39}
40
41impl Default for AuthOptions {
42 fn default() -> Self {
43 Self {
44 refresh_buffer_seconds: 300,
45 lock_timeout_seconds: 30,
46 login_timeout_seconds: 3600,
47 callback_image_src: None,
48 }
49 }
50}
51
52impl AuthOptions {
53 pub fn refresh_buffer(&self) -> TimeDuration {
55 TimeDuration::seconds(self.refresh_buffer_seconds)
56 }
57
58 pub fn lock_timeout(&self) -> Duration {
60 Duration::from_secs(self.lock_timeout_seconds)
61 }
62
63 pub fn login_timeout(&self) -> Duration {
65 Duration::from_secs(self.login_timeout_seconds)
66 }
67}
68
69#[async_trait::async_trait]
74pub trait AuthSession: Send + Sync {
75 fn auth_client(&self) -> &AuthClient;
77
78 fn store_name(&self) -> &'static str {
80 self.auth_client().store_name()
81 }
82
83 async fn token_with_login(&self, login: Option<bool>) -> Result<Token> {
85 match login {
86 Some(true) => self.login().await,
87 Some(false) => self.token().await,
88 None => self.token_or_login().await,
89 }
90 }
91
92 async fn login(&self) -> Result<Token> {
94 self.auth_client().login().await
95 }
96
97 async fn token(&self) -> Result<Token> {
99 self.auth_client().token().await
100 }
101
102 async fn token_or_login(&self) -> Result<Token> {
104 self.auth_client().token_or_login().await
105 }
106
107 async fn force_refresh(&self) -> Result<Token> {
109 self.auth_client().force_refresh().await
110 }
111
112 async fn refresh_rejected_token(&self, stale: &str) -> Result<Token> {
114 self.auth_client().refresh_rejected_token(stale).await
115 }
116
117 async fn logout(&self) -> Result<()> {
119 self.auth_client().logout().await
120 }
121}
122
123pub struct AuthClient {
125 key: String,
126 flow: Arc<dyn TokenProvider>,
127 store: Arc<dyn CredentialStore>,
128 options: AuthOptions,
129}
130
131impl AuthSession for AuthClient {
132 fn auth_client(&self) -> &AuthClient {
133 self
134 }
135}
136
137impl AuthClient {
138 pub fn new(
140 key: String,
141 flow: Arc<dyn TokenProvider>,
142 store: Arc<dyn CredentialStore>,
143 options: AuthOptions,
144 ) -> Self {
145 Self {
146 key,
147 flow,
148 store,
149 options,
150 }
151 }
152
153 pub fn store_name(&self) -> &'static str {
155 self.store.name()
156 }
157
158 pub async fn login(&self) -> Result<Token> {
160 let cache_key = self.key.clone();
161 let lock = self
162 .store
163 .lock(&cache_key, self.options.lock_timeout())
164 .await?;
165 let result = async {
166 let (_, token) = tokio::try_join!(
167 self.store.prepare_write(),
168 self.flow.login(self.options.login_timeout())
169 )?;
170 self.store.save(&cache_key, &token).await?;
171 Ok(public_token(token))
172 }
173 .await;
174 release(lock, result).await
175 }
176
177 pub async fn token(&self) -> Result<Token> {
179 self.load_token(false).await
180 }
181
182 async fn load_token(&self, login: bool) -> Result<Token> {
183 let cache_key = self.key.clone();
184 let now = OffsetDateTime::now_utc();
185 if let Some(token) = self.store.load(&cache_key).await? {
186 if self.can_reuse(&token, now) {
187 return Ok(public_token(token));
188 }
189 }
190
191 let lock = self
192 .store
193 .lock(&cache_key, self.options.lock_timeout())
194 .await?;
195 let result = async {
196 let token = self.store.load(&cache_key).await?;
197 let now = OffsetDateTime::now_utc();
198 if let Some(token) = token.as_ref() {
199 if self.can_reuse(token, now) {
200 return Ok(public_token(token.clone()));
201 }
202 }
203 self.renew(token, login).await
204 }
205 .await;
206 release(lock, result).await
207 }
208
209 pub async fn token_or_login(&self) -> Result<Token> {
211 self.load_token(true).await
212 }
213
214 pub async fn force_refresh(&self) -> Result<Token> {
216 self.refresh_rejected(None).await
217 }
218
219 pub async fn refresh_rejected_token(&self, stale_access_token: &str) -> Result<Token> {
221 self.refresh_rejected(Some(stale_access_token)).await
222 }
223
224 async fn refresh_rejected(&self, stale_access_token: Option<&str>) -> Result<Token> {
225 let cache_key = self.key.clone();
226 let lock = self
227 .store
228 .lock(&cache_key, self.options.lock_timeout())
229 .await?;
230 let result = async {
231 let token = self.store.load(&cache_key).await?;
232 if let (Some(stale), Some(current)) = (stale_access_token, token.as_ref()) {
233 if current.access_token != stale
234 && self.can_reuse(current, OffsetDateTime::now_utc())
235 {
236 return Ok(public_token(current.clone()));
237 }
238 }
239 self.renew(token, false).await
240 }
241 .await;
242 release(lock, result).await
243 }
244
245 pub async fn logout(&self) -> Result<()> {
247 let cache_key = self.key.clone();
248 let lock = self
249 .store
250 .lock(&cache_key, self.options.lock_timeout())
251 .await?;
252 let result = self.store.delete(&cache_key).await;
253 release(lock, result).await
254 }
255
256 fn can_reuse(&self, token: &Token, now: OffsetDateTime) -> bool {
257 token.is_valid(now) && !token.needs_refresh(now, self.options.refresh_buffer())
258 }
259
260 async fn renew(&self, token: Option<Token>, login: bool) -> Result<Token> {
261 if token.is_none() && !login && !self.flow.can_authenticate_silently() {
262 return Err(Error::LoginRequired(self.key.clone()));
263 }
264 self.store.prepare_write().await?;
265 let renewed = match token {
266 Some(token) => self.flow.refresh(&token).await?,
267 None => self.flow.authenticate(self.options.login_timeout()).await?,
268 };
269 self.store.save(&self.key, &renewed).await?;
270 Ok(public_token(renewed))
271 }
272}
273
274async fn release<T>(lock: Box<dyn crate::StorageLock>, result: Result<T>) -> Result<T> {
275 let released = lock.release().await;
276 match result {
277 Err(error) => Err(error),
278 Ok(value) => released.map(|()| value),
279 }
280}
281
282fn public_token(mut token: Token) -> Token {
283 token.refresh_token = None;
284 token
285}