coven_storage/
token_custody.rs1use crate::{OAuthTokens, S3Credentials, StorageCredentials, StorageError};
5use std::sync::Arc;
6
7#[async_trait::async_trait]
10pub trait TokenCustody: Send + Sync {
11 async fn replace(&self, rejected: &OAuthTokens) -> Result<OAuthTokens, StorageError>;
17}
18
19#[derive(Clone)]
21pub enum ProviderAuth {
22 S3(S3Credentials),
24 OAuth {
26 tokens: OAuthTokens,
28 custody: Arc<dyn TokenCustody>,
30 },
31 CloudKit,
33}
34
35impl ProviderAuth {
36 pub fn new(credentials: StorageCredentials, custody: Arc<dyn TokenCustody>) -> Self {
38 match credentials {
39 StorageCredentials::S3(keys) => Self::S3(keys),
40 StorageCredentials::OAuth(tokens) => Self::OAuth { tokens, custody },
41 StorageCredentials::CloudKit => Self::CloudKit,
42 }
43 }
44
45 pub fn credentials(&self) -> StorageCredentials {
47 match self {
48 Self::S3(keys) => StorageCredentials::S3(keys.clone()),
49 Self::OAuth { tokens, .. } => StorageCredentials::OAuth(tokens.clone()),
50 Self::CloudKit => StorageCredentials::CloudKit,
51 }
52 }
53}
54
55pub(crate) struct HeldTokens {
58 current: tokio::sync::RwLock<OAuthTokens>,
59 custody: Arc<dyn TokenCustody>,
60 replacing: tokio::sync::Mutex<()>,
61 #[cfg(test)]
64 waits: tokio::sync::watch::Sender<usize>,
65}
66
67impl HeldTokens {
68 pub(crate) fn new(tokens: OAuthTokens, custody: Arc<dyn TokenCustody>) -> Self {
69 Self {
70 current: tokio::sync::RwLock::new(tokens),
71 custody,
72 replacing: tokio::sync::Mutex::new(()),
73 #[cfg(test)]
74 waits: tokio::sync::watch::channel(0).0,
75 }
76 }
77
78 async fn one_replacement(&self) -> tokio::sync::MutexGuard<'_, ()> {
81 #[cfg(test)]
82 match self.replacing.try_lock() {
83 Ok(one) => return one,
84 Err(_) => self.waits.send_modify(|waits| *waits += 1),
85 }
86 self.replacing.lock().await
87 }
88
89 #[cfg(test)]
92 pub(crate) fn waiting_from_now(&self) -> impl std::future::Future<Output = ()> {
93 let mut waits = self.waits.subscribe();
94 let now = *waits.borrow_and_update();
95 async move {
96 coven_foundation::clock::within(waits.wait_for(|waits| *waits > now))
97 .await
98 .expect("a call waits for the replacement")
99 .expect("the waits are kept");
100 }
101 }
102
103 #[cfg(test)]
105 pub(crate) async fn hold(&self) -> tokio::sync::RwLockWriteGuard<'_, OAuthTokens> {
106 self.current.write().await
107 }
108
109 pub(crate) async fn current(&self) -> OAuthTokens {
111 self.current.read().await.clone()
112 }
113
114 pub(crate) async fn install(&self, tokens: OAuthTokens) {
118 let _one = self.one_replacement().await;
119 *self.current.write().await = tokens;
120 }
121
122 pub(crate) async fn rejected(&self, used: &OAuthTokens) -> Result<OAuthTokens, StorageError> {
124 let _one = self.one_replacement().await;
125 let current = self.current().await;
126 if current.access_token.as_str() != used.access_token.as_str() {
127 return Ok(current);
128 }
129 let replacement = self.custody.replace(used).await?;
130 *self.current.write().await = replacement.clone();
131 Ok(replacement)
132 }
133}
134
135#[cfg(test)]
136#[path = "token_custody_tests.rs"]
137pub(crate) mod tests;