Skip to main content

xurl/store/
tokens.rs

1//! Token CRUD operations — save, get, clear for Bearer, OAuth2, OAuth1.
2
3use super::TokenStore;
4use super::types::{OAuth1Token, OAuth2Token, Token, TokenType};
5use crate::error::Result;
6
7#[allow(dead_code)] // Public library API — used by consumers and integration tests
8impl TokenStore {
9    // ── Save ─────────────────────────────────────────────────────────
10
11    /// Saves a bearer token into the resolved app.
12    ///
13    /// # Errors
14    ///
15    /// Returns an error if the store cannot be saved to disk.
16    pub fn save_bearer_token(&mut self, token: &str) -> Result<()> {
17        self.save_bearer_token_for_app("", token)
18    }
19
20    /// Saves a bearer token into the named app.
21    ///
22    /// # Errors
23    ///
24    /// Returns an error if the store cannot be saved to disk.
25    pub fn save_bearer_token_for_app(&mut self, app_name: &str, token: &str) -> Result<()> {
26        let app = self.resolve_app_mut(app_name);
27        app.bearer_token = Some(Token {
28            token_type: TokenType::Bearer,
29            bearer: Some(token.to_string()),
30            oauth2: None,
31            oauth1: None,
32        });
33        self.save_to_file()
34    }
35
36    /// Saves an `OAuth2` token into the resolved app.
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if the store cannot be saved to disk.
41    pub fn save_oauth2_token(
42        &mut self,
43        username: &str,
44        access_token: &str,
45        refresh_token: &str,
46        expiration_time: u64,
47    ) -> Result<()> {
48        self.save_oauth2_token_for_app("", username, access_token, refresh_token, expiration_time)
49    }
50
51    /// Saves an `OAuth2` token into the named app.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error if the store cannot be saved to disk.
56    pub fn save_oauth2_token_for_app(
57        &mut self,
58        app_name: &str,
59        username: &str,
60        access_token: &str,
61        refresh_token: &str,
62        expiration_time: u64,
63    ) -> Result<()> {
64        let app = self.resolve_app_mut(app_name);
65        app.oauth2_tokens.insert(
66            username.to_string(),
67            Token {
68                token_type: TokenType::Oauth2,
69                bearer: None,
70                oauth2: Some(OAuth2Token {
71                    access_token: access_token.to_string(),
72                    refresh_token: refresh_token.to_string(),
73                    expiration_time,
74                }),
75                oauth1: None,
76            },
77        );
78        self.save_to_file()
79    }
80
81    /// Saves an `OAuth2` token into the named app's unnamed (`/me`-failed salvage) slot.
82    ///
83    /// Used by the refresh and exchange paths when post-token username discovery
84    /// fails: the refreshed access token is still valid and is preserved here
85    /// rather than discarded. Single-occupancy, last-write-wins per `KTD1`.
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if the store cannot be saved to disk.
90    pub fn save_oauth2_token_unnamed_for_app(
91        &mut self,
92        app_name: &str,
93        access_token: &str,
94        refresh_token: &str,
95        expiration_time: u64,
96    ) -> Result<()> {
97        let app = self.resolve_app_mut(app_name);
98        app.unnamed_oauth2_token = Some(Token {
99            token_type: TokenType::Oauth2,
100            bearer: None,
101            oauth2: Some(OAuth2Token {
102                access_token: access_token.to_string(),
103                refresh_token: refresh_token.to_string(),
104                expiration_time,
105            }),
106            oauth1: None,
107        });
108        self.save_to_file()
109    }
110
111    /// Saves `OAuth1` tokens into the resolved app.
112    ///
113    /// # Errors
114    ///
115    /// Returns an error if the store cannot be saved to disk.
116    pub fn save_oauth1_tokens(
117        &mut self,
118        access_token: &str,
119        token_secret: &str,
120        consumer_key: &str,
121        consumer_secret: &str,
122    ) -> Result<()> {
123        self.save_oauth1_tokens_for_app(
124            "",
125            access_token,
126            token_secret,
127            consumer_key,
128            consumer_secret,
129        )
130    }
131
132    /// Saves `OAuth1` tokens into the named app.
133    ///
134    /// # Errors
135    ///
136    /// Returns an error if the store cannot be saved to disk.
137    pub fn save_oauth1_tokens_for_app(
138        &mut self,
139        app_name: &str,
140        access_token: &str,
141        token_secret: &str,
142        consumer_key: &str,
143        consumer_secret: &str,
144    ) -> Result<()> {
145        let app = self.resolve_app_mut(app_name);
146        app.oauth1_token = Some(Token {
147            token_type: TokenType::Oauth1,
148            bearer: None,
149            oauth2: None,
150            oauth1: Some(OAuth1Token {
151                access_token: access_token.to_string(),
152                token_secret: token_secret.to_string(),
153                consumer_key: consumer_key.to_string(),
154                consumer_secret: consumer_secret.to_string(),
155            }),
156        });
157        self.save_to_file()
158    }
159
160    // ── Get ──────────────────────────────────────────────────────────
161
162    /// Gets an `OAuth2` token for a username from the resolved app.
163    #[must_use]
164    pub fn get_oauth2_token(&self, username: &str) -> Option<&Token> {
165        self.get_oauth2_token_for_app("", username)
166    }
167
168    /// Gets an `OAuth2` token for a username from the named app.
169    #[must_use]
170    pub fn get_oauth2_token_for_app(&self, app_name: &str, username: &str) -> Option<&Token> {
171        let app = self.resolve_app(app_name);
172        app.oauth2_tokens.get(username)
173    }
174
175    /// Gets the first `OAuth2` token from the resolved app.
176    #[must_use]
177    pub fn get_first_oauth2_token(&self) -> Option<&Token> {
178        self.get_first_oauth2_token_for_app("")
179    }
180
181    /// Gets the default user's token, or the first `OAuth2` token from the named app.
182    #[must_use]
183    pub fn get_first_oauth2_token_for_app(&self, app_name: &str) -> Option<&Token> {
184        let app = self.resolve_app(app_name);
185        // Prefer the default user if one is set and still has a token
186        if !app.default_user.is_empty()
187            && let Some(token) = app.oauth2_tokens.get(&app.default_user)
188        {
189            return Some(token);
190        }
191        app.oauth2_tokens.values().next()
192    }
193
194    /// Gets the unnamed (`/me`-failed salvage) `OAuth2` token from the named app.
195    ///
196    /// Returns `None` when the slot is empty.
197    #[must_use]
198    pub fn get_oauth2_token_unnamed_for_app(&self, app_name: &str) -> Option<&Token> {
199        let app = self.resolve_app(app_name);
200        app.unnamed_oauth2_token.as_ref()
201    }
202
203    /// Gets `OAuth1` tokens from the resolved app.
204    #[must_use]
205    pub fn get_oauth1_tokens(&self) -> Option<&Token> {
206        self.get_oauth1_tokens_for_app("")
207    }
208
209    /// Gets `OAuth1` tokens from the named app.
210    #[must_use]
211    pub fn get_oauth1_tokens_for_app(&self, app_name: &str) -> Option<&Token> {
212        let app = self.resolve_app(app_name);
213        app.oauth1_token.as_ref()
214    }
215
216    /// Gets the bearer token from the resolved app.
217    #[must_use]
218    pub fn get_bearer_token(&self) -> Option<&Token> {
219        self.get_bearer_token_for_app("")
220    }
221
222    /// Gets the bearer token from the named app.
223    #[must_use]
224    pub fn get_bearer_token_for_app(&self, app_name: &str) -> Option<&Token> {
225        let app = self.resolve_app(app_name);
226        app.bearer_token.as_ref()
227    }
228
229    /// Returns the names of every app in the store that holds at least one
230    /// stored credential (OAuth2 token, OAuth1 tokens, or bearer token).
231    ///
232    /// Iterates in `BTreeMap` key order so the result is deterministic.
233    /// Used by the `get_auth_header` resolver to surface a "wrong-app"
234    /// envelope when the active app is empty but the user has credentials
235    /// stored under a different app.
236    #[must_use]
237    pub fn apps_with_credentials(&self) -> Vec<String> {
238        self.apps
239            .iter()
240            .filter(|(_, app)| {
241                !app.oauth2_tokens.is_empty()
242                    || app.oauth1_token.is_some()
243                    || app.bearer_token.is_some()
244                    || app.unnamed_oauth2_token.is_some()
245            })
246            .map(|(name, _)| name.clone())
247            .collect()
248    }
249
250    // ── Clear ────────────────────────────────────────────────────────
251
252    /// Clears an `OAuth2` token for a username from the resolved app.
253    ///
254    /// # Errors
255    ///
256    /// Returns an error if the store cannot be saved to disk.
257    pub fn clear_oauth2_token(&mut self, username: &str) -> Result<()> {
258        self.clear_oauth2_token_for_app("", username)
259    }
260
261    /// Clears an `OAuth2` token for a username from the named app.
262    ///
263    /// # Errors
264    ///
265    /// Returns an error if the store cannot be saved to disk.
266    pub fn clear_oauth2_token_for_app(&mut self, app_name: &str, username: &str) -> Result<()> {
267        let app = self.resolve_app_mut(app_name);
268        app.oauth2_tokens.remove(username);
269        self.save_to_file()
270    }
271
272    /// Clears `OAuth1` tokens from the resolved app.
273    ///
274    /// # Errors
275    ///
276    /// Returns an error if the store cannot be saved to disk.
277    pub fn clear_oauth1_tokens(&mut self) -> Result<()> {
278        self.clear_oauth1_tokens_for_app("")
279    }
280
281    /// Clears `OAuth1` tokens from the named app.
282    ///
283    /// # Errors
284    ///
285    /// Returns an error if the store cannot be saved to disk.
286    pub fn clear_oauth1_tokens_for_app(&mut self, app_name: &str) -> Result<()> {
287        let app = self.resolve_app_mut(app_name);
288        app.oauth1_token = None;
289        self.save_to_file()
290    }
291
292    /// Clears the bearer token from the resolved app.
293    ///
294    /// # Errors
295    ///
296    /// Returns an error if the store cannot be saved to disk.
297    pub fn clear_bearer_token(&mut self) -> Result<()> {
298        self.clear_bearer_token_for_app("")
299    }
300
301    /// Clears the bearer token from the named app.
302    ///
303    /// # Errors
304    ///
305    /// Returns an error if the store cannot be saved to disk.
306    pub fn clear_bearer_token_for_app(&mut self, app_name: &str) -> Result<()> {
307        let app = self.resolve_app_mut(app_name);
308        app.bearer_token = None;
309        self.save_to_file()
310    }
311
312    /// Clears all tokens from the resolved app.
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if the store cannot be saved to disk.
317    pub fn clear_all(&mut self) -> Result<()> {
318        self.clear_all_for_app("")
319    }
320
321    /// Clears all tokens from the named app.
322    ///
323    /// # Errors
324    ///
325    /// Returns an error if the store cannot be saved to disk.
326    pub fn clear_all_for_app(&mut self, app_name: &str) -> Result<()> {
327        let app = self.resolve_app_mut(app_name);
328        app.oauth2_tokens.clear();
329        app.oauth1_token = None;
330        app.bearer_token = None;
331        app.unnamed_oauth2_token = None;
332        self.save_to_file()
333    }
334
335    // ── Query ────────────────────────────────────────────────────────
336
337    /// Gets all `OAuth2` usernames from the resolved app.
338    #[must_use]
339    pub fn get_oauth2_usernames(&self) -> Vec<String> {
340        self.get_oauth2_usernames_for_app("")
341    }
342
343    /// Gets all `OAuth2` usernames from the named app.
344    #[must_use]
345    pub fn get_oauth2_usernames_for_app(&self, app_name: &str) -> Vec<String> {
346        let app = self.resolve_app(app_name);
347        app.oauth2_tokens.keys().cloned().collect()
348    }
349
350    /// Checks if `OAuth1` tokens exist in the resolved app.
351    #[must_use]
352    pub fn has_oauth1_tokens(&self) -> bool {
353        self.active_app()
354            .is_some_and(|app| app.oauth1_token.is_some())
355    }
356
357    /// Checks if a bearer token exists in the resolved app.
358    #[must_use]
359    pub fn has_bearer_token(&self) -> bool {
360        self.active_app()
361            .is_some_and(|app| app.bearer_token.is_some())
362    }
363}